├── assets
├── list.png
├── list.psd
├── login.png
├── login.psd
├── shield.png
├── connected.png
├── connected.psd
├── email-header.jpg
├── email-header.png
├── email-header.psd
├── high-speed.png
├── high-speed.psd
├── ublock-sites.png
├── ublock-sites.psd
├── logo-shield-48.png
├── logo-shield-48.psd
├── eirevpn-logo-128.png
├── eirevpn-logo-128.psd
├── eirevpn-logo-48.png
├── eirevpn-logo-48.psd
├── logo-shield-128.png
├── logo-shield-128.psd
├── smalltile-chrome.png
├── smalltile-chrome.psd
├── social-profiler.png
└── social-profiler.psd
├── site
├── next-env.d.ts
├── static
│ ├── images
│ │ ├── mail.png
│ │ ├── contact.png
│ │ ├── shield.png
│ │ ├── download.png
│ │ ├── links-image.png
│ │ ├── icons8-chrome-144.png
│ │ ├── icons8-firefox-144.png
│ │ └── undraw_security_o890.png
│ └── js
│ │ └── cookie-policy.js
├── interfaces
│ ├── error.ts
│ ├── connection.ts
│ ├── user.ts
│ ├── settings.ts
│ ├── userplan.ts
│ ├── server.ts
│ └── plan.ts
├── .vscode
│ └── settings.json
├── pages
│ ├── admin
│ │ ├── index.tsx
│ │ ├── login.tsx
│ │ ├── userplans
│ │ │ ├── index.tsx
│ │ │ ├── create.tsx
│ │ │ └── [user_id].tsx
│ │ ├── users
│ │ │ ├── create.tsx
│ │ │ ├── index.tsx
│ │ │ └── [id].tsx
│ │ ├── servers
│ │ │ ├── create.tsx
│ │ │ ├── index.tsx
│ │ │ └── [id].tsx
│ │ ├── plans
│ │ │ ├── create.tsx
│ │ │ ├── index.tsx
│ │ │ └── [id].tsx
│ │ ├── connections.tsx
│ │ └── settings.tsx
│ ├── _app.js
│ ├── account
│ │ ├── index.tsx
│ │ └── edit.tsx
│ ├── signup.tsx
│ ├── login.tsx
│ ├── index.tsx
│ ├── confirm_email.tsx
│ ├── forgot_pass
│ │ ├── index.tsx
│ │ └── [token].tsx
│ ├── contact.tsx
│ └── downloads.tsx
├── next.config.js
├── components
│ ├── ErrorMessage.tsx
│ ├── CrudToolbar.tsx
│ ├── SuccessMessage.tsx
│ ├── ButtonMain.tsx
│ ├── Pagination.tsx
│ ├── FormCheck.tsx
│ ├── FormDatetime.tsx
│ ├── UserDashboard.tsx
│ ├── admin
│ │ ├── tables
│ │ │ ├── ConnectionsTable.tsx
│ │ │ ├── UsersTable.tsx
│ │ │ ├── PlansTable.tsx
│ │ │ ├── ServersTable.tsx
│ │ │ └── UserPlansTable.tsx
│ │ ├── AdminSidePanel.tsx
│ │ └── forms
│ │ │ ├── UserCreateForm.tsx
│ │ │ ├── PlanCreateForm.tsx
│ │ │ ├── LoginForm.tsx
│ │ │ ├── ServerCreateForm.tsx
│ │ │ ├── UserPlanCreateForm.tsx
│ │ │ ├── SettingsForm.tsx
│ │ │ └── PlanEditForm.tsx
│ ├── FormDropdown.tsx
│ ├── FormInput.tsx
│ ├── user
│ │ ├── forms
│ │ │ ├── EditDetailsForm.tsx
│ │ │ ├── ForgotPasswordForm.tsx
│ │ │ ├── ContactForm.tsx
│ │ │ ├── UpdatePasswordForm.tsx
│ │ │ └── ChangePasswordForm.tsx
│ │ └── EditDetailsDashboard.tsx
│ ├── Footer.tsx
│ ├── SubscriptionCard.tsx
│ └── Header.tsx
├── tsconfig.json
├── service
│ ├── Analytics.ts
│ └── Auth.ts
├── package.json
└── hooks
│ └── useAsync.ts
├── proxy
├── config.yaml
├── go.mod
├── config
│ └── config.go
├── main.go
└── go.sum
├── browser
├── src
│ ├── assets
│ │ ├── images
│ │ │ ├── allow.png
│ │ │ └── manage.png
│ │ └── icons
│ │ │ ├── logo-shield-48.png
│ │ │ ├── eirevpn-logo-128.png
│ │ │ ├── eirevpn-logo-48.png
│ │ │ └── logo-shield-128.png
│ ├── popup
│ │ ├── services
│ │ │ ├── comunicationManager.js
│ │ │ ├── authService.js
│ │ │ └── apiService.js
│ │ ├── popup.jsx
│ │ ├── components
│ │ │ ├── largeAlert.jsx
│ │ │ ├── container.jsx
│ │ │ └── header.jsx
│ │ ├── containers
│ │ │ ├── loading.jsx
│ │ │ ├── incognito.jsx
│ │ │ ├── settings.jsx
│ │ │ └── main.jsx
│ │ └── index.html
│ ├── background
│ │ ├── util.js
│ │ └── background-chrome.js
│ ├── _locales
│ │ └── en
│ │ │ └── messages.json
│ ├── utils
│ │ ├── storage.js
│ │ ├── hot-reload.js
│ │ └── ext.js
│ ├── manifest.json
│ └── manifest-ff.json
├── config.json
├── .vscode
│ └── settings.json
├── webpack.tests.js
├── .eslintrc
├── karma.conf.js
├── package.json
├── webpack.config.dev.js
├── webpack.config.dist.js
└── webpack.utils.js
├── api
├── integrations
│ ├── integrations.go
│ └── sendgrid
│ │ └── sendgrid.go
├── models
│ ├── base.go
│ ├── models.go
│ ├── cart.go
│ ├── forgotpass.go
│ ├── emailtoken.go
│ ├── user_app_session.go
│ ├── userplan.go
│ ├── connection.go
│ ├── server.go
│ ├── user.go
│ └── plan.go
├── util
│ ├── random
│ │ └── random.go
│ └── jwt
│ │ └── jwt.go
├── main.go
├── config.test.yaml
├── go.mod
├── db
│ └── db.go
├── handlers
│ ├── message
│ │ └── message.go
│ └── settings
│ │ └── settings.go
├── config.example.yaml
├── logger
│ └── logger.go
├── config
│ └── config.go
└── test
│ └── main_test.go
├── .gitignore
└── README.MD
/assets/list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/list.png
--------------------------------------------------------------------------------
/assets/list.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/list.psd
--------------------------------------------------------------------------------
/assets/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/login.png
--------------------------------------------------------------------------------
/assets/login.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/login.psd
--------------------------------------------------------------------------------
/assets/shield.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/shield.png
--------------------------------------------------------------------------------
/assets/connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/connected.png
--------------------------------------------------------------------------------
/assets/connected.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/connected.psd
--------------------------------------------------------------------------------
/assets/email-header.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/email-header.jpg
--------------------------------------------------------------------------------
/assets/email-header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/email-header.png
--------------------------------------------------------------------------------
/assets/email-header.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/email-header.psd
--------------------------------------------------------------------------------
/assets/high-speed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/high-speed.png
--------------------------------------------------------------------------------
/assets/high-speed.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/high-speed.psd
--------------------------------------------------------------------------------
/assets/ublock-sites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/ublock-sites.png
--------------------------------------------------------------------------------
/assets/ublock-sites.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/ublock-sites.psd
--------------------------------------------------------------------------------
/site/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/assets/logo-shield-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/logo-shield-48.png
--------------------------------------------------------------------------------
/assets/logo-shield-48.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/logo-shield-48.psd
--------------------------------------------------------------------------------
/assets/eirevpn-logo-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/eirevpn-logo-128.png
--------------------------------------------------------------------------------
/assets/eirevpn-logo-128.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/eirevpn-logo-128.psd
--------------------------------------------------------------------------------
/assets/eirevpn-logo-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/eirevpn-logo-48.png
--------------------------------------------------------------------------------
/assets/eirevpn-logo-48.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/eirevpn-logo-48.psd
--------------------------------------------------------------------------------
/assets/logo-shield-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/logo-shield-128.png
--------------------------------------------------------------------------------
/assets/logo-shield-128.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/logo-shield-128.psd
--------------------------------------------------------------------------------
/assets/smalltile-chrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/smalltile-chrome.png
--------------------------------------------------------------------------------
/assets/smalltile-chrome.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/smalltile-chrome.psd
--------------------------------------------------------------------------------
/assets/social-profiler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/social-profiler.png
--------------------------------------------------------------------------------
/assets/social-profiler.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/assets/social-profiler.psd
--------------------------------------------------------------------------------
/proxy/config.yaml:
--------------------------------------------------------------------------------
1 | App:
2 | ProxyPort: 11211
3 | RestPort: 3003
4 | ProxyUsername:
5 | ProxyPassword:
6 |
--------------------------------------------------------------------------------
/site/static/images/mail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/site/static/images/mail.png
--------------------------------------------------------------------------------
/site/static/images/contact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/site/static/images/contact.png
--------------------------------------------------------------------------------
/site/static/images/shield.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/site/static/images/shield.png
--------------------------------------------------------------------------------
/site/static/images/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/site/static/images/download.png
--------------------------------------------------------------------------------
/browser/src/assets/images/allow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/browser/src/assets/images/allow.png
--------------------------------------------------------------------------------
/site/static/images/links-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/site/static/images/links-image.png
--------------------------------------------------------------------------------
/browser/src/assets/images/manage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/browser/src/assets/images/manage.png
--------------------------------------------------------------------------------
/site/static/images/icons8-chrome-144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/site/static/images/icons8-chrome-144.png
--------------------------------------------------------------------------------
/browser/src/assets/icons/logo-shield-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/browser/src/assets/icons/logo-shield-48.png
--------------------------------------------------------------------------------
/site/static/images/icons8-firefox-144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/site/static/images/icons8-firefox-144.png
--------------------------------------------------------------------------------
/site/static/images/undraw_security_o890.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/site/static/images/undraw_security_o890.png
--------------------------------------------------------------------------------
/browser/src/assets/icons/eirevpn-logo-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/browser/src/assets/icons/eirevpn-logo-128.png
--------------------------------------------------------------------------------
/browser/src/assets/icons/eirevpn-logo-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/browser/src/assets/icons/eirevpn-logo-48.png
--------------------------------------------------------------------------------
/browser/src/assets/icons/logo-shield-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dylankilkenny/EireVPN/HEAD/browser/src/assets/icons/logo-shield-128.png
--------------------------------------------------------------------------------
/site/interfaces/error.ts:
--------------------------------------------------------------------------------
1 | export default interface APIError {
2 | status: number;
3 | code: string;
4 | title: string;
5 | detail: string;
6 | }
7 |
--------------------------------------------------------------------------------
/site/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "search.exclude": {
3 | "**/.git": true,
4 | "**/node_modules": true,
5 | "**/.next": true,
6 | }
7 | }
--------------------------------------------------------------------------------
/api/integrations/integrations.go:
--------------------------------------------------------------------------------
1 | package integrations
2 |
3 | import (
4 | "eirevpn/api/integrations/stripe"
5 | )
6 |
7 | func Init() {
8 | stripe.Init()
9 | }
10 |
--------------------------------------------------------------------------------
/browser/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "chromePath": "src",
3 | "operaPath": "src",
4 | "firefoxPath": "src",
5 | "devDirectory": "dev",
6 | "distDirectory": "dist",
7 | "tempDirectory": "temp"
8 | }
--------------------------------------------------------------------------------
/site/interfaces/connection.ts:
--------------------------------------------------------------------------------
1 | export default interface Connection {
2 | id: string;
3 | createdAt: string;
4 | updatedAt: string;
5 | server_id: number;
6 | user_id: number;
7 | server_country: string;
8 | }
9 |
--------------------------------------------------------------------------------
/browser/src/popup/services/comunicationManager.js:
--------------------------------------------------------------------------------
1 | import ext from '../../utils/ext';
2 |
3 | export default function sendMessage(message, data) {
4 | ext.runtime.sendMessage({ action: message, data }, () => {});
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | browser/node_modules
2 | browser/dist
3 | browser/dev
4 | browser/temp
5 | browser/.idea
6 | browser/.vscode
7 |
8 | site/node_modules
9 | site/.next
10 |
11 | api/.vscode
12 | api/tmp
13 | api/pgdata
14 | api/config.yaml
15 | api/logs
16 | api/assets
17 |
--------------------------------------------------------------------------------
/browser/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "search.exclude": {
3 | "**/.git": true,
4 | "**/node_modules": true,
5 | "**/bower_components": true,
6 | "**/temp": true,
7 | "**/dev": true,
8 | "**/dist": true
9 | }
10 | }
--------------------------------------------------------------------------------
/browser/src/background/util.js:
--------------------------------------------------------------------------------
1 | export default {
2 | timeout(ms, promise) {
3 | return new Promise((resolve, reject) => {
4 | setTimeout(() => {
5 | reject(new Error('timeout'));
6 | }, ms);
7 | promise.then(resolve, reject);
8 | });
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/site/interfaces/user.ts:
--------------------------------------------------------------------------------
1 | export default interface User {
2 | id: string;
3 | createdAt: string;
4 | updatedAt: string;
5 | firstname: string;
6 | lastname: string;
7 | email: string;
8 | stripe_customer_id: string;
9 | type: string;
10 | email_confirmed: boolean;
11 | }
12 |
--------------------------------------------------------------------------------
/site/pages/admin/index.tsx:
--------------------------------------------------------------------------------
1 | // import { LayoutAdminDash } from '../../components/Layout';
2 | // import AdminSidePanel from '../../components/AdminSidePanel';
3 | // import Link from 'next/link';
4 | // import { NextPage } from 'next';
5 |
6 | export default function Login() {
7 | return
;
8 | }
9 |
--------------------------------------------------------------------------------
/site/interfaces/settings.ts:
--------------------------------------------------------------------------------
1 | export default interface Settings {
2 | enableCsrf: string;
3 | enableSubscriptions: string;
4 | enableAuth: string;
5 | enableStripe: string;
6 | authCookieAge: number;
7 | authCookieName: string;
8 | authTokenExpiry: number;
9 | allowedOrigins: string[];
10 | }
11 |
--------------------------------------------------------------------------------
/site/interfaces/userplan.ts:
--------------------------------------------------------------------------------
1 | export default interface UserPlan {
2 | id: string;
3 | createdAt: string;
4 | updatedAt: string;
5 | plan_name: string;
6 | plan_type: string;
7 | user_id: number;
8 | plan_id: number;
9 | active: boolean;
10 | start_date: string;
11 | expiry_date: string;
12 | }
13 |
--------------------------------------------------------------------------------
/browser/src/popup/popup.jsx:
--------------------------------------------------------------------------------
1 | /* global document */
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import Popup from './containers/popup';
5 | import '../assets/css/index.css';
6 |
7 | const Index = () => ;
8 |
9 | ReactDOM.render(, document.getElementById('display-container'));
10 |
--------------------------------------------------------------------------------
/site/interfaces/server.ts:
--------------------------------------------------------------------------------
1 | export default interface Server {
2 | id: string;
3 | createdAt: string;
4 | updatedAt: string;
5 | country: string;
6 | country_code: number;
7 | type: string;
8 | ip: string;
9 | port: number;
10 | username: string;
11 | password: string;
12 | image_path: string;
13 | }
14 |
--------------------------------------------------------------------------------
/browser/webpack.tests.js:
--------------------------------------------------------------------------------
1 | import Enzyme from 'enzyme';
2 | import EnzymeAdapter from 'enzyme-adapter-react-16';
3 |
4 | Enzyme.configure({ adapter: new EnzymeAdapter() });
5 |
6 | const context = require.context('./src', true, /spec\.js/); // make sure you have your directory and regex test set correctly!
7 | context.keys().forEach(context);
8 |
--------------------------------------------------------------------------------
/browser/src/popup/components/largeAlert.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Alert from 'react-bootstrap/Alert';
3 |
4 | const LargeAlert = ({ variant, heading, body }) => (
5 |
6 | {heading}
7 | {body}
8 |
9 | );
10 |
11 | export default LargeAlert;
12 |
--------------------------------------------------------------------------------
/site/interfaces/plan.ts:
--------------------------------------------------------------------------------
1 | export default interface Plan {
2 | id: string;
3 | createdAt: string;
4 | updatedAt: string;
5 | name: string;
6 | amount: number;
7 | interval: string;
8 | interval_count: number;
9 | plan_type: string;
10 | currency: string;
11 | stripe_plan_id: string;
12 | stripe_product_id: string;
13 | }
14 |
--------------------------------------------------------------------------------
/site/pages/_app.js:
--------------------------------------------------------------------------------
1 | import 'bootstrap/dist/css/bootstrap.min.css';
2 | import 'react-datepicker/dist/react-datepicker.css';
3 | import '../static/css/index.css';
4 | import React, { useEffect } from 'react';
5 | import { initGA } from '../service/Analytics';
6 |
7 | export default function MyApp({ Component, pageProps }) {
8 | return ;
9 | }
10 |
--------------------------------------------------------------------------------
/api/models/base.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // BaseModel are commonly used fields for all models
8 | type BaseModel struct {
9 | ID uint `gorm:"AUTO_INCREMENT;primary_key;column:id;" json:"id"`
10 | CreatedAt time.Time `json:"createdAt"`
11 | UpdatedAt time.Time `json:"updatedAt"`
12 | DeletedAt *time.Time `sql:"index" json:"deletedAt,omitempty"`
13 | }
14 |
--------------------------------------------------------------------------------
/site/static/js/cookie-policy.js:
--------------------------------------------------------------------------------
1 | window.addEventListener('load', function() {
2 | window.wpcc.init({
3 | border: 'thin',
4 | corners: 'small',
5 | colors: {
6 | popup: { background: '#222222', text: '#ffffff', border: '#f9f9f9' },
7 | button: { background: '#f9f9f9', text: '#000000' }
8 | },
9 | content: { href: 'https://eirevpn.ie/policies/cookie-policy' }
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/browser/src/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "appName": {
3 | "message": "ÉireVPN",
4 | "description": "The name of the react extension."
5 | },
6 | "appDescription": {
7 | "message": "ÉireVPN is an irish based VPN solution",
8 | "description": "The description of the extension."
9 | },
10 | "btnTooltip": {
11 | "message": "ÉireVPN",
12 | "description": "Tooltip for the button."
13 | }
14 | }
--------------------------------------------------------------------------------
/browser/src/popup/components/container.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Container from 'react-bootstrap/Container';
3 | import PropTypes from 'prop-types';
4 |
5 | const PopupContainer = ({ children }) => (
6 |
7 | {children}
8 |
9 | );
10 |
11 | PopupContainer.propTypes = {
12 | children: PropTypes.any.isRequired
13 | };
14 |
15 | export default PopupContainer;
16 |
--------------------------------------------------------------------------------
/site/next.config.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | dev: {
3 | env: {
4 | apiDomain: 'http://localhost:3001',
5 | GA_KEY: 'UA-158748602-1'
6 | }
7 | },
8 | qa: {
9 | env: {
10 | apiDomain: 'http://api.qa.eirevpn.ie',
11 | GA_KEY: 'UA-158748602-1'
12 | }
13 | },
14 | prod: {
15 | env: {
16 | apiDomain: 'https://api.eirevpn.ie',
17 | GA_KEY: 'UA-158748602-1'
18 | }
19 | }
20 | };
21 | module.exports = config[process.env.NODE_ENV || 'dev'];
22 |
--------------------------------------------------------------------------------
/api/util/random/random.go:
--------------------------------------------------------------------------------
1 | package random
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/base64"
6 | )
7 |
8 | func GenerateRandomBytes(n int) ([]byte, error) {
9 | b := make([]byte, n)
10 | _, err := rand.Read(b)
11 | // Note that err == nil only if we read len(b) bytes.
12 | if err != nil {
13 | return nil, err
14 | }
15 |
16 | return b, nil
17 | }
18 |
19 | func GenerateRandomString(s int) (string, error) {
20 | b, err := GenerateRandomBytes(s)
21 | return base64.URLEncoding.EncodeToString(b), err
22 | }
23 |
--------------------------------------------------------------------------------
/site/components/ErrorMessage.tsx:
--------------------------------------------------------------------------------
1 | import Alert from 'react-bootstrap/Alert';
2 | import APIError from '../interfaces/error';
3 |
4 | interface EMProps {
5 | error: APIError | undefined;
6 | show: boolean;
7 | }
8 |
9 | const ErrorMessage: React.FC = ({ error, show }): JSX.Element => {
10 | if (!show) {
11 | return ;
12 | }
13 | return (
14 |
15 | {error?.detail}
16 |
17 | );
18 | };
19 |
20 | export default ErrorMessage;
21 |
--------------------------------------------------------------------------------
/browser/src/popup/services/authService.js:
--------------------------------------------------------------------------------
1 | import AbortController from 'node-abort-controller';
2 | import storage from '../../utils/storage';
3 | import API from './apiService';
4 |
5 | const controller = new AbortController();
6 |
7 | export default {
8 | async logout() {
9 | controller.abort();
10 |
11 | await API.Logout();
12 |
13 | await storage.set({ csrfToken: '' });
14 | },
15 |
16 | async isLoggedIn() {
17 | const csrfToken = await storage.get('csrfToken');
18 | return !!csrfToken;
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/site/components/CrudToolbar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ButtonMain from './ButtonMain';
3 | import Button from 'react-bootstrap/Button';
4 |
5 | interface CrudToolbarProps {
6 | HandleCreate: () => void;
7 | }
8 |
9 | const CrudToolbar: React.FC = ({ HandleCreate }): JSX.Element => {
10 | const handleCreate = () => {
11 | HandleCreate();
12 | };
13 | return (
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default CrudToolbar;
21 |
--------------------------------------------------------------------------------
/api/models/models.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | database "eirevpn/api/db"
5 |
6 | "github.com/jinzhu/gorm"
7 | )
8 |
9 | var db_connection *gorm.DB
10 |
11 | func db() *gorm.DB {
12 | if db_connection == nil {
13 | db_connection = database.GetDB()
14 | }
15 | return db_connection
16 | }
17 |
18 | // Get returns all models
19 | func Get() []interface{} {
20 | return []interface{}{
21 | &Plan{},
22 | &User{},
23 | &UserPlan{},
24 | &UserAppSession{},
25 | &Cart{},
26 | &Server{},
27 | &EmailToken{},
28 | &ForgotPassword{},
29 | &Connection{},
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/proxy/go.mod:
--------------------------------------------------------------------------------
1 | module eirevpn/proxy
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
7 | github.com/elazarl/goproxy v0.0.0-20190711103511-473e67f1d7d2
8 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2
9 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
10 | gopkg.in/yaml.v2 v2.2.7
11 | )
12 |
13 | replace github.com/elazarl/goproxy => github.com/dylankilkenny/goproxy v0.0.0-20200109204127-1c107847a855
14 |
15 | replace github.com/elazarl/goproxy/ext => github.com/dylankilkenny/goproxy/ext v0.0.0-20200109204127-1c107847a855
16 |
--------------------------------------------------------------------------------
/site/components/SuccessMessage.tsx:
--------------------------------------------------------------------------------
1 | import Alert from 'react-bootstrap/Alert';
2 | import APIError from '../interfaces/error';
3 |
4 | interface SMProps {
5 | show: boolean;
6 | message: string;
7 | className?: string;
8 | }
9 |
10 | const SuccessMessage: React.FC = ({ show, message, className }): JSX.Element => {
11 | if (!className) {
12 | className = '';
13 | }
14 | if (!show) {
15 | return ;
16 | }
17 | return (
18 |
19 | {message}
20 |
21 | );
22 | };
23 |
24 | export default SuccessMessage;
25 |
--------------------------------------------------------------------------------
/browser/src/popup/containers/loading.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Container from 'react-bootstrap/Container';
3 | import Row from 'react-bootstrap/Row';
4 | import Col from 'react-bootstrap/Col';
5 | import Spinner from 'react-bootstrap/Spinner';
6 |
7 | const Loading = () => (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 |
21 | export default Loading;
22 |
--------------------------------------------------------------------------------
/api/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | cfg "eirevpn/api/config"
5 | "eirevpn/api/integrations"
6 | "eirevpn/api/logger"
7 | "eirevpn/api/models"
8 | "eirevpn/api/router"
9 | "os"
10 | "path/filepath"
11 |
12 | "eirevpn/api/db"
13 | )
14 |
15 | func main() {
16 | debugMode := false
17 | logging := true
18 |
19 | appPath, _ := os.Getwd()
20 | filename, _ := filepath.Abs(appPath + "/config.yaml")
21 | cfg.Init(filename)
22 | conf := cfg.Load()
23 |
24 | integrations.Init()
25 |
26 | db.Init(conf, debugMode, models.Get())
27 |
28 | logger.Init(logging)
29 |
30 | r := router.Init(logging)
31 |
32 | r.Run(":" + conf.App.Port)
33 | }
34 |
--------------------------------------------------------------------------------
/site/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "downlevelIteration": true
21 | },
22 | "exclude": [
23 | "node_modules"
24 | ],
25 | "include": [
26 | "next-env.d.ts",
27 | "**/*.ts",
28 | "**/*.tsx"
29 | ]
30 | }
--------------------------------------------------------------------------------
/site/pages/account/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { LayoutUserDash } from '../../components/Layout';
3 | import UserDashboard from '../../components/UserDashboard';
4 | import { useRouter } from 'next/router';
5 | import Cookies from 'js-cookie';
6 |
7 | export default function Account(): JSX.Element {
8 | const router = useRouter();
9 | let userid = Cookies.get('uid')!;
10 | useEffect(() => {
11 | if (!userid) {
12 | router.push('/login');
13 | }
14 | });
15 |
16 | return (
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | Account.getInitialProps = async () => {
24 | return {};
25 | };
26 |
--------------------------------------------------------------------------------
/browser/src/popup/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | <%= htmlWebpackPlugin.options.title %>
7 |
8 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/site/components/ButtonMain.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from 'react-bootstrap/Button';
3 |
4 | interface ButtonMainProps {
5 | value: string;
6 | type?: 'submit';
7 | className?: string;
8 | onClick?: () => void;
9 | }
10 |
11 | type Event = React.ChangeEvent;
12 |
13 | const ButtonMain: React.FC = ({
14 | value,
15 | className,
16 | type,
17 | onClick
18 | }): JSX.Element => {
19 | return (
20 |
28 | );
29 | };
30 |
31 | export default ButtonMain;
32 |
--------------------------------------------------------------------------------
/site/service/Analytics.ts:
--------------------------------------------------------------------------------
1 | import ReactGA from 'react-ga';
2 |
3 | declare global {
4 | interface Window {
5 | GA_INITIALIZED: boolean;
6 | wpcc: () => void;
7 | }
8 | }
9 |
10 | export const initGA = () => {
11 | ReactGA.initialize(process.env.GA_KEY ? process.env.GA_KEY : '');
12 | };
13 |
14 | export const logPageView = () => {
15 | ReactGA.set({ page: window.location.pathname });
16 | ReactGA.pageview(window.location.pathname);
17 | };
18 |
19 | export const logEvent = (category = '', action = '') => {
20 | if (category && action) {
21 | ReactGA.event({ category, action });
22 | }
23 | };
24 |
25 | export const logException = (description = '', fatal = false) => {
26 | if (description) {
27 | ReactGA.exception({ description, fatal });
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/site/pages/admin/login.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { LayoutLogin } from '../../components/Layout';
3 | import LoginForm from '../../components/admin/forms/LoginForm';
4 | import Router from 'next/router';
5 | import API from '../../service/APIService';
6 |
7 | export default function LoginPage(): JSX.Element {
8 | const [error, setError] = useState();
9 |
10 | async function HandleLogin(body: string) {
11 | const res = await API.Login(body);
12 | if (res.status == 200) {
13 | Router.push('/admin/users');
14 | } else {
15 | setError(res);
16 | }
17 | }
18 |
19 | return (
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/site/pages/signup.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { LayoutLogin } from '../components/Layout';
3 | import SignupForm from '../components/admin/forms/SignupForm';
4 | import Router from 'next/router';
5 | import API from '../service/APIService';
6 |
7 | export default function SignupPage(): JSX.Element {
8 | const [error, setError] = useState();
9 |
10 | async function HandleSignup(body: string) {
11 | const res = await API.Signup(body);
12 | if (res.status == 200) {
13 | Router.push('/downloads?signedup=1');
14 | } else {
15 | setError(res);
16 | }
17 | }
18 |
19 | return (
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/browser/src/utils/storage.js:
--------------------------------------------------------------------------------
1 | /* global browser, window, chrome */
2 | import 'regenerator-runtime/runtime';
3 | import ext from './ext';
4 |
5 | export default {
6 | async set(obj) {
7 | await ext.storage.local.set(obj, () => {});
8 | },
9 | async get(key) {
10 | try {
11 | if (browser['storage']) {
12 | const resp = await ext.storage.local.get(key);
13 | return resp[key];
14 | }
15 | } catch (error) {
16 | console.log(error);
17 | }
18 | try {
19 | if (chrome['storage']) {
20 | const val = await new Promise(resolve => {
21 | chrome.storage.local.get([key], resp => {
22 | resolve(resp[key]);
23 | });
24 | });
25 | return val;
26 | }
27 | } catch (error) {
28 | console.log(error);
29 | }
30 | return '';
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/api/config.test.yaml:
--------------------------------------------------------------------------------
1 | App:
2 | Port: '3001'
3 | Domain: localhost
4 | JWTSecret: secretkey1995
5 | AllowedOrigins:
6 | - http://localhost:3001/
7 | EnableCSRF: true
8 | EnableSubscriptions: false
9 | EnableAuth: true
10 | AuthCookieAge: 168
11 | RefreshCookieAge: 168
12 | AuthCookieName: authToken
13 | RefreshCookieName: refreshToken
14 | AuthTokenExpiry: 1
15 | RefreshTokenExpiry: 48
16 |
17 | DB:
18 | User: eirevpn_test
19 | Password: eirevpn_test
20 | Database: eirevpn_test
21 | Host: localhost
22 | Port: 5431
23 |
24 | Stripe:
25 | SecretKey: sk_test_kLGFCqgqvp8m4xItjb7tCutQ00aVWpUjWt
26 | EndpointSecret: whsec_NiHocSXUplUAkCIk2R4uai3gMXHYILPs
27 | IntegrationActive: false
28 | SuccessUrl: https://eirevpn.ie/success
29 | ErrorUrl: https://eirevpn.ie/error
30 |
31 | SendGrid:
32 | APIKey: key
33 | IntegrationActive: false
34 | Templates:
35 | Registration: id
36 |
--------------------------------------------------------------------------------
/site/components/Pagination.tsx:
--------------------------------------------------------------------------------
1 | import Pagination from 'react-bootstrap/Pagination';
2 | import { useState } from 'react';
3 |
4 | interface PaginationProps {
5 | count: number;
6 | pageLimit: number;
7 | handlePagination: (page_number: number) => void;
8 | }
9 |
10 | const Pages: React.FC = ({ count, handlePagination, pageLimit }): JSX.Element => {
11 | const [activePage, setActivePage] = useState(1);
12 | const handleClick = (page: number) => {
13 | setActivePage(page);
14 | handlePagination(page);
15 | };
16 | const num_pages = Math.ceil(count / pageLimit);
17 | let items = [];
18 | for (let i = 1; i <= num_pages; i++) {
19 | items.push(
20 | handleClick(i)} key={i} active={i === activePage}>
21 | {i}
22 |
23 | );
24 | }
25 | return {items};
26 | };
27 |
28 | export default Pages;
29 |
--------------------------------------------------------------------------------
/site/components/FormCheck.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import Col from 'react-bootstrap/Col';
4 | import DatePicker from 'react-datepicker';
5 | import dayjs from 'dayjs';
6 |
7 | interface FormDatetimeProps {
8 | name: string;
9 | label?: string;
10 | labelEl?: JSX.Element;
11 | feedback: string;
12 | className?: string;
13 | }
14 |
15 | const FormGroup: React.FC = ({
16 | name,
17 | label,
18 | labelEl,
19 | feedback,
20 | className
21 | }): JSX.Element => {
22 | if (!!className) className = '';
23 | return (
24 |
25 |
33 |
34 | );
35 | };
36 |
37 | export default FormGroup;
38 |
--------------------------------------------------------------------------------
/site/service/Auth.ts:
--------------------------------------------------------------------------------
1 | import Router from 'next/router';
2 | import API from './APIService';
3 | import AbortController from 'node-abort-controller';
4 |
5 | const controller = new AbortController();
6 | const ADMIN_LOGIN = '/admin/login';
7 | const USER_LOGIN = '/login';
8 |
9 | const redirectLoginURL = (): string => {
10 | if (Router.pathname.includes('admin')) return ADMIN_LOGIN;
11 | else return USER_LOGIN;
12 | };
13 |
14 | const ClearAndRedirect = () => {
15 | controller.abort();
16 | localStorage.clear();
17 | Router.push(redirectLoginURL());
18 | };
19 |
20 | const IsLoggedIn = () => {
21 | let csrfToken = localStorage.getItem('X-CSRF-Token');
22 | if (csrfToken !== null) {
23 | return true;
24 | }
25 | return false;
26 | };
27 |
28 | const Logout = () => {
29 | const res = API.Logout();
30 | ClearAndRedirect();
31 | };
32 |
33 | export default {
34 | ClearAndRedirect,
35 | Logout,
36 | IsLoggedIn
37 | };
38 |
--------------------------------------------------------------------------------
/site/pages/login.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { LayoutLogin } from '../components/Layout';
3 | import LoginForm from '../components/admin/forms/LoginForm';
4 | import { useRouter } from 'next/router';
5 | import API from '../service/APIService';
6 |
7 | export default function LoginPage(): JSX.Element {
8 | const router = useRouter();
9 | const signedup = router.query.signedup;
10 | const [error, setError] = useState();
11 |
12 | async function HandleLogin(body: string) {
13 | const res = await API.Login(body);
14 | if (res.status == 200) {
15 | router.push('/account');
16 | } else {
17 | setError(res);
18 | }
19 | }
20 |
21 | return (
22 |
23 |
24 |
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/site/pages/admin/userplans/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AdminSidePanel from '../../../components/admin/AdminSidePanel';
3 | import ErrorMessage from '../../../components/ErrorMessage';
4 | import { LayoutAdminDash } from '../../../components/Layout';
5 | import UserPlansTable from '../../../components/admin/tables/UserPlansTable';
6 | import useAsync from '../../../hooks/useAsync';
7 | import API from '../../../service/APIService';
8 | import CrudToolbar from '../../../components/CrudToolbar';
9 | import Router from 'next/router';
10 |
11 | export default function Users(): JSX.Element {
12 | const { data, loading, error } = useAsync(API.GetUserPlansList);
13 | const hasError = !!error;
14 |
15 | if (loading) {
16 | return ;
17 | }
18 |
19 | return (
20 | }>
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/site/pages/admin/users/create.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useRouter } from 'next/router';
3 | import { LayoutAdminDash } from '../../../components/Layout';
4 | import AdminSidePanel from '../../../components/admin/AdminSidePanel';
5 | import API from '../../../service/APIService';
6 | import UserCreateForm from '../../../components/admin/forms/UserCreateForm';
7 |
8 | export default function UserCreate(): JSX.Element {
9 | const router = useRouter();
10 | const [error, setError] = useState();
11 |
12 | async function HandleSave(body: string) {
13 | const res = await API.CreateUser(body);
14 | if (res.status == 200) {
15 | router.push('/admin/users');
16 | } else {
17 | setError(res);
18 | }
19 | }
20 |
21 | return (
22 | }>
23 |
24 |
25 | );
26 | }
27 |
28 | UserCreate.getInitialProps = async () => {
29 | return {};
30 | };
31 |
--------------------------------------------------------------------------------
/proxy/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 |
7 | "gopkg.in/yaml.v2"
8 | )
9 |
10 | type Config struct {
11 | App struct {
12 | ProxyPort string `yaml:"ProxyPort"`
13 | RestPort string `yaml:"RestPort"`
14 | ProxyUsername string `yaml:"ProxyUsername"`
15 | ProxyPassword string `yaml:"ProxyPassword"`
16 | } `yaml:"App"`
17 | }
18 |
19 | var configFilename string
20 |
21 | func Init(filename string) {
22 | configFilename = filename
23 | }
24 |
25 | func Load() Config {
26 | conf := Config{}
27 | yamlFile, err := ioutil.ReadFile(configFilename)
28 | if err != nil {
29 | fmt.Println(err)
30 | }
31 | err = yaml.Unmarshal(yamlFile, &conf)
32 | if err != nil {
33 | fmt.Println(err)
34 | }
35 | return conf
36 | }
37 |
38 | func (c *Config) SaveConfig() error {
39 | newConf, err := yaml.Marshal(&c)
40 | if err != nil {
41 | return err
42 | }
43 | err = ioutil.WriteFile("config.yaml", newConf, 0644)
44 | if err != nil {
45 | return err
46 | }
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/site/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "site",
3 | "version": "1.0.2",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "@types/js-cookie": "^2.2.4",
16 | "@types/react-datepicker": "^2.11.0",
17 | "bootstrap": "^4.4.1",
18 | "dayjs": "^1.8.19",
19 | "isomorphic-unfetch": "^3.0.0",
20 | "js-cookie": "^2.2.1",
21 | "next": "^9.1.8-canary.15",
22 | "node-abort-controller": "^1.0.4",
23 | "react": "^16.12.0",
24 | "react-bootstrap": "^1.0.0-beta.16",
25 | "react-datepicker": "^2.11.0",
26 | "react-dom": "^16.12.0",
27 | "react-ga": "^2.7.0",
28 | "react-icons": "^3.9.0",
29 | "swr": "^0.1.16",
30 | "use-http": "^0.2.4"
31 | },
32 | "devDependencies": {
33 | "@types/node": "^13.1.6",
34 | "@types/react": "^16.9.17",
35 | "typescript": "^3.7.4"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/site/pages/admin/servers/create.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useRouter } from 'next/router';
3 | import { LayoutAdminDash } from '../../../components/Layout';
4 | import AdminSidePanel from '../../../components/admin/AdminSidePanel';
5 | import API from '../../../service/APIService';
6 | import ServerCreateForm from '../../../components/admin/forms/ServerCreateForm';
7 |
8 | export default function ServerCreate(): JSX.Element {
9 | const router = useRouter();
10 | const [error, setError] = useState();
11 |
12 | async function HandleSave(body: FormData) {
13 | const res = await API.CreateServer(body);
14 | if (res.status == 200) {
15 | router.push('/admin/servers');
16 | } else {
17 | setError(res);
18 | }
19 | }
20 |
21 | return (
22 | }>
23 |
24 |
25 | );
26 | }
27 |
28 | ServerCreate.getInitialProps = async () => {
29 | return {};
30 | };
31 |
--------------------------------------------------------------------------------
/browser/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "globals": {
5 | "fetch": false
6 | },
7 | "env": {
8 | "node": true,
9 | "es6": true,
10 | "browser": true
11 | },
12 | "rules": {
13 | "no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"],
14 | "react/forbid-prop-types": 0,
15 | "react/prop-types": 0,
16 | "camelcase": 0,
17 | "react/require-default-props": 0,
18 | "comma-dangle": 0,
19 | "function-paren-newline": 0,
20 | "arrow-parens": 0,
21 | "space-before-function-paren": 0,
22 | "object-curly-newline": 0,
23 | "dot-notation": 0,
24 | "import/no-named-as-default": 0,
25 | "import/no-named-as-default-member": 0,
26 | "jsx-a11y/click-events-have-key-events": 0,
27 | "jsx-a11y/no-static-element-interactions": 0,
28 | "quotes": 0,
29 | "react/no-did-mount-set-state": 0,
30 | "no-console": 0
31 | }
32 | }
--------------------------------------------------------------------------------
/site/pages/admin/plans/create.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useRouter } from 'next/router';
3 | import { LayoutAdminDash } from '../../../components/Layout';
4 | import AdminSidePanel from '../../../components/admin/AdminSidePanel';
5 | import API from '../../../service/APIService';
6 | import PlanCreateForm from '../../../components/admin/forms/PlanCreateForm';
7 |
8 | export default function PlanCreate(): JSX.Element {
9 | const router = useRouter();
10 | const [error, setError] = useState();
11 |
12 | async function HandleSave(body: string) {
13 | console.log(body);
14 | const res = await API.CreatePlan(body);
15 | if (res.status == 200) {
16 | router.push('/admin/plans');
17 | } else {
18 | setError(res);
19 | }
20 | }
21 |
22 | return (
23 | }>
24 |
25 |
26 | );
27 | }
28 |
29 | PlanCreate.getInitialProps = async () => {
30 | return {};
31 | };
32 |
--------------------------------------------------------------------------------
/site/components/FormDatetime.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import Col from 'react-bootstrap/Col';
4 | import DatePicker from 'react-datepicker';
5 | import dayjs from 'dayjs';
6 |
7 | interface FormDatetimeProps {
8 | name: string;
9 | label: string;
10 | value: string;
11 | onChange: (value: React.SetStateAction) => void;
12 | }
13 |
14 | const FormGroup: React.FC = ({ name, label, value, onChange }): JSX.Element => {
15 | return (
16 |
17 | {label}
18 |
19 | onChange(dayjs(date).toString())}
23 | showTimeSelect
24 | timeFormat="HH:mm"
25 | timeIntervals={15}
26 | timeCaption="time"
27 | dateFormat="MMMM d, yyyy h:mm aa"
28 | />
29 |
30 |
31 | );
32 | };
33 |
34 | export default FormGroup;
35 |
--------------------------------------------------------------------------------
/api/models/cart.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/jinzhu/gorm"
7 | )
8 |
9 | // Cart contains the details of which plans each user is trying to purchase
10 | type Cart struct {
11 | BaseModel
12 | UserID uint `json:"user_id"`
13 | PlanID uint `json:"plan_id"`
14 | }
15 |
16 | func (c *Cart) Find() error {
17 | if err := db().Where(&c).First(&c).Error; err != nil {
18 | return err
19 | }
20 | return nil
21 | }
22 |
23 | func (c *Cart) Save() error {
24 | if err := db().Save(&c).Error; err != nil {
25 | return err
26 | }
27 | return nil
28 | }
29 |
30 | func (c *Cart) Delete() error {
31 | if err := db().Delete(&c).Error; err != nil {
32 | return err
33 | }
34 | return nil
35 | }
36 |
37 | // BeforeCreate sets the CreatedAt column to the current time
38 | func (c *Cart) BeforeCreate(scope *gorm.Scope) error {
39 | scope.SetColumn("CreatedAt", time.Now())
40 |
41 | return nil
42 | }
43 |
44 | // BeforeUpdate sets the UpdatedAt column to the current time
45 | func (c *Cart) BeforeUpdate(scope *gorm.Scope) error {
46 | scope.SetColumn("UpdatedAt", time.Now())
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/site/pages/admin/plans/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { LayoutAdminDash } from '../../../components/Layout';
3 | import AdminSidePanel from '../../../components/admin/AdminSidePanel';
4 | import PlansTable from '../../../components/admin/tables/PlansTable';
5 | import ErrorMessage from '../../../components/ErrorMessage';
6 | import API from '../../../service/APIService';
7 | import useAsync from '../../../hooks/useAsync';
8 | import CrudToolbar from '../../../components/CrudToolbar';
9 | import Router from 'next/router';
10 |
11 | export default function Plans(): JSX.Element {
12 | const { data, loading, error } = useAsync(API.GetPlansList);
13 | const hasError = !!error;
14 |
15 | const HandleCreate = () => {
16 | Router.push('/admin/plans/create');
17 | };
18 |
19 | if (loading) {
20 | return ;
21 | }
22 |
23 | return (
24 | }>
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/browser/src/popup/containers/incognito.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Container from 'react-bootstrap/Container';
3 | import Row from 'react-bootstrap/Row';
4 | import Col from 'react-bootstrap/Col';
5 | import Alert from 'react-bootstrap/Alert';
6 | import Image from 'react-bootstrap/Image';
7 |
8 | const Incognito = () => (
9 |
10 |
11 | Permission to run in private windows is required by Firefox.
12 |
13 |
14 |
15 | 1. Right click on the extensions icon and click manage
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 2. Scroll down to Run in Private Windows and choose Allow.
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 |
34 | export default Incognito;
35 |
--------------------------------------------------------------------------------
/site/pages/admin/servers/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { LayoutAdminDash } from '../../../components/Layout';
3 | import AdminSidePanel from '../../../components/admin/AdminSidePanel';
4 | import ServersTable from '../../../components/admin/tables/ServersTable';
5 | import ErrorMessage from '../../../components/ErrorMessage';
6 | import CrudToolbar from '../../../components/CrudToolbar';
7 | import API from '../../../service/APIService';
8 | import useAsync from '../../../hooks/useAsync';
9 | import Router from 'next/router';
10 |
11 | export default function Servers(): JSX.Element {
12 | const { data, loading, error } = useAsync(API.GetServersList);
13 | const hasError = !!error;
14 |
15 | const HandleCreate = () => {
16 | Router.push('/admin/servers/create');
17 | };
18 |
19 | if (loading) {
20 | return ;
21 | }
22 |
23 | return (
24 | }>
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/browser/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ÉireVPN Beta - Free VPN & Ad Blocker",
3 | "author": "ÉireVPN Team",
4 | "version": "0.1.7",
5 | "manifest_version": 2,
6 | "description": "ÉireVPN is an irish based VPN solution with ad blocking technology baked right in. Why use 2 extensions when you can have one?",
7 | "icons": {
8 | "48": "assets/icons/logo-shield-48.png",
9 | "128": "assets/icons/logo-shield-128.png"
10 | },
11 | "default_locale": "en",
12 | "background": {
13 | "scripts": [
14 | "background/background.js",
15 | "hotreload/hotreload.js"
16 | ]
17 | },
18 | "permissions": [
19 | "tabs",
20 | "webRequest",
21 | "webRequestBlocking",
22 | "proxy",
23 | "storage",
24 | "http://api.eirevpn.ie/",
25 | "https://api.eirevpn.ie/",
26 | "http://localhost/"
27 | ],
28 | "browser_action": {
29 | "default_icon": {
30 | "48": "assets/icons/logo-shield-48.png"
31 | },
32 | "default_title": "ÉireVPN Beta - Free VPN & Ad Blocker",
33 | "default_popup": "popup/index.html"
34 | }
35 | }
--------------------------------------------------------------------------------
/api/go.mod:
--------------------------------------------------------------------------------
1 | module eirevpn/api
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/caarlos0/env v3.5.0+incompatible
7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
8 | github.com/gin-contrib/cors v1.3.0
9 | github.com/gin-gonic/gin v1.4.0
10 | github.com/gofrs/uuid v3.2.0+incompatible
11 | github.com/jinzhu/configor v1.1.1
12 | github.com/jinzhu/gorm v1.9.10
13 | github.com/joho/godotenv v1.3.0
14 | github.com/lib/pq v1.2.0
15 | github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b
16 | github.com/satori/go.uuid v1.2.0
17 | github.com/sendgrid/rest v2.4.1+incompatible
18 | github.com/sendgrid/sendgrid-go v3.5.0+incompatible
19 | github.com/sirupsen/logrus v1.4.3-0.20190701143506-07a84ee7412e
20 | github.com/stretchr/testify v1.3.0
21 | github.com/stripe/stripe-go v62.8.2+incompatible
22 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
23 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect
24 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e // indirect
25 | golang.org/x/text v0.3.2 // indirect
26 | golang.org/x/tools v0.0.0-20190728063539-fc6e2057e7f6 // indirect
27 | golang.org/x/tools/gopls v0.1.3 // indirect
28 | gopkg.in/yaml.v2 v2.2.2
29 | )
30 |
--------------------------------------------------------------------------------
/browser/src/manifest-ff.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ÉireVPN Beta - Free VPN & Ad Blocker",
3 | "author": "ÉireVPN Team",
4 | "version": "0.1.7",
5 | "manifest_version": 2,
6 | "description": "ÉireVPN is an irish based VPN solution with ad blocking technology baked right in. Why use 2 extensions when you can have one?",
7 | "icons": {
8 | "48": "assets/icons/logo-shield-48.png",
9 | "128": "assets/icons/logo-shield-128.png"
10 | },
11 | "browser_specific_settings": {
12 | "gecko": {
13 | "id": "extensions@eirevpn.ie"
14 | }
15 | },
16 | "default_locale": "en",
17 | "background": {
18 | "scripts": [
19 | "background/background.js",
20 | "hotreload/hotreload.js"
21 | ]
22 | },
23 | "permissions": [
24 | "tabs",
25 | "storage",
26 | "webRequest",
27 | "webRequestBlocking",
28 | "proxy",
29 | "http://api.eirevpn.ie/",
30 | "https://api.eirevpn.ie/",
31 | "http://localhost/"
32 |
33 | ],
34 | "browser_action": {
35 | "default_icon": {
36 | "48": "assets/icons/logo-shield-48.png"
37 | },
38 | "default_title": "ÉireVPN Beta - Free VPN & Ad Blocker",
39 | "default_popup": "popup/index.html"
40 | }
41 | }
--------------------------------------------------------------------------------
/api/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "eirevpn/api/config"
5 | "fmt"
6 | "log"
7 |
8 | "github.com/jinzhu/gorm"
9 | _ "github.com/jinzhu/gorm/dialects/postgres"
10 | _ "github.com/lib/pq"
11 | )
12 |
13 | var db *gorm.DB
14 | var err error
15 |
16 | // Init creates a connection to postgres database and
17 | // migrates any new models
18 | func Init(config config.Config, debug bool, models []interface{}) {
19 |
20 | dbinfo := fmt.Sprintf("user=%s password=%s host=%s port=%v dbname=%s sslmode=disable",
21 | config.DB.User,
22 | config.DB.Password,
23 | config.DB.Host,
24 | config.DB.Port,
25 | config.DB.Database,
26 | )
27 |
28 | fmt.Println(dbinfo)
29 |
30 | db, err = gorm.Open("postgres", dbinfo)
31 | db.LogMode(debug)
32 |
33 | if err != nil {
34 | log.Println("Failed to connect to database")
35 | panic(err.Error())
36 | }
37 | log.Println("Database connected")
38 |
39 | for _, model := range models {
40 | if !db.HasTable(model) {
41 | err := db.CreateTable(model).Error
42 | if err != nil {
43 | log.Fatal(err)
44 | }
45 | log.Println("Table Created")
46 | }
47 | db.AutoMigrate(model)
48 | }
49 | }
50 |
51 | //GetDB ...
52 | func GetDB() *gorm.DB {
53 | return db
54 | }
55 |
56 | func CloseDB() {
57 | db.Close()
58 | }
59 |
--------------------------------------------------------------------------------
/site/components/UserDashboard.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { IconContext } from 'react-icons';
3 | import { IoMdPerson } from 'react-icons/io';
4 | import Container from 'react-bootstrap/Container';
5 | import Row from 'react-bootstrap/Row';
6 | import Col from 'react-bootstrap/Col';
7 | import UserDetailsCard from './UserDetailsCard';
8 | import SubscriptionCard from './SubscriptionCard';
9 |
10 | interface UserDashboardProps {
11 | userid: string;
12 | }
13 |
14 | const UserDashboard: React.FC = ({ userid }) => {
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
My Account
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | export default UserDashboard;
41 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # EireVPN
2 |
3 |
4 | This repo contains an implementation of a browser based VPN service. Included in this implementation:
5 | - VPN browser extension
6 | - VPN proxy server
7 | - Website
8 | - User account management
9 | - Admin panels
10 | - Stripe integration
11 | - Send grid integration
12 |
13 | Technology stack:
14 | - React (site and browser extension)
15 | - Go (backend, api, proxy server)
16 | - NodeJs
17 | - PostgresSql
18 |
19 |
20 | ## Screenshots
21 | ### Landing Page
22 | 
23 | ### Create an account
24 | 
25 | ### User account page
26 | 
27 | ### Change details
28 | 
29 | ### Downloads
30 | 
31 | ### Contact Support
32 | 
33 | ### Admin panel
34 | 
35 | ### Admin panel
36 | 
37 | ### Extension
38 | 
39 | 
40 | 
41 | 
42 |
--------------------------------------------------------------------------------
/site/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { LayoutMain } from '../components/Layout';
2 | import Image from 'react-bootstrap/Image';
3 | import Container from 'react-bootstrap/Container';
4 | import Row from 'react-bootstrap/Row';
5 | import Col from 'react-bootstrap/Col';
6 | import Button from 'react-bootstrap/Button';
7 | import Link from 'next/link';
8 |
9 | export default function LandingPage() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | High speed Irish VPN and Ad Blocker.
17 |
18 | Safely secure your browsing and enjoy unrestricted access worldwide.
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/site/components/admin/tables/ConnectionsTable.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Table from 'react-bootstrap/Table';
3 | import Connection from '../../../interfaces/connection';
4 | import dayjs from 'dayjs';
5 | import Router from 'next/router';
6 |
7 | interface ConnTableProps {
8 | connections: Connection[];
9 | show: boolean;
10 | }
11 |
12 | const ConnectionsTable: React.FC = ({ connections, show }) => {
13 | if (!show) {
14 | return ;
15 | }
16 | return (
17 |
18 |
19 |
20 | | # |
21 | Created |
22 | User ID |
23 | Server ID |
24 | Country |
25 |
26 |
27 |
28 | {connections.map(c => (
29 |
30 | | {c.id} |
31 |
32 | {dayjs(c.createdAt)
33 | .format('DD-MM-YYYY H:mm')
34 | .toString()}
35 | |
36 | {c.user_id} |
37 | {c.server_id} |
38 | {c.server_country} |
39 |
40 | ))}
41 |
42 |
43 | );
44 | };
45 |
46 | export default ConnectionsTable;
47 |
--------------------------------------------------------------------------------
/api/handlers/message/message.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import (
4 | "eirevpn/api/errors"
5 | "eirevpn/api/integrations/sendgrid"
6 | "eirevpn/api/logger"
7 | "net/http"
8 |
9 | "github.com/gin-gonic/gin"
10 | )
11 |
12 | type MessageFields struct {
13 | Email string `json:"email" binding:"required"`
14 | Subject string `json:"subject" binding:"required"`
15 | Message string `json:"message" binding:"required"`
16 | }
17 |
18 | // Message rel
19 | func Message(c *gin.Context) {
20 |
21 | mf := MessageFields{}
22 | if err := c.BindJSON(&mf); err != nil {
23 | logger.Log(logger.Fields{
24 | Loc: "/message - Message()",
25 | Code: errors.MsgBindingFailed.Code,
26 | Err: err.Error(),
27 | })
28 | c.AbortWithStatusJSON(errors.InternalServerError.Status, errors.InternalServerError)
29 | return
30 | }
31 |
32 | if err := sendgrid.Send().SupportRequest(mf.Email, mf.Subject, mf.Message); err != nil {
33 | logger.Log(logger.Fields{
34 | Loc: "/message - Message()",
35 | Code: errors.InternalServerError.Code,
36 | Extra: map[string]interface{}{"Email": mf.Email, "Detail": "Error sending support email"},
37 | Err: err.Error(),
38 | })
39 | c.AbortWithStatusJSON(errors.InternalServerError.Status, errors.InternalServerError)
40 | return
41 | }
42 |
43 | c.JSON(http.StatusOK, gin.H{
44 | "status": 200,
45 | })
46 | }
47 |
--------------------------------------------------------------------------------
/browser/src/utils/hot-reload.js:
--------------------------------------------------------------------------------
1 | /* global chrome */
2 |
3 | const filesInDirectory = dir => new Promise(resolve =>
4 | dir.createReader().readEntries(entries =>
5 | Promise.all(entries.filter(e => e.name[0] !== '.').map(e => (
6 | e.isDirectory
7 | ? filesInDirectory(e)
8 | : new Promise(resolvePromise => e.file(resolvePromise)))))
9 | .then(files => [].concat(...files))
10 | .then(resolve)));
11 |
12 | const timestampForFilesInDirectory = dir =>
13 | filesInDirectory(dir).then(files =>
14 | files.map(f => f.name + f.lastModifiedDate).join());
15 |
16 | const reload = () => {
17 | chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
18 | if (tabs[0]) {
19 | chrome.tabs.reload(tabs[0].id);
20 | }
21 |
22 | chrome.runtime.reload();
23 | });
24 | };
25 |
26 | const watchChanges = (dir, lastTimestamp) => {
27 | timestampForFilesInDirectory(dir).then((timestamp) => {
28 | if (!lastTimestamp || (lastTimestamp === timestamp)) {
29 | setTimeout(() => watchChanges(dir, timestamp), 1000);
30 | } else {
31 | reload();
32 | }
33 | });
34 | };
35 |
36 | if (chrome) {
37 | chrome.management.getSelf((self) => {
38 | if (self.installType === 'development') {
39 | chrome.runtime.getPackageDirectoryEntry(dir => watchChanges(dir));
40 | }
41 | });
42 | }
43 |
--------------------------------------------------------------------------------
/api/config.example.yaml:
--------------------------------------------------------------------------------
1 | App:
2 | Port: '3001'
3 | Domain: eirevpn.ie
4 | JWTSecret: secretkey1995
5 | AllowedOrigins:
6 | - http://localhost:3000
7 | - http://localhost:3001
8 | - https://eirevpn.ie
9 | - https://www.eirevpn.ie
10 | - https://api.eirevpn.ie
11 | - chrome-extension://pplkeenpmmifelccendefanihacjnpnb
12 | - chrome-extension://fmhlhjegfkgemampnpomkajhdpjnhmie
13 | - moz-extension://extensions@eirevpn.ie
14 | EnableCSRF: true
15 | EnableSubscriptions: false
16 | EnableAuth: true
17 | AuthCookieAge: 168
18 | RefreshCookieAge: 168
19 | AuthCookieName: authToken
20 | RefreshCookieName: refreshToken
21 | AuthTokenExpiry: 1
22 | RefreshTokenExpiry: 48
23 | TestMode: true
24 | DB:
25 | User: eirevpn_prod
26 | Password: eirevpn_prod
27 | Database: eirevpn_prod
28 | Host: localhost
29 | Port: 5432s
30 | Stripe:
31 | SecretKey: sk_test_sssssssssss
32 | EndpointSecret: whsec_ssssssssss
33 | IntegrationActive: false
34 | SuccessUrl: https://eirevpn.ie/success
35 | ErrorUrl: https://eirevpn.ie/error
36 | SendGrid:
37 | APIKey: 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
38 | IntegrationActive: true
39 | Templates:
40 | Registration: d-e2a3e60211f4430ab68a36ac7191475f
41 | SupportRequest: d-c459bbe9dcfe44c1a5967f7b1cb01f8c
42 | ForgotPassword: d-48668daa6afa4e3c842b9d2bb5406fef
43 |
--------------------------------------------------------------------------------
/site/components/admin/AdminSidePanel.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 |
4 | export default function AdminSidePanel(): JSX.Element {
5 | return (
6 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/site/pages/admin/connections.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { LayoutAdminDash } from '../../components/Layout';
3 | import AdminSidePanel from '../../components/admin/AdminSidePanel';
4 | import ConnectionsTable from '../../components/admin/tables/ConnectionsTable';
5 | import ErrorMessage from '../../components/ErrorMessage';
6 | import Pagination from '../../components/Pagination';
7 | import CrudToolbar from '../../components/CrudToolbar';
8 | import API from '../../service/APIService';
9 | import useAsync from '../../hooks/useAsync';
10 | import Router from 'next/router';
11 |
12 | export default function Connections(): JSX.Element {
13 | const [offset, setOffset] = useState(0);
14 | const { data, loading, error } = useAsync(() => API.GetConnectionsList(offset), [offset]);
15 | const hasError = !!error;
16 | const pageLimit = 20;
17 |
18 | const handlePagination = (page_number: number) => {
19 | setOffset((page_number - 1) * pageLimit);
20 | };
21 |
22 | if (loading) {
23 | return ;
24 | }
25 |
26 | return (
27 | }>
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/site/hooks/useAsync.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import AbortController from 'node-abort-controller';
3 | import Error from '../interfaces/error';
4 |
5 | type PostFunc = (body: string) => Promise;
6 | type GetFunc = () => Promise;
7 | type MakeRequestFunc = GetFunc & PostFunc;
8 |
9 | const fetchError: Error = {
10 | status: 0,
11 | code: 'ERR',
12 | title: 'Something Went Wrong',
13 | detail: 'Something Went Wrong'
14 | };
15 |
16 | export default function useAsync(makeRequestFunc: MakeRequestFunc, dependency?: T[]) {
17 | const controller = new AbortController();
18 | const [data, setData] = useState();
19 | const [error, setError] = useState();
20 | const [loading, setloading] = useState(true);
21 | async function asyncCall() {
22 | try {
23 | const res = await makeRequestFunc();
24 | if (res.status == 200) {
25 | setData(res.data);
26 | } else {
27 | controller.abort();
28 | setError(res);
29 | }
30 | } catch (e) {
31 | console.log(e);
32 | setError(fetchError);
33 | controller.abort();
34 | } finally {
35 | setloading(false);
36 | }
37 | }
38 | if (dependency) {
39 | useEffect(() => {
40 | asyncCall();
41 | }, [...dependency]);
42 | } else {
43 | useEffect(() => {
44 | asyncCall();
45 | }, []);
46 | }
47 |
48 | return { data, loading, error };
49 | }
50 |
--------------------------------------------------------------------------------
/api/models/forgotpass.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/satori/go.uuid"
7 | "github.com/jinzhu/gorm"
8 | )
9 |
10 | // ForgotPassword contains the password access token with a one to one mapping
11 | // to the user
12 | type ForgotPassword struct {
13 | BaseModel
14 | UserID uint
15 | Token string `json:"token"`
16 | }
17 |
18 | func (fp *ForgotPassword) Find() error {
19 | if err := db().Where(&fp).First(&fp).Error; err != nil {
20 | return err
21 | }
22 | return nil
23 | }
24 |
25 | func (fp *ForgotPassword) Create() error {
26 | if err := db().Create(&fp).Error; err != nil {
27 | return err
28 | }
29 | return nil
30 | }
31 |
32 | func (fp *ForgotPassword) Save() error {
33 | if err := db().Save(&fp).Error; err != nil {
34 | return err
35 | }
36 | return nil
37 | }
38 |
39 | func (fp *ForgotPassword) Delete() error {
40 | if err := db().Delete(&fp).Error; err != nil {
41 | return err
42 | }
43 | return nil
44 | }
45 |
46 | // BeforeCreate sets the CreatedAt column to the current time
47 | func (fp *ForgotPassword) BeforeCreate(scope *gorm.Scope) error {
48 | scope.SetColumn("CreatedAt", time.Now())
49 | token := uuid.NewV4()
50 | scope.SetColumn("Token", token.String())
51 | return nil
52 | }
53 |
54 | // BeforeUpdate sets the UpdatedAt column to the current time
55 | func (fp *ForgotPassword) BeforeUpdate(scope *gorm.Scope) error {
56 | scope.SetColumn("UpdatedAt", time.Now())
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/site/components/admin/tables/UsersTable.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Table from 'react-bootstrap/Table';
3 | import User from '../../../interfaces/user';
4 | import dayjs from 'dayjs';
5 | import Router from 'next/router';
6 |
7 | interface UsersTableProps {
8 | users: User[];
9 | show: boolean;
10 | }
11 |
12 | const UsersTable: React.FC = ({ users, show }) => {
13 | if (!show) {
14 | return ;
15 | }
16 | return (
17 |
18 |
19 |
20 | | # |
21 | Created |
22 | First Name |
23 | Last Name |
24 | Email |
25 | Stripe ID |
26 |
27 |
28 |
29 | {users?.map(user => (
30 | Router.push('/admin/users/[id]', '/admin/users/' + user.id)}
33 | >
34 | | {user.id} |
35 |
36 | {dayjs(user.createdAt)
37 | .format('DD-MM-YYYY H:mm')
38 | .toString()}
39 | |
40 | {user.firstname} |
41 | {user.lastname} |
42 | {user.email} |
43 | {user.stripe_customer_id} |
44 |
45 | ))}
46 |
47 |
48 | );
49 | };
50 |
51 | export default UsersTable;
52 |
--------------------------------------------------------------------------------
/site/pages/admin/users/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import AdminSidePanel from '../../../components/admin/AdminSidePanel';
3 | import ErrorMessage from '../../../components/ErrorMessage';
4 | import { LayoutAdminDash } from '../../../components/Layout';
5 | import UsersTable from '../../../components/admin/tables/UsersTable';
6 | import useAsync from '../../../hooks/useAsync';
7 | import API from '../../../service/APIService';
8 | import CrudToolbar from '../../../components/CrudToolbar';
9 | import Pagination from '../../../components/Pagination';
10 | import Router from 'next/router';
11 |
12 | export default function Users(): JSX.Element {
13 | const [offset, setOffset] = useState(0);
14 | const { data, loading, error } = useAsync(() => API.GetUsersList(offset), [offset]);
15 | const hasError = !!error;
16 | const pageLimit = 20;
17 |
18 | const HandleCreate = () => {
19 | Router.push('/admin/users/create');
20 | };
21 |
22 | const handlePagination = (page_number: number) => {
23 | setOffset((page_number - 1) * pageLimit);
24 | };
25 |
26 | if (loading) {
27 | return ;
28 | }
29 |
30 | return (
31 | }>
32 |
33 |
34 |
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/site/pages/confirm_email.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { LayoutMain } from '../components/Layout';
3 | import { useRouter } from 'next/router';
4 | import API from '../service/APIService';
5 | import ErrorMessage from '../components/ErrorMessage';
6 | import SuccessMessage from '../components/SuccessMessage';
7 | import useAsync from '../hooks/useAsync';
8 | import Row from 'react-bootstrap/Row';
9 | import Col from 'react-bootstrap/Col';
10 | import Container from 'react-bootstrap/Container';
11 | import Card from 'react-bootstrap/Card';
12 |
13 | export default function ConfirmEmailPage(): JSX.Element {
14 | const router = useRouter();
15 | const token = router.query.token;
16 | const { data, loading, error } = useAsync(() => API.ConfirmEmail(token.toString()));
17 |
18 | useEffect(() => {
19 | if (!token) {
20 | router.push('/');
21 | }
22 | });
23 |
24 | if (loading) {
25 | return ;
26 | }
27 |
28 | return (
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 | }
45 |
46 | ConfirmEmailPage.getInitialProps = async () => {
47 | return {};
48 | };
49 |
--------------------------------------------------------------------------------
/site/pages/forgot_pass/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { LayoutMain } from '../../components/Layout';
3 | import { useRouter } from 'next/router';
4 | import API from '../../service/APIService';
5 | import ErrorMessage from '../../components/ErrorMessage';
6 | import SuccessMessage from '../../components/SuccessMessage';
7 | import ForgotPasswordForm from '../../components/user/forms/ForgotPasswordForm';
8 | import useAsync from '../../hooks/useAsync';
9 | import Row from 'react-bootstrap/Row';
10 | import Col from 'react-bootstrap/Col';
11 | import Container from 'react-bootstrap/Container';
12 |
13 | export default function ForgotPassword(): JSX.Element {
14 | const [respError, setRespError] = useState();
15 | const [success, setSuccess] = useState();
16 |
17 | async function HandleSubmit(body: string) {
18 | const res = await API.ForgotPasswordEmail(body);
19 | if (res.status == 200) {
20 | setSuccess(true);
21 | setRespError(false);
22 | } else {
23 | setRespError(res);
24 | setSuccess(false);
25 | }
26 | }
27 |
28 | return (
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | ForgotPassword.getInitialProps = async () => {
44 | return {};
45 | };
46 |
--------------------------------------------------------------------------------
/site/pages/admin/settings.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { LayoutAdminDash } from '../../components/Layout';
3 | import AdminSidePanel from '../../components/admin/AdminSidePanel';
4 | import ErrorMessage from '../../components/ErrorMessage';
5 | import API from '../../service/APIService';
6 | import useAsync from '../../hooks/useAsync';
7 | import SettingsForm from '../../components/admin/forms/SettingsForm';
8 |
9 | export default function SettingsPage(): JSX.Element {
10 | const { data, loading, error } = useAsync(() => API.GetSettings());
11 | const [respError, setRespError] = useState();
12 | const [success, setSuccess] = useState();
13 | const hasError = !!error;
14 |
15 | async function HandleSave(body: string) {
16 | const res = await API.UpdateSettings(body);
17 | if (res.status == 200) {
18 | setSuccess(true);
19 | setRespError(false);
20 | } else {
21 | setRespError(res);
22 | setSuccess(false);
23 | }
24 | }
25 |
26 | if (loading) {
27 | return ;
28 | }
29 |
30 | return (
31 | }>
32 | {!error ? (
33 |
39 | ) : (
40 |
41 | )}
42 |
43 | );
44 | }
45 |
46 | SettingsPage.getInitialProps = async () => {
47 | return {};
48 | };
49 |
--------------------------------------------------------------------------------
/site/pages/forgot_pass/[token].tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { LayoutMain } from '../../components/Layout';
3 | import { useRouter } from 'next/router';
4 | import API from '../../service/APIService';
5 | import Row from 'react-bootstrap/Row';
6 | import Col from 'react-bootstrap/Col';
7 | import Container from 'react-bootstrap/Container';
8 | import UpdatePasswordForm from '../../components/user/forms/UpdatePasswordForm';
9 |
10 | export default function UpdatePassword(): JSX.Element {
11 | const router = useRouter();
12 | const token = router.query.token.toString();
13 | const [respError, setRespError] = useState();
14 | const [success, setSuccess] = useState();
15 |
16 | async function HandleSubmit(body: string) {
17 | const res = await API.UpdatePassword(body, token);
18 | if (res.status == 200) {
19 | setSuccess(true);
20 | setRespError(false);
21 | } else {
22 | setRespError(res);
23 | setSuccess(false);
24 | }
25 | }
26 |
27 | useEffect(() => {
28 | if (!token) {
29 | router.push('/');
30 | }
31 | });
32 |
33 | return (
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 |
48 | UpdatePassword.getInitialProps = async () => {
49 | return {};
50 | };
51 |
--------------------------------------------------------------------------------
/site/pages/admin/userplans/create.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useRouter } from 'next/router';
3 | import { LayoutAdminDash } from '../../../components/Layout';
4 | import AdminSidePanel from '../../../components/admin/AdminSidePanel';
5 | import API from '../../../service/APIService';
6 | import UserPlanCreateForm from '../../../components/admin/forms/UserPlanCreateForm';
7 | import useAsync from '../../../hooks/useAsync';
8 | import ErrorMessage from '../../../components/ErrorMessage';
9 |
10 | export default function UserPlanCreate(): JSX.Element {
11 | const router = useRouter();
12 | const userid = router.query.userid;
13 | const { data, loading, error } = useAsync(() => API.GetPlansList());
14 | const [respError, setRespError] = useState();
15 |
16 | async function HandleSave(body: string) {
17 | const res = await API.CreateUserPlan(body);
18 | if (res.status == 200) {
19 | router.push('/admin/userplans');
20 | } else {
21 | setRespError(res);
22 | }
23 | }
24 |
25 | if (loading) {
26 | return ;
27 | }
28 | console.log(data);
29 | return (
30 | }>
31 | {!error ? (
32 |
38 | ) : (
39 |
40 | )}
41 |
42 | );
43 | }
44 |
45 | UserPlanCreate.getInitialProps = async () => {
46 | return {};
47 | };
48 |
--------------------------------------------------------------------------------
/browser/src/popup/containers/settings.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Container from 'react-bootstrap/Container';
3 | import Row from 'react-bootstrap/Row';
4 | import Col from 'react-bootstrap/Col';
5 | import PropTypes from 'prop-types';
6 | import { IoIosMail, IoIosLogOut } from 'react-icons/io';
7 | import { IconContext } from 'react-icons';
8 | import ext from '../../utils/ext';
9 |
10 | const Settings = ({ logout }) => {
11 | const forgotPass = () => {
12 | const newURL = `${process.env.DOMAIN}/contact`;
13 | ext.tabs.create({ url: newURL });
14 | };
15 | return (
16 |
17 |
18 |
19 |
20 |
21 | Logout
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
forgotPass()} className="create-account-link">
34 |
35 |
36 |
37 | Contact Support
38 |
39 |
40 |
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | Settings.propTypes = {
48 | logout: PropTypes.func.isRequired
49 | };
50 |
51 | export default Settings;
52 |
--------------------------------------------------------------------------------
/api/models/emailtoken.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/satori/go.uuid"
7 | "github.com/jinzhu/gorm"
8 | )
9 |
10 | // EmailConfirm contains the email confirmation tokens with a one to one mapping
11 | // to the user
12 | type EmailToken struct {
13 | BaseModel
14 | UserID uint
15 | Token string `json:"token"`
16 | }
17 |
18 | func (et *EmailToken) Find() error {
19 | if err := db().Where(&et).First(&et).Error; err != nil {
20 | return err
21 | }
22 | return nil
23 | }
24 |
25 | func (et *EmailToken) GetUser() (*User, error) {
26 | var user User
27 | if err := db().Model(&user).Related(&et).Error; err != nil {
28 | return nil, err
29 | }
30 | return &user, nil
31 | }
32 |
33 | func (et *EmailToken) Create() error {
34 | if err := db().Create(&et).Error; err != nil {
35 | return err
36 | }
37 | return nil
38 | }
39 |
40 | func (et *EmailToken) Save() error {
41 | if err := db().Save(&et).Error; err != nil {
42 | return err
43 | }
44 | return nil
45 | }
46 |
47 | func (et *EmailToken) Delete() error {
48 | if err := db().Delete(&et).Error; err != nil {
49 | return err
50 | }
51 | return nil
52 | }
53 |
54 | // BeforeCreate sets the CreatedAt column to the current time
55 | func (et *EmailToken) BeforeCreate(scope *gorm.Scope) error {
56 | scope.SetColumn("CreatedAt", time.Now())
57 | token := uuid.NewV4()
58 | scope.SetColumn("Token", token.String())
59 | return nil
60 | }
61 |
62 | // BeforeUpdate sets the UpdatedAt column to the current time
63 | func (et *EmailToken) BeforeUpdate(scope *gorm.Scope) error {
64 | scope.SetColumn("UpdatedAt", time.Now())
65 | return nil
66 | }
67 |
--------------------------------------------------------------------------------
/browser/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function (config) {
2 | config.set({
3 | browsers: ['Chrome'], // run in Chrome
4 | singleRun: true, // just run once by default
5 | frameworks: ['mocha'], // use the mocha test framework
6 | files: [
7 | { pattern: 'webpack.tests.js', watched: false },
8 | ],
9 | preprocessors: {
10 | 'webpack.tests.js': ['webpack', 'sourcemap'],
11 | },
12 | reporters: ['dots'], // report results in this format
13 | webpack: { // kind of a copy of your webpack config
14 | devtool: 'inline-source-map', // just do inline source maps instead of the default
15 | mode: 'development',
16 | module: {
17 | rules: [
18 | {
19 | loader: 'babel-loader',
20 | exclude: /node_modules/,
21 | test: /\.(js|jsx)$/,
22 | query: {
23 | presets: ['@babel/preset-env', '@babel/preset-react'],
24 | },
25 | resolve: {
26 | extensions: ['.js', '.jsx'],
27 | },
28 | },
29 | {
30 | test: /\.scss$/,
31 | use: [
32 | {
33 | loader: 'style-loader',
34 | },
35 | {
36 | loader: 'css-loader',
37 | },
38 | {
39 | loader: 'sass-loader',
40 | },
41 | ],
42 | },
43 | ],
44 | },
45 | },
46 | plugins: [
47 | require("karma-mocha"),
48 | require("karma-webpack"),
49 | require("karma-sourcemap-loader"),
50 | require("karma-chrome-launcher"),
51 | ]
52 | });
53 | };
--------------------------------------------------------------------------------
/site/components/admin/tables/PlansTable.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Table from 'react-bootstrap/Table';
3 | import Plan from '../../../interfaces/plan';
4 | import dayjs from 'dayjs';
5 | import Router from 'next/router';
6 |
7 | interface PlansTableProps {
8 | plans: Plan[];
9 | show: boolean;
10 | }
11 |
12 | const UsersTable: React.FC = ({ plans, show }) => {
13 | if (!show) {
14 | return ;
15 | }
16 | return (
17 |
18 |
19 |
20 | | # |
21 | Created |
22 | Name |
23 | Cost |
24 | Interval |
25 | Interval Count |
26 | Stripe Plan ID |
27 | Stripe Product ID |
28 |
29 |
30 |
31 | {plans.map(plan => (
32 | Router.push('/admin/plans/[id]', '/admin/plans/' + plan.id)}
35 | >
36 | | {plan.id} |
37 |
38 | {dayjs(plan.createdAt)
39 | .format('DD-MM-YYYY H:mm')
40 | .toString()}
41 | |
42 | {plan.name} |
43 | {plan.amount} |
44 | {plan.interval} |
45 | {plan.interval_count} |
46 | {plan.stripe_plan_id} |
47 | {plan.stripe_product_id} |
48 |
49 | ))}
50 |
51 |
52 | );
53 | };
54 |
55 | export default UsersTable;
56 |
--------------------------------------------------------------------------------
/site/components/FormDropdown.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import Col from 'react-bootstrap/Col';
4 |
5 | interface FormDropdownProps {
6 | name: string;
7 | label: string;
8 | value: string;
9 | className?: string;
10 | options?: string[];
11 | optionsKV?: { value: string; name: string }[];
12 | onChange?: (value: React.SetStateAction) => void;
13 | }
14 |
15 | type Event = React.ChangeEvent;
16 |
17 | const FormGroup: React.FC = ({
18 | name,
19 | label,
20 | value,
21 | options,
22 | optionsKV,
23 | className,
24 | onChange
25 | }): JSX.Element => {
26 | let DropdownOptions;
27 | if (options) {
28 | options.unshift('...');
29 | DropdownOptions = options.map((val, i) => (
30 |
33 | ));
34 | } else if (optionsKV) {
35 | optionsKV.unshift({ value: '', name: '...' });
36 | DropdownOptions = optionsKV.map((val, i) => (
37 |
40 | ));
41 | } else {
42 | DropdownOptions = ;
43 | }
44 | return (
45 |
46 | {label}
47 | {
53 | if (onChange) {
54 | onChange(e.target.value);
55 | }
56 | }}
57 | >
58 | {DropdownOptions}
59 |
60 |
61 | );
62 | };
63 |
64 | export default FormGroup;
65 |
--------------------------------------------------------------------------------
/browser/src/utils/ext.js:
--------------------------------------------------------------------------------
1 | /* global browser, window, chrome */
2 |
3 | const apis = [
4 | 'alarms',
5 | 'bookmarks',
6 | 'browserAction',
7 | 'commands',
8 | 'contextMenus',
9 | 'cookies',
10 | 'downloads',
11 | 'getBrowserInfo',
12 | 'events',
13 | 'extension',
14 | 'extensionTypes',
15 | 'history',
16 | 'i18n',
17 | 'idle',
18 | 'notifications',
19 | 'pageAction',
20 | 'proxy',
21 | 'runtime',
22 | 'storage',
23 | 'tabs',
24 | 'webNavigation',
25 | 'webRequest',
26 | 'windows'
27 | ];
28 |
29 | function Extension() {
30 | const self = this;
31 |
32 | apis.forEach(api => {
33 | self[api] = null;
34 |
35 | try {
36 | if (chrome[api]) {
37 | self[api] = chrome[api];
38 | }
39 | } catch (e) {
40 | return;
41 | }
42 |
43 | try {
44 | if (window[api]) {
45 | self[api] = window[api];
46 | }
47 | } catch (e) {
48 | return;
49 | }
50 |
51 | try {
52 | if (browser[api]) {
53 | self[api] = browser[api];
54 | }
55 | } catch (e) {
56 | return;
57 | }
58 |
59 | try {
60 | self.api = browser.extension[api];
61 | } catch (e) {
62 | // I want application to not crush, but don't care about the message
63 | }
64 | });
65 |
66 | try {
67 | if (browser && browser.runtime) {
68 | this.runtime = browser.runtime;
69 | }
70 | } catch (e) {
71 | return;
72 | }
73 |
74 | try {
75 | if (browser && browser.browserAction) {
76 | this.browserAction = browser.browserAction;
77 | }
78 | } catch (e) {
79 | // I want application to not crush, but don't care about the message
80 | }
81 | }
82 |
83 | module.exports = new Extension();
84 |
--------------------------------------------------------------------------------
/site/components/admin/tables/ServersTable.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Table from 'react-bootstrap/Table';
3 | import Server from '../../../interfaces/server';
4 | import dayjs from 'dayjs';
5 | import Router from 'next/router';
6 |
7 | interface ServersTableProps {
8 | servers: Server[];
9 | show: boolean;
10 | }
11 |
12 | const ServersTable: React.FC = ({ servers, show }) => {
13 | if (!show) {
14 | return ;
15 | }
16 | return (
17 |
18 |
19 |
20 | | # |
21 | Created |
22 | Country |
23 | Code |
24 | Type |
25 | IP |
26 | Port |
27 | Username |
28 | Password |
29 |
30 |
31 |
32 | {servers.map(server => (
33 | Router.push('/admin/servers/[id]', '/admin/servers/' + server.id)}
36 | >
37 | | {server.id} |
38 |
39 | {dayjs(server.createdAt)
40 | .format('DD-MM-YYYY H:mm')
41 | .toString()}
42 | |
43 | {server.country} |
44 | {server.country_code} |
45 | {server.type} |
46 | {server.ip} |
47 | {server.port} |
48 | {server.username} |
49 | {server.password} |
50 |
51 | ))}
52 |
53 |
54 | );
55 | };
56 |
57 | export default ServersTable;
58 |
--------------------------------------------------------------------------------
/proxy/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | c "eirevpn/proxy/config"
5 | "encoding/json"
6 | "fmt"
7 | "log"
8 | "net/http"
9 | "os"
10 | "path/filepath"
11 |
12 | "github.com/elazarl/goproxy"
13 | "github.com/elazarl/goproxy/ext/auth"
14 | )
15 |
16 | type credentials struct {
17 | Username string `json:"username"`
18 | Password string `json:"password"`
19 | }
20 |
21 | func main() {
22 | appPath, _ := os.Getwd()
23 | filename, _ := filepath.Abs(appPath + "/config.yaml")
24 | c.Init(filename)
25 | go startProxy()
26 | startAPI()
27 | }
28 |
29 | func startAPI() {
30 | http.HandleFunc("/update_creds", func(w http.ResponseWriter, r *http.Request) {
31 | d := json.NewDecoder(r.Body)
32 | cred := &credentials{}
33 | err := d.Decode(cred)
34 | if err != nil {
35 | fmt.Println(err)
36 | return
37 | }
38 | config := c.Load()
39 | config.App.ProxyUsername = cred.Username
40 | config.App.ProxyPassword = cred.Password
41 | if err = config.SaveConfig(); err != nil {
42 | fmt.Println("Error saving config: ", err)
43 | }
44 | fmt.Println("Updated Configuration: ", config)
45 | })
46 | config := c.Load()
47 | fmt.Println("REST API Started")
48 | http.ListenAndServe(":"+config.App.RestPort, nil)
49 | }
50 |
51 | func startProxy() {
52 | proxy := goproxy.NewProxyHttpServer()
53 | proxy.Verbose = true
54 | auth.ProxyBasic(proxy, "Auth", func(user, passwd string) bool {
55 | if user == c.Load().App.ProxyUsername && passwd == c.Load().App.ProxyPassword {
56 | fmt.Println("Authenticated, allowing connection.")
57 | return true
58 | }
59 | fmt.Printf("Wrong Credentials: %s:%s \n", user, passwd)
60 | return false
61 | })
62 | fmt.Println("Proxy Started")
63 | log.Fatal(http.ListenAndServe(":"+c.Load().App.ProxyPort, proxy))
64 | }
65 |
--------------------------------------------------------------------------------
/api/models/user_app_session.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "eirevpn/api/util/random"
5 | "time"
6 |
7 | "github.com/jinzhu/gorm"
8 | )
9 |
10 | // UserAppSession contains the users session identifier token
11 | type UserAppSession struct {
12 | BaseModel
13 | UserID uint `json:"user_id"`
14 | Identifier string `json:"indentifier"`
15 | }
16 |
17 | func (us *UserAppSession) Find() error {
18 | if err := db().Where(&us).First(&us).Error; err != nil {
19 | return err
20 | }
21 | return nil
22 | }
23 |
24 | // New adds a new user session and deletes all old sessions
25 | func (us *UserAppSession) New(UserID uint) error {
26 | us.UserID = UserID
27 | if err := us.DeleteAll(); err != nil {
28 | return err
29 | }
30 | if err := us.Create(); err != nil {
31 | return err
32 | }
33 | return nil
34 | }
35 |
36 | // Create adds a new user sessions
37 | func (us *UserAppSession) Create() error {
38 | if err := db().Create(&us).Error; err != nil {
39 | return err
40 | }
41 | return nil
42 | }
43 |
44 | // DeleteAll removes any existing user sessions
45 | func (us *UserAppSession) DeleteAll() error {
46 | if err := db().Delete(UserAppSession{}, "user_id = ?", us.UserID).Error; err != nil {
47 | return err
48 | }
49 | return nil
50 | }
51 |
52 | // BeforeCreate sets the CreatedAt column to the current time
53 | func (us *UserAppSession) BeforeCreate(scope *gorm.Scope) error {
54 | scope.SetColumn("CreatedAt", time.Now())
55 | if identifier, err := random.GenerateRandomString(64); err == nil {
56 | scope.SetColumn("Identifier", identifier)
57 |
58 | }
59 | return nil
60 | }
61 |
62 | // BeforeUpdate sets the UpdatedAt column to the current time
63 | func (us *UserAppSession) BeforeUpdate(scope *gorm.Scope) error {
64 | scope.SetColumn("UpdatedAt", "check")
65 | return nil
66 | }
67 |
--------------------------------------------------------------------------------
/site/components/FormInput.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import Col from 'react-bootstrap/Col';
4 |
5 | type Event = React.ChangeEvent;
6 | type KeyEvent = React.KeyboardEvent;
7 |
8 | interface FormGroupProps {
9 | name: string;
10 | label: string;
11 | value: string;
12 | textarea?: boolean;
13 | required?: boolean;
14 | type?: string;
15 | className?: string;
16 | textOnly?: boolean;
17 | isInvalid?: boolean;
18 | feebackType?: 'valid' | 'invalid';
19 | feebackValue?: string;
20 | onKeyPress?: (target: KeyEvent) => void;
21 | onChange?: (value: React.SetStateAction) => void;
22 | }
23 |
24 | const FormGroup: React.FC = ({
25 | name,
26 | label,
27 | value,
28 | textarea,
29 | required,
30 | type,
31 | className,
32 | textOnly,
33 | isInvalid,
34 | feebackType,
35 | feebackValue,
36 | onKeyPress,
37 | onChange
38 | }): JSX.Element => {
39 | const plainText = textOnly ? true : false;
40 |
41 | return (
42 |
43 | {label}
44 | {
57 | if (onChange) {
58 | onChange(e.target.value);
59 | }
60 | }}
61 | />
62 | {feebackValue}
63 |
64 | );
65 | };
66 |
67 | export default FormGroup;
68 |
--------------------------------------------------------------------------------
/site/components/admin/tables/UserPlansTable.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Table from 'react-bootstrap/Table';
3 | import UserPlan from '../../../interfaces/userplan';
4 | import dayjs from 'dayjs';
5 | import Router from 'next/router';
6 |
7 | interface UserPlansTableProps {
8 | userplans: UserPlan[];
9 | show: boolean;
10 | }
11 |
12 | const UsersTable: React.FC = ({ userplans, show }) => {
13 | if (!show) {
14 | return ;
15 | }
16 | return (
17 |
18 |
19 |
20 | | # |
21 | Created |
22 | User ID |
23 | Plan ID |
24 | Active |
25 | Start Date |
26 | End Date |
27 |
28 |
29 |
30 | {userplans.map(up => (
31 |
34 | Router.push('/admin/userplans/[user_id]', '/admin/userplans/' + up.user_id)
35 | }
36 | >
37 | | {up.id} |
38 |
39 | {dayjs(up.createdAt)
40 | .format('DD-MM-YYYY H:mm')
41 | .toString()}
42 | |
43 | {up.user_id} |
44 | {up.plan_id} |
45 | {up.active.toString()} |
46 |
47 | {dayjs(up.start_date)
48 | .format('DD-MM-YYYY H:mm')
49 | .toString()}
50 | |
51 |
52 | {dayjs(up.expiry_date)
53 | .format('DD-MM-YYYY H:mm')
54 | .toString()}
55 | |
56 |
57 | ))}
58 |
59 |
60 | );
61 | };
62 |
63 | export default UsersTable;
64 |
--------------------------------------------------------------------------------
/site/pages/contact.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { LayoutMain } from '../components/Layout';
3 | import Image from 'react-bootstrap/Image';
4 | import Container from 'react-bootstrap/Container';
5 | import Row from 'react-bootstrap/Row';
6 | import Col from 'react-bootstrap/Col';
7 | import ContactForm from '../components/user/forms/ContactForm';
8 | import API from '../service/APIService';
9 | import ErrorMessage from '../components/ErrorMessage';
10 | import SuccessMessage from '../components/SuccessMessage';
11 |
12 | export default function ContactPage(): JSX.Element {
13 | const [success, setSuccess] = useState();
14 | const [error, setError] = useState();
15 | async function HandleSend(body: string) {
16 | const res = await API.ContactSupport(body);
17 | if (res.status == 200) {
18 | setSuccess(true);
19 | setError(false);
20 | } else {
21 | setError(res);
22 | setSuccess(false);
23 | }
24 | }
25 | return (
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
37 |
Having an issue? Let us know.
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/site/components/user/forms/EditDetailsForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import User from '../../../interfaces/user';
4 | import ButtonMain from '../../ButtonMain';
5 | import Card from 'react-bootstrap/Card';
6 | import FormInput from '../../FormInput';
7 |
8 | interface UserEditFormProps {
9 | user: User;
10 | HandleSave: (body: string) => Promise;
11 | }
12 |
13 | const UserForm: React.FC = ({ user, HandleSave }) => {
14 | const [firstname, setFirstname] = useState(user.firstname);
15 | const [lastname, setLastname] = useState(user.lastname);
16 | const [email, setEmail] = useState(user.email);
17 |
18 | const handleSaveClick = () => {
19 | HandleSave(JSON.stringify({ firstname, lastname, email }));
20 | };
21 |
22 | return (
23 |
24 |
25 |
26 |
27 | Details
28 |
29 |
30 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
50 |
51 |
52 |
53 |
54 |
55 | );
56 | };
57 |
58 | export default UserForm;
59 |
--------------------------------------------------------------------------------
/site/pages/account/edit.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { LayoutUserDash } from '../../components/Layout';
3 | import EditDetailsDashboard from '../../components/user/EditDetailsDashboard';
4 | import API from '../../service/APIService';
5 | import useAsync from '../../hooks/useAsync';
6 | import { useRouter } from 'next/router';
7 | import Cookies from 'js-cookie';
8 |
9 | export default function PlansEdit(): JSX.Element {
10 | const router = useRouter();
11 | let userid = Cookies.get('uid')!;
12 | useEffect(() => {
13 | if (!userid) {
14 | router.push('/login');
15 | }
16 | });
17 | const { data, loading, error } = useAsync(() => API.GetUserByID(userid));
18 | const [respError, setRespError] = useState();
19 | const [success, setSuccess] = useState();
20 |
21 | async function HandleDetailsSave(body: string) {
22 | const res = await API.UpdateUser(userid, body);
23 | if (res.status == 200) {
24 | setSuccess(true);
25 | setRespError(false);
26 | } else {
27 | setRespError(res);
28 | setSuccess(false);
29 | }
30 | }
31 |
32 | async function HandlePasswordSave(body: string) {
33 | const res = await API.ChangePassword(body);
34 | if (res.status == 200) {
35 | setSuccess(true);
36 | setRespError(false);
37 | } else {
38 | setRespError(res);
39 | setSuccess(false);
40 | }
41 | }
42 |
43 | if (loading) {
44 | return ;
45 | }
46 |
47 | if (error) {
48 | console.log(error);
49 | }
50 |
51 | return (
52 |
53 |
60 |
61 | );
62 | }
63 |
64 | PlansEdit.getInitialProps = async () => {
65 | return {};
66 | };
67 |
--------------------------------------------------------------------------------
/site/pages/admin/plans/[id].tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { LayoutAdminDash } from '../../../components/Layout';
3 | import AdminSidePanel from '../../../components/admin/AdminSidePanel';
4 | import ErrorMessage from '../../../components/ErrorMessage';
5 | import API from '../../../service/APIService';
6 | import useAsync from '../../../hooks/useAsync';
7 | import PlanForm from '../../../components/admin/forms/PlanEditForm';
8 | import { useRouter } from 'next/router';
9 |
10 | export default function PlansEdit(): JSX.Element {
11 | const router = useRouter();
12 | const planID = router.query.id.toString();
13 | const { data, loading, error } = useAsync(() => API.GetPlanByID(planID));
14 | const [respError, setRespError] = useState();
15 | const [success, setSuccess] = useState();
16 | const hasError = !!error;
17 |
18 | async function HandleSave(body: string) {
19 | const res = await API.UpdatePlan(planID, body);
20 | if (res.status == 200) {
21 | setSuccess(true);
22 | setRespError(false);
23 | } else {
24 | setRespError(res);
25 | setSuccess(false);
26 | }
27 | }
28 |
29 | async function HandleDelete() {
30 | const res = await API.DeletePlan(planID);
31 | if (res.status == 200) {
32 | router.push('/admin/plans');
33 | } else {
34 | setRespError(res);
35 | }
36 | }
37 |
38 | if (loading) {
39 | return ;
40 | }
41 | return (
42 | }>
43 | {!error ? (
44 |
51 | ) : (
52 |
53 | )}
54 |
55 | );
56 | }
57 |
58 | PlansEdit.getInitialProps = async () => {
59 | return {};
60 | };
61 |
--------------------------------------------------------------------------------
/site/pages/admin/servers/[id].tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { LayoutAdminDash } from '../../../components/Layout';
3 | import AdminSidePanel from '../../../components/admin/AdminSidePanel';
4 | import ErrorMessage from '../../../components/ErrorMessage';
5 | import API from '../../../service/APIService';
6 | import useAsync from '../../../hooks/useAsync';
7 | import ServerEditForm from '../../../components/admin/forms/ServerEditForm';
8 | import { useRouter } from 'next/router';
9 |
10 | export default function ServerEdit(): JSX.Element {
11 | const router = useRouter();
12 | const serverID = router.query.id.toString();
13 | const { data, loading, error } = useAsync(() => API.GetServerByID(serverID));
14 | const [respError, setRespError] = useState();
15 | const [success, setSuccess] = useState();
16 | const hasError = !!error;
17 |
18 | async function HandleSave(body: string) {
19 | const res = await API.UpdateServer(serverID, body);
20 | if (res.status == 200) {
21 | setSuccess(true);
22 | setRespError(false);
23 | } else {
24 | setRespError(res);
25 | setSuccess(false);
26 | }
27 | }
28 |
29 | async function HandleDelete() {
30 | const res = await API.DeleteServer(serverID);
31 | if (res.status == 200) {
32 | router.push('/admin/servers');
33 | } else {
34 | setRespError(res);
35 | }
36 | }
37 |
38 | if (loading) {
39 | return ;
40 | }
41 |
42 | return (
43 | }>
44 | {!error ? (
45 |
52 | ) : (
53 |
54 | )}
55 |
56 | );
57 | }
58 |
59 | ServerEdit.getInitialProps = async () => {
60 | return {};
61 | };
62 |
--------------------------------------------------------------------------------
/api/models/userplan.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/jinzhu/gorm"
7 | )
8 |
9 | type AllUserPlans []UserPlan
10 |
11 | // UserPlan contains the details of which plans each user is signed up for
12 | type UserPlan struct {
13 | BaseModel
14 | UserID uint `json:"user_id" binding:"required"`
15 | PlanID uint `json:"plan_id" binding:"required"`
16 | Active bool `json:"active" binding:"required"`
17 | StartDate time.Time `json:"start_date" binding:"required"`
18 | ExpiryDate time.Time `json:"expiry_date" binding:"required"`
19 | }
20 |
21 | func (up *UserPlan) Find() error {
22 | if err := db().Where(&up).First(&up).Error; err != nil {
23 | return err
24 | }
25 | return nil
26 | }
27 |
28 | func (aup *AllUserPlans) FindAll() error {
29 | if err := db().Find(&aup).Error; err != nil {
30 | return err
31 | }
32 | return nil
33 | }
34 |
35 | func (up *UserPlan) Save() error {
36 | if err := db().Save(&up).Error; err != nil {
37 | return err
38 | }
39 | return nil
40 | }
41 |
42 | func (up *UserPlan) Create() error {
43 | if err := up.DeleteAll(); err != nil {
44 | return err
45 | }
46 | if err := db().Create(&up).Error; err != nil {
47 | return err
48 | }
49 | return nil
50 | }
51 |
52 | func (up *UserPlan) Delete() error {
53 | if err := db().Delete(&up).Error; err != nil {
54 | return err
55 | }
56 | return nil
57 | }
58 |
59 | // DeleteAll removes any existing user sessions
60 | func (up *UserPlan) DeleteAll() error {
61 | if err := db().Delete(UserPlan{}, "user_id = ?", up.UserID).Error; err != nil {
62 | return err
63 | }
64 | return nil
65 | }
66 |
67 | // BeforeCreate sets the CreatedAt column to the current time
68 | func (up *UserPlan) BeforeCreate(scope *gorm.Scope) error {
69 | scope.SetColumn("CreatedAt", time.Now())
70 |
71 | return nil
72 | }
73 |
74 | // BeforeUpdate sets the UpdatedAt column to the current time
75 | func (up *UserPlan) BeforeUpdate(scope *gorm.Scope) error {
76 | scope.SetColumn("UpdatedAt", "check")
77 | return nil
78 | }
79 |
--------------------------------------------------------------------------------
/site/pages/admin/userplans/[user_id].tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { LayoutAdminDash } from '../../../components/Layout';
3 | import AdminSidePanel from '../../../components/admin/AdminSidePanel';
4 | import ErrorMessage from '../../../components/ErrorMessage';
5 | import API from '../../../service/APIService';
6 | import useAsync from '../../../hooks/useAsync';
7 | import UserPlanEditForm from '../../../components/admin/forms/UserPlanEditForm';
8 | import { useRouter } from 'next/router';
9 |
10 | export default function UserPlanEdit(): JSX.Element {
11 | const router = useRouter();
12 | const userID = router.query.user_id.toString();
13 | const { data, loading, error } = useAsync(() => API.GetUserPlanByUserID(userID));
14 | const [respError, setRespError] = useState();
15 | const [success, setSuccess] = useState();
16 | const hasError = !!error;
17 |
18 | async function HandleSave(body: string) {
19 | console.log(body);
20 | const res = await API.UpdateUserPlan(userID, body);
21 | if (res.status == 200) {
22 | setSuccess(true);
23 | setRespError(false);
24 | } else {
25 | setRespError(res);
26 | setSuccess(false);
27 | }
28 | }
29 |
30 | async function HandleDelete() {
31 | const res = await API.DeleteUserPlan(userID);
32 | if (res.status == 200) {
33 | router.push('/admin/userplans');
34 | } else {
35 | setRespError(res);
36 | }
37 | }
38 |
39 | if (loading) {
40 | return ;
41 | }
42 |
43 | return (
44 | }>
45 | {!error ? (
46 |
53 | ) : (
54 |
55 | )}
56 |
57 | );
58 | }
59 |
60 | UserPlanEdit.getInitialProps = async () => {
61 | return {};
62 | };
63 |
--------------------------------------------------------------------------------
/api/logger/logger.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 | "time"
8 | // . "github.com/logrusorgru/aurora"
9 | )
10 |
11 | var loggingEnabled bool
12 |
13 | const LogFilePath = "./logs/default.log"
14 |
15 | type Fields struct {
16 | Code string
17 | Loc string
18 | Err string
19 | Extra map[string]interface{}
20 | }
21 |
22 | func Init(enabled bool) {
23 | loggingEnabled = enabled
24 | }
25 |
26 | func createLogFile() {
27 | // detect if file exists
28 | var _, err = os.Stat(LogFilePath)
29 | // create file if not exists
30 | if os.IsNotExist(err) {
31 | var file, err = os.Create(LogFilePath)
32 | if err != nil {
33 | fmt.Println(err.Error())
34 | }
35 | defer file.Close()
36 | }
37 | fmt.Println("==> created log file", LogFilePath)
38 | }
39 |
40 | func writeFile(msg string) {
41 | // open file using READ & WRITE permission
42 | var file, err = os.OpenFile(LogFilePath, os.O_APPEND|os.O_WRONLY, 0644)
43 | if err != nil {
44 | fmt.Println(err.Error())
45 | }
46 | // defer file.Close()
47 |
48 | // write to file
49 | _, err = file.WriteString(msg + "\n")
50 | if err != nil {
51 | fmt.Println(err.Error())
52 | }
53 | // save changes
54 | err = file.Sync()
55 | if err != nil {
56 | fmt.Println(err.Error())
57 | }
58 | }
59 |
60 | func Log(fields Fields) {
61 | if loggingEnabled {
62 | var msg strings.Builder
63 | msg.WriteString(fmt.Sprintf("%s %v | ", "ERROR", time.Now().Format("2006-01-02 15:04:05")))
64 | if fields.Err != "" {
65 | msg.WriteString(fmt.Sprintf("%s: %v | ", "ERROR", fields.Err))
66 | }
67 | if fields.Code != "" {
68 | msg.WriteString(fmt.Sprintf("%s: %v | ", "CODE", fields.Code))
69 | }
70 | for k, v := range fields.Extra {
71 | if k == "Err" {
72 | v = strings.Replace(v.(string), "\n", " ", -1)
73 | }
74 | field := fmt.Sprintf("%s: %v | ", k, v)
75 | msg.WriteString(field)
76 | }
77 | if fields.Loc != "" {
78 | msg.WriteString(fmt.Sprintf("%s: %v", "LOC", fields.Loc))
79 | }
80 | fmt.Println(msg.String())
81 | writeFile(msg.String())
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/browser/src/popup/services/apiService.js:
--------------------------------------------------------------------------------
1 | import 'regenerator-runtime/runtime';
2 | import storage from '../../utils/storage';
3 |
4 | const FORBIDDEN = 403;
5 |
6 | const getCsrfToken = async () => {
7 | const csrfToken = await storage.get('csrfToken');
8 | return csrfToken;
9 | };
10 |
11 | const fetcher = async args => {
12 | const headers = new Headers();
13 |
14 | headers.set('Content-Type', 'application/json');
15 | headers.set('Accept', 'application/json');
16 |
17 | let csrfToken = await getCsrfToken();
18 | if (csrfToken !== undefined) {
19 | headers.set('X-CSRF-Token', csrfToken);
20 | }
21 |
22 | const res = await fetch(args.url, {
23 | headers,
24 | credentials: 'include',
25 | method: args.method,
26 | body: args.body
27 | });
28 |
29 | csrfToken = res.headers.get('X-CSRF-Token');
30 | if (csrfToken !== null) {
31 | await storage.set({ csrfToken });
32 | }
33 |
34 | if (res.status === FORBIDDEN) {
35 | return {
36 | status: 403
37 | };
38 | }
39 |
40 | const j = await res.json();
41 | return j;
42 | };
43 |
44 | const getRequest = async url => {
45 | const args = {
46 | method: 'GET',
47 | url,
48 | body: undefined
49 | };
50 | const res = await fetcher(args);
51 | return res;
52 | };
53 |
54 | const postRequest = async (url, body) => {
55 | const args = {
56 | method: 'POST',
57 | url,
58 | body
59 | };
60 | const res = await fetcher(args);
61 | return res;
62 | };
63 |
64 | export default {
65 | async getServers() {
66 | return getRequest(`${process.env.API_URL}/api/private/servers`);
67 | },
68 | async Logout() {
69 | return getRequest(`${process.env.API_URL}/api/user/logout`);
70 | },
71 | async connectServer(serverId) {
72 | return getRequest(`${process.env.API_URL}/api/private/servers/connect/${serverId}`);
73 | },
74 | async login(email, password) {
75 | return postRequest(
76 | `${process.env.API_URL}/api/user/login`,
77 | JSON.stringify({
78 | email,
79 | password
80 | })
81 | );
82 | }
83 | };
84 |
--------------------------------------------------------------------------------
/api/models/connection.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/jinzhu/gorm"
7 | )
8 |
9 | type AllConnections []Connection
10 |
11 | // Connections contains the email confirmation tokens with a one to one mapping
12 | // to the user
13 | type Connection struct {
14 | BaseModel
15 | UserID uint `json:"user_id"`
16 | ServerID uint `json:"server_id"`
17 | ServerCountry string `json:"server_country"`
18 | }
19 |
20 | func (c *Connection) Find() error {
21 | if err := db().Where(&c).First(&c).Error; err != nil {
22 | return err
23 | }
24 | return nil
25 | }
26 |
27 | func (c *Connection) GetUser() (*User, error) {
28 | var user User
29 | if err := db().Model(&user).Related(&c).Error; err != nil {
30 | return nil, err
31 | }
32 | return &user, nil
33 | }
34 |
35 | func (c *Connection) Create() error {
36 | if err := db().Create(&c).Error; err != nil {
37 | return err
38 | }
39 | return nil
40 | }
41 |
42 | func (c *Connection) Save() error {
43 | if err := db().Save(&c).Error; err != nil {
44 | return err
45 | }
46 | return nil
47 | }
48 |
49 | func (c *Connection) Delete() error {
50 | if err := db().Delete(&c).Error; err != nil {
51 | return err
52 | }
53 | return nil
54 | }
55 |
56 | func (ac *AllConnections) FindAll(offset int) error {
57 | limit := 20
58 | if err := db().Order("created_at desc").Limit(limit).Offset(offset).Find(&ac).Error; err != nil {
59 | return err
60 | }
61 | return nil
62 | }
63 |
64 | func (ac *AllConnections) Count() (*int, error) {
65 | var count int
66 | if err := db().Model(&AllConnections{}).Count(&count).Error; err != nil {
67 | return nil, err
68 | }
69 | return &count, nil
70 | }
71 |
72 | // BeforeCreate sets the CreatedAt column to the current time
73 | func (c *Connection) BeforeCreate(scope *gorm.Scope) error {
74 | scope.SetColumn("CreatedAt", time.Now())
75 | return nil
76 | }
77 |
78 | // BeforeUpdate sets the UpdatedAt column to the current time
79 | func (c *Connection) BeforeUpdate(scope *gorm.Scope) error {
80 | scope.SetColumn("UpdatedAt", time.Now())
81 | return nil
82 | }
83 |
--------------------------------------------------------------------------------
/api/models/server.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/jinzhu/gorm"
7 | )
8 |
9 | type AllServers []Server
10 |
11 | type ServerType string
12 |
13 | var (
14 | ServerTypeProxy ServerType = "Proxy"
15 | ServerTypeVPN ServerType = "VPN"
16 | )
17 |
18 | // Cart contains the details of which plans each user is signed trying to purchase
19 | type Server struct {
20 | BaseModel
21 | Country string `form:"country" json:"country" binding:"required"`
22 | CountryCode string `form:"country_code" json:"country_code" binding:"required"`
23 | Type ServerType `form:"type" json:"type"` // binding:"required" removed for time being
24 | IP string `form:"ip" json:"ip" binding:"required"`
25 | Port int `form:"port" json:"port" binding:"required"`
26 | Username string `form:"username" json:"username" binding:"required"`
27 | Password string `form:"password" json:"password" binding:"required"`
28 | ImagePath string `form:"image_path" json:"image_path"`
29 | }
30 |
31 | func (s *Server) Find() error {
32 | if err := db().Where(&s).First(&s).Error; err != nil {
33 | return err
34 | }
35 | return nil
36 | }
37 |
38 | func (s *Server) Create() error {
39 | if err := db().Create(&s).Error; err != nil {
40 | return err
41 | }
42 | return nil
43 | }
44 |
45 | func (s *Server) Save() error {
46 | if err := db().Save(&s).Error; err != nil {
47 | return err
48 | }
49 | return nil
50 | }
51 |
52 | func (s *Server) Delete() error {
53 | if err := db().Delete(&s).Error; err != nil {
54 | return err
55 | }
56 | return nil
57 | }
58 |
59 | func (as *AllServers) FindAll() error {
60 | if err := db().Find(&as).Error; err != nil {
61 | return err
62 | }
63 | return nil
64 | }
65 |
66 | // BeforeCreate sets the CreatedAt column to the current time
67 | func (s *Server) BeforeCreate(scope *gorm.Scope) error {
68 | scope.SetColumn("CreatedAt", time.Now())
69 |
70 | return nil
71 | }
72 |
73 | // Beforecdate sets the cdatedAt column to the current time
74 | func (s *Server) Beforecdate(scope *gorm.Scope) error {
75 | scope.SetColumn("cdatedAt", "check")
76 | return nil
77 | }
78 |
--------------------------------------------------------------------------------
/proxy/go.sum:
--------------------------------------------------------------------------------
1 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
2 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
3 | github.com/dylankilkenny/goproxy v0.0.0-20200109204127-1c107847a855 h1:E1cvKqWpoqI2y7p0JnUsB8SiXKbGjqLd+PqFLEXuQOI=
4 | github.com/dylankilkenny/goproxy v0.0.0-20200109204127-1c107847a855/go.mod h1:0kYif2kfds0D2VPlQYwBuF79c0hxgr6nSRPm2+KuvtA=
5 | github.com/dylankilkenny/goproxy/ext v0.0.0-20200109204127-1c107847a855 h1:dREF3NkYnTACtJ8XuCnc3tpN92GUMfIcxGs5SpGMkTg=
6 | github.com/dylankilkenny/goproxy/ext v0.0.0-20200109204127-1c107847a855/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
7 | github.com/elazarl/goproxy v0.0.0-20190711103511-473e67f1d7d2 h1:aZtFdDNWY/yH86JPR2WX/PN63635VsE/f/nXNPAbYxY=
8 | github.com/elazarl/goproxy v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
9 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
10 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
11 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
12 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
13 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
14 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
15 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
16 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
19 | gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
20 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
21 |
--------------------------------------------------------------------------------
/site/pages/admin/users/[id].tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { LayoutAdminDash } from '../../../components/Layout';
3 | import AdminSidePanel from '../../../components/admin/AdminSidePanel';
4 | import ErrorMessage from '../../../components/ErrorMessage';
5 | import API from '../../../service/APIService';
6 | import useAsync from '../../../hooks/useAsync';
7 | import UserForm from '../../../components/admin/forms/UserEditForm';
8 | import { useRouter } from 'next/router';
9 |
10 | export default function UserEdit(): JSX.Element {
11 | const router = useRouter();
12 | const userID = router.query.id.toString();
13 | const { data, loading, error } = useAsync(() => API.GetUserByID(userID));
14 | const userplan = useAsync(() => API.GetUserPlanByUserID(userID));
15 | const [respError, setRespError] = useState();
16 | const [success, setSuccess] = useState();
17 | const hasError = !!error;
18 |
19 | let showCreateUserPlan = false;
20 | if (userplan.error) {
21 | showCreateUserPlan = true;
22 | }
23 |
24 | async function HandleSave(body: string) {
25 | const res = await API.UpdateUser(userID, body);
26 | if (res.status == 200) {
27 | setSuccess(true);
28 | setRespError(false);
29 | } else {
30 | setRespError(res);
31 | setSuccess(false);
32 | }
33 | }
34 |
35 | async function HandleDelete() {
36 | const res = await API.DeleteUser(userID);
37 | if (res.status == 200) {
38 | router.push('/admin/users');
39 | } else {
40 | setRespError(res);
41 | }
42 | }
43 |
44 | if (loading) {
45 | return ;
46 | }
47 |
48 | return (
49 | }>
50 | {!error ? (
51 |
59 | ) : (
60 |
61 | )}
62 |
63 | );
64 | }
65 |
66 | UserEdit.getInitialProps = async () => {
67 | return {};
68 | };
69 |
--------------------------------------------------------------------------------
/browser/src/popup/containers/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Image from 'react-bootstrap/Image';
3 | import Spinner from 'react-bootstrap/Spinner';
4 | import Container from 'react-bootstrap/Container';
5 | import Col from 'react-bootstrap/Col';
6 | import Row from 'react-bootstrap/Row';
7 | import { IoIosArrowForward } from 'react-icons/io';
8 | import { IconContext } from 'react-icons';
9 | import ApiService from '../services/apiService';
10 | import AuthService from '../services/authService';
11 |
12 | class Main extends React.Component {
13 | state = {};
14 |
15 | async componentDidMount() {
16 | const resp = await ApiService.getServers();
17 | if (resp.status === 200) {
18 | this.setState({ servers: resp.data.servers });
19 | } else if (resp.status === 403) {
20 | await AuthService.logout();
21 | this.props.renderLogin();
22 | }
23 | }
24 |
25 | connect = server => {
26 | this.props.renderConnect(server);
27 | };
28 |
29 | render() {
30 | if (!this.state.servers) {
31 | return (
32 |
33 |
34 |
35 | );
36 | }
37 | return (
38 |
39 | {this.state.servers.map(server => (
40 | this.connect(server)} className="server-row" key={server.id}>
41 |
42 |
43 |
44 |
45 | {server.country}
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | ))}
56 |
57 | );
58 | }
59 | }
60 | export default Main;
61 |
--------------------------------------------------------------------------------
/browser/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eirevpn",
3 | "version": "0.0.1",
4 | "description": "EireVPN",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "test": "karma start",
8 | "start": "webpack --config webpack.config.dev.js",
9 | "watch-dev": "webpack --config webpack.config.dev.js --watch",
10 | "build": "webpack --config webpack.config.dist.js"
11 | },
12 | "author": "Kamil Mikosz",
13 | "dependencies": {
14 | "@babel/core": "^7.5.5",
15 | "@babel/plugin-proposal-class-properties": "^7.8.3",
16 | "@babel/preset-env": "^7.5.5",
17 | "@babel/preset-react": "^7.0.0",
18 | "babel-eslint": "^10.0.3",
19 | "babel-loader": "^8.0.6",
20 | "bootstrap": "^4.3.1",
21 | "chai": "^4.2.0",
22 | "clean-webpack-plugin": "^0.1.19",
23 | "copy-webpack-plugin": "^4.6.0",
24 | "css-loader": "^0.28.11",
25 | "enzyme": "^3.10.0",
26 | "enzyme-adapter-react-16": "^1.14.0",
27 | "eslint": "^4.19.1",
28 | "eslint-config-airbnb": "^16.1.0",
29 | "eslint-loader": "^2.2.1",
30 | "eslint-plugin-import": "^2.18.2",
31 | "eslint-plugin-jsx-a11y": "^6.2.3",
32 | "eslint-plugin-mocha": "^5.3.0",
33 | "eslint-plugin-react": "^7.14.3",
34 | "html-webpack-plugin": "^3.2.0",
35 | "jest-enzyme": "^6.1.2",
36 | "jwt-decode": "^2.2.0",
37 | "karma": "^2.0.5",
38 | "karma-chrome-launcher": "^2.2.0",
39 | "karma-cli": "^1.0.1",
40 | "karma-mocha": "^1.3.0",
41 | "karma-sourcemap-loader": "^0.3.7",
42 | "karma-webpack": "^3.0.5",
43 | "mocha": "^5.2.0",
44 | "node-abort-controller": "^1.0.4",
45 | "node-sass": "^4.12.0",
46 | "prop-types": "^15.7.2",
47 | "react": "^16.8.6",
48 | "react-alert": "^5.5.0",
49 | "react-bootstrap": "^1.0.0-beta.12",
50 | "react-dom": "^16.8.6",
51 | "react-icons": "^3.7.0",
52 | "react-icons-kit": "^1.3.1",
53 | "regenerator-runtime": "^0.13.3",
54 | "sass-loader": "^7.1.0",
55 | "style-loader": "^0.21.0",
56 | "styled-components": "^4.4.0",
57 | "uglifyjs-webpack-plugin": "^1.3.0",
58 | "webpack": "^4.37.0",
59 | "webpack-cli": "^3.3.6",
60 | "xmlhttprequest": "^1.8.0",
61 | "zip-webpack-plugin": "^3.0.0"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/browser/src/background/background-chrome.js:
--------------------------------------------------------------------------------
1 | import ext from '../utils/ext';
2 | import storage from '../utils/storage';
3 | import util from './util';
4 |
5 | let proxyUsername;
6 | let proxyPassword;
7 |
8 | function disconnectProxy() {
9 | proxyUsername = undefined;
10 | proxyPassword = undefined;
11 | ext.proxy.settings.clear({ scope: 'regular' }, () => {});
12 | console.log('disconnected');
13 | ext.browserAction.setBadgeText({ text: '' });
14 | ext.browserAction.setBadgeBackgroundColor({ color: '#0000' });
15 | storage.set({ connected: false, server: undefined, ip: '' });
16 | }
17 |
18 | function connectProxy(proxy) {
19 | const config = {
20 | mode: 'fixed_servers',
21 | rules: {
22 | singleProxy: {
23 | host: proxy.ip,
24 | port: proxy.port
25 | }
26 | }
27 | };
28 | proxyUsername = proxy.username;
29 | proxyPassword = proxy.password;
30 | ext.proxy.settings.set({ value: config, scope: 'regular' }, () => {});
31 | ext.browserAction.setBadgeText({ text: 'on' });
32 | ext.browserAction.setBadgeBackgroundColor({ color: 'green' });
33 | util
34 | .timeout(1500, fetch('https://api.eirevpn.ie/api/plans'))
35 | .then(() => {})
36 | .catch(error => {
37 | console.log(error);
38 | storage.set({ proxy_error: true });
39 | disconnectProxy();
40 | });
41 | console.log('connected');
42 | }
43 |
44 | function setAuth(details, callbackFn) {
45 | if (proxyUsername !== undefined && proxyPassword !== undefined) {
46 | callbackFn({
47 | authCredentials: { username: proxyUsername, password: proxyPassword }
48 | });
49 | } else {
50 | console.log('proxyUsername or proxyPassword not defined');
51 | storage.set({ proxy_error: true });
52 | disconnectProxy();
53 | }
54 | }
55 |
56 | function handleMessage(request, sender, sendResponse) {
57 | if (request.action === 'connect') {
58 | connectProxy(request.data);
59 | } else if (request.action === 'disconnect') {
60 | storage.set({ proxy_error: true });
61 | disconnectProxy();
62 | }
63 | sendResponse(true);
64 | }
65 |
66 | ext.runtime.onMessage.addListener(handleMessage);
67 | ext.webRequest.onAuthRequired.addListener(setAuth, { urls: [''] }, ['asyncBlocking']);
68 |
--------------------------------------------------------------------------------
/site/components/admin/forms/UserCreateForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import FormInput from '../../FormInput';
4 | import Card from 'react-bootstrap/Card';
5 | import ErrorMessage from '../../ErrorMessage';
6 | import APIError from '../../../interfaces/error';
7 | import ButtonMain from '../../ButtonMain';
8 |
9 | interface UserCreateFormProps {
10 | HandleSave: (body: string) => Promise;
11 | error: APIError;
12 | }
13 |
14 | const UserCreateForm: React.FC = ({ HandleSave, error }) => {
15 | const [firstname, setFirstname] = useState('');
16 | const [lastname, setLastname] = useState('');
17 | const [email, setEmail] = useState('');
18 | const [password, setPassword] = useState('');
19 |
20 | const hasError = !!error;
21 |
22 | const handleSaveClick = () => {
23 | HandleSave(JSON.stringify({ firstname, lastname, email, password }));
24 | };
25 |
26 | return (
27 |
28 |
29 |
30 |
31 | Create User
32 |
33 |
34 |
35 |
36 |
39 |
45 |
46 |
47 |
48 |
49 |
56 |
57 |
58 |
59 |
60 |
61 | );
62 | };
63 |
64 | export default UserCreateForm;
65 |
--------------------------------------------------------------------------------
/browser/src/popup/components/header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { FaArrowLeft } from 'react-icons/fa';
4 | import Image from 'react-bootstrap/Image';
5 | import { MdSettings } from 'react-icons/md';
6 | import { IconContext } from 'react-icons';
7 | import PropTypes from 'prop-types';
8 |
9 | const Cont = styled.div`
10 | padding-top: 5px;
11 | height 60px;
12 | padding-left: 14px;
13 | background-color: #004643;
14 | `;
15 |
16 | const BrandingCont = styled.div`
17 | float: left;
18 | width: 85%;
19 | color: #5a6268;
20 | `;
21 |
22 | const IconCont = styled.div`
23 | margin-left: 85%;
24 | `;
25 |
26 | const HeaderBranding = () => (
27 |
28 |
29 |
ÉireVPN
30 |
31 | );
32 |
33 | const LoginHeader = () => (
34 |
35 |
36 |
37 |
38 |
39 | );
40 |
41 | const MainHeader = ({ renderSettings }) => (
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | );
53 |
54 | const SettingsHeader = ({ renderMain }) => (
55 |
56 |
57 |
58 |
59 |
60 | );
61 |
62 | const Header = ({ view, renderSettings, renderMain }) => {
63 | switch (view) {
64 | case 0:
65 | return ;
66 | case 1:
67 | return ;
68 | case 3:
69 | return ;
70 | default:
71 | return ;
72 | }
73 | };
74 |
75 | MainHeader.propTypes = {
76 | renderSettings: PropTypes.func.isRequired
77 | };
78 |
79 | SettingsHeader.propTypes = {
80 | renderMain: PropTypes.func.isRequired
81 | };
82 |
83 | Header.propTypes = {
84 | renderSettings: PropTypes.func,
85 | renderMain: PropTypes.func,
86 | view: PropTypes.number.isRequired
87 | };
88 |
89 | export default Header;
90 |
--------------------------------------------------------------------------------
/site/components/user/EditDetailsDashboard.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import User from '../../interfaces/user';
3 | import APIError from '../../interfaces/error';
4 | import Container from 'react-bootstrap/Container';
5 | import Row from 'react-bootstrap/Row';
6 | import Col from 'react-bootstrap/Col';
7 | import { IconContext } from 'react-icons';
8 | import { IoMdPerson, IoIosArrowBack } from 'react-icons/io';
9 | import EditDetailsForm from '../../components/user/forms/EditDetailsForm';
10 | import ChangePasswordForm from '../../components/user/forms/ChangePasswordForm';
11 | import { useRouter } from 'next/router';
12 | import SuccessMessage from '../SuccessMessage';
13 | import ErrorMessage from '../ErrorMessage';
14 |
15 | interface EditDetailsDashbaordProps {
16 | user: User;
17 | success: boolean;
18 | error: APIError;
19 | HandleDetailsSave: (body: string) => Promise;
20 | HandlePasswordSave: (body: string) => Promise;
21 | }
22 |
23 | const EditDetailsDashbaord: React.FC = ({
24 | user,
25 | success,
26 | error,
27 | HandleDetailsSave,
28 | HandlePasswordSave
29 | }) => {
30 | const router = useRouter();
31 | const handleBackClick = () => {
32 | router.push('/account');
33 | };
34 | return (
35 |
36 |
37 |
38 |
39 |
40 |
41 |
My Account
42 |
43 |
44 |
45 |
46 |
47 |
48 |
Back
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | );
67 | };
68 |
69 | export default EditDetailsDashbaord;
70 |
--------------------------------------------------------------------------------
/api/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 |
7 | "gopkg.in/yaml.v2"
8 | )
9 |
10 | var configFilename string
11 |
12 | type Config struct {
13 | App struct {
14 | Port string `yaml:"Port"`
15 | Domain string `yaml:"Domain"`
16 | JWTSecret string `yaml:"JWTSecret"`
17 | AllowedOrigins []string `yaml:"AllowedOrigins"`
18 | EnableCSRF bool `yaml:"EnableCSRF"`
19 | EnableSubscriptions bool `yaml:"EnableSubscriptions"`
20 | EnableAuth bool `yaml:"EnableAuth"`
21 | AuthCookieAge int `yaml:"AuthCookieAge"`
22 | RefreshCookieAge int `yaml:"RefreshCookieAge"`
23 | AuthCookieName string `yaml:"AuthCookieName"`
24 | RefreshCookieName string `yaml:"RefreshCookieName"`
25 | AuthTokenExpiry int `yaml:"AuthTokenExpiry"`
26 | RefreshTokenExpiry int `yaml:"RefreshTokenExpiry"`
27 | TestMode bool `yaml:"TestMode"`
28 | } `yaml:"App"`
29 |
30 | DB struct {
31 | User string `yaml:"User"`
32 | Password string `yaml:"Password"`
33 | Database string `yaml:"Database"`
34 | Host string `yaml:"Host"`
35 | Port int `yaml:"Port"`
36 | } `yaml:"DB"`
37 |
38 | Stripe struct {
39 | SecretKey string `yaml:"SecretKey"`
40 | EndpointSecret string `yaml:"EndpointSecret"`
41 | IntegrationActive bool `yaml:"IntegrationActive"`
42 | SuccessURL string `yaml:"SuccessUrl"`
43 | ErrorURL string `yaml:"ErrorUrl"`
44 | } `yaml:"Stripe"`
45 |
46 | SendGrid struct {
47 | APIKey string `yaml:"APIKey"`
48 | IntegrationActive bool `yaml:"IntegrationActive"`
49 | Templates struct {
50 | Registration string `yaml:"Registration"`
51 | SupportRequest string `yaml:"SupportRequest"`
52 | ForgotPassword string `yaml:"ForgotPassword"`
53 | } `yaml:"Templates"`
54 | } `yaml:"SendGrid"`
55 | }
56 |
57 | func Init(filename string) {
58 | configFilename = filename
59 | }
60 |
61 | func Load() Config {
62 | conf := Config{}
63 | yamlFile, err := ioutil.ReadFile(configFilename)
64 | if err != nil {
65 | fmt.Println(err)
66 | }
67 | err = yaml.Unmarshal(yamlFile, &conf)
68 | if err != nil {
69 | fmt.Println(err)
70 | }
71 | return conf
72 | }
73 |
74 | func (c *Config) SaveConfig() error {
75 | newConf, err := yaml.Marshal(&c)
76 | if err != nil {
77 | return err
78 | }
79 | err = ioutil.WriteFile("config.yaml", newConf, 0644)
80 | if err != nil {
81 | return err
82 | }
83 | return nil
84 | }
85 |
--------------------------------------------------------------------------------
/site/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'react-bootstrap/Image';
2 | import Container from 'react-bootstrap/Container';
3 | import Row from 'react-bootstrap/Row';
4 | import Col from 'react-bootstrap/Col';
5 | import Link from 'next/link';
6 |
7 | export default function Footer() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ÉireVPN
18 |
19 |
20 |
21 |
22 | Copyright © ÉireVPN 2020 - All rights reserved.
23 |
24 |
25 |
26 |
27 |
28 |
29 | Downloads
30 |
31 |
32 |
33 |
34 | Support
35 |
36 |
37 |
38 |
39 | Login
40 |
41 |
42 |
43 |
44 | Sign Up
45 |
46 |
47 |
48 |
49 |
50 |
51 | Privacy
52 |
53 |
54 |
55 |
56 | Cookies
57 |
58 |
59 |
60 |
61 | Terms
62 |
63 |
64 |
65 |
74 |
75 |
76 |
77 |
78 | );
79 | }
80 |
--------------------------------------------------------------------------------
/api/handlers/settings/settings.go:
--------------------------------------------------------------------------------
1 | package settings
2 |
3 | import (
4 | "eirevpn/api/config"
5 | "eirevpn/api/errors"
6 | "eirevpn/api/logger"
7 | "net/http"
8 | "strconv"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | type SettingsFields struct {
14 | EnableCSRF string `json:"enableCsrf" binding:"required"`
15 | EnableSubscriptions string `json:"enableSubscriptions" binding:"required"`
16 | EnableAuth string `json:"enableAuth" binding:"required"`
17 | IntegrationActive string `json:"enableStripe" binding:"required"`
18 | AllowedOrigins []string `json:"allowedOrigins" binding:"required"`
19 | }
20 |
21 | // UpdateSettings updates the config.yaml
22 | func UpdateSettings(c *gin.Context) {
23 |
24 | settingsUpdates := SettingsFields{}
25 | if err := c.BindJSON(&settingsUpdates); err != nil {
26 | logger.Log(logger.Fields{
27 | Loc: "/settings/update - UpdateSettings()",
28 | Code: errors.SettingsUpdateFailed.Code,
29 | Err: err.Error(),
30 | })
31 | c.AbortWithStatusJSON(errors.SettingsUpdateFailed.Status, errors.SettingsUpdateFailed)
32 | return
33 | }
34 |
35 | conf := config.Load()
36 | conf.App.EnableCSRF = settingsUpdates.EnableCSRF == "true"
37 | conf.App.EnableSubscriptions = settingsUpdates.EnableSubscriptions == "true"
38 | conf.App.EnableAuth = settingsUpdates.EnableAuth == "true"
39 | conf.Stripe.IntegrationActive = settingsUpdates.IntegrationActive == "true"
40 | conf.App.AllowedOrigins = settingsUpdates.AllowedOrigins
41 |
42 | if err := conf.SaveConfig(); err != nil {
43 | logger.Log(logger.Fields{
44 | Loc: "/settings/update - UpdateSettings()",
45 | Code: errors.SettingsUpdateFailed.Code,
46 | Extra: map[string]interface{}{"PlanID": c.Param("id")},
47 | Err: err.Error(),
48 | })
49 | c.AbortWithStatusJSON(errors.SettingsUpdateFailed.Status, errors.SettingsUpdateFailed)
50 | return
51 |
52 | }
53 | c.JSON(http.StatusOK, gin.H{
54 | "status": 200,
55 | })
56 | }
57 |
58 | // Settings fetches the settings
59 | func Settings(c *gin.Context) {
60 | conf := config.Load()
61 |
62 | s := SettingsFields{
63 | EnableCSRF: strconv.FormatBool(conf.App.EnableCSRF),
64 | EnableSubscriptions: strconv.FormatBool(conf.App.EnableSubscriptions),
65 | EnableAuth: strconv.FormatBool(conf.App.EnableAuth),
66 | IntegrationActive: strconv.FormatBool(conf.Stripe.IntegrationActive),
67 | AllowedOrigins: conf.App.AllowedOrigins,
68 | }
69 |
70 | c.JSON(http.StatusOK, gin.H{
71 | "status": 200,
72 | "data": gin.H{
73 | "settings": s,
74 | },
75 | })
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/api/models/user.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "eirevpn/api/integrations/stripe"
5 | "time"
6 |
7 | stripego "github.com/stripe/stripe-go"
8 |
9 | "github.com/jinzhu/gorm"
10 | "golang.org/x/crypto/bcrypt"
11 | )
12 |
13 | type UserType string
14 | type AllUsers []User
15 |
16 | var (
17 | UserTypeNormal UserType = "normal"
18 | UserTypeAdmin UserType = "admin"
19 | )
20 |
21 | // User contains the users details
22 | type User struct {
23 | BaseModel
24 | FirstName string `json:"firstname"`
25 | LastName string `json:"lastname"`
26 | Email string `json:"email" binding:"required"`
27 | Password string `json:"password" binding:"required"`
28 | StripeCustomerID string `json:"stripe_customer_id"`
29 | Type UserType `json:"type"`
30 | EmailConfirmed bool `json:"email_confirmed"`
31 | }
32 |
33 | func (u *User) Find() error {
34 | if err := db().Where(u).First(&u).Error; err != nil {
35 | return err
36 | }
37 | return nil
38 | }
39 |
40 | func (u *User) Create() error {
41 | if err := db().Create(&u).Error; err != nil {
42 | return err
43 | }
44 | return nil
45 | }
46 |
47 | func (u *User) Save() error {
48 | if err := db().Save(&u).Error; err != nil {
49 | return err
50 | }
51 | return nil
52 | }
53 |
54 | func (u *User) Delete() error {
55 | if err := db().Delete(&u).Error; err != nil {
56 | return err
57 | }
58 | return nil
59 | }
60 |
61 | func (u *User) CreateStripeCustomer() (*stripego.Customer, error) {
62 | return stripe.CreateCustomer(u.Email, u.FirstName, u.LastName, u.ID)
63 | }
64 |
65 | func (au *AllUsers) FindAll(offset int) error {
66 | limit := 20
67 | if err := db().Order("created_at desc").Limit(limit).Offset(offset).Find(&au).Error; err != nil {
68 | return err
69 | }
70 | return nil
71 | }
72 |
73 | func (au *AllUsers) Count() (*int, error) {
74 | var count int
75 | if err := db().Model(&AllUsers{}).Count(&count).Error; err != nil {
76 | return nil, err
77 | }
78 | return &count, nil
79 | }
80 |
81 | // BeforeCreate sets the CreatedAt column to the current time
82 | // and encrypts the users password
83 | func (u *User) BeforeCreate(scope *gorm.Scope) error {
84 | scope.SetColumn("CreatedAt", time.Now())
85 | if pw, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost); err == nil {
86 | scope.SetColumn("Password", string(pw))
87 | }
88 | return nil
89 | }
90 |
91 | // BeforeUpdate sets the UpdatedAt column to the current time
92 | func (u *User) BeforeUpdate(scope *gorm.Scope) error {
93 | scope.SetColumn("UpdatedAt", time.Now())
94 | return nil
95 | }
96 |
--------------------------------------------------------------------------------
/site/components/admin/forms/PlanCreateForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import Card from 'react-bootstrap/Card';
4 | import FormInput from '../../FormInput';
5 | import FormDropdown from '../../FormDropdown';
6 | import ButtonMain from '../../ButtonMain';
7 | import ErrorMessage from '../../ErrorMessage';
8 | import APIError from '../../../interfaces/error';
9 |
10 | interface PlanCreateFormProps {
11 | error: APIError;
12 | HandleSave: (body: string) => Promise;
13 | }
14 |
15 | type Event = React.ChangeEvent;
16 |
17 | const UserCreateForm: React.FC = ({ error, HandleSave }) => {
18 | const [name, setName] = useState('');
19 | const [amountString, setAmount] = useState('');
20 | const [interval, setInterval] = useState('');
21 | const [intervalCountString, setIntervalCount] = useState('');
22 | const [plan_type, setPlanType] = useState('');
23 |
24 | const hasError = !!error;
25 |
26 | const handleSaveClick = () => {
27 | const amount = parseInt(amountString);
28 | const interval_count = parseInt(intervalCountString);
29 | const currency = 'EUR';
30 | HandleSave(JSON.stringify({ name, amount, interval, interval_count, plan_type, currency }));
31 | };
32 |
33 | return (
34 |
35 |
36 |
37 |
38 | Create Plan
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
51 |
57 |
58 |
59 |
66 |
67 |
68 |
69 |
70 |
71 | );
72 | };
73 |
74 | export default UserCreateForm;
75 |
--------------------------------------------------------------------------------
/site/components/user/forms/ForgotPasswordForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import ButtonMain from '../../ButtonMain';
4 | import Card from 'react-bootstrap/Card';
5 | import ErrorMessage from '../../ErrorMessage';
6 | import FormInput from '../../FormInput';
7 | import APIError from '../../../interfaces/error';
8 | import SuccessMessage from '../../SuccessMessage';
9 |
10 | interface ForgotPasswordProps {
11 | success: boolean;
12 | error: APIError;
13 | HandleSubmit: (body: string) => Promise;
14 | }
15 |
16 | type TFormEvent = React.FormEvent;
17 |
18 | const ForgotPasswordForm: React.FC = ({ success, error, HandleSubmit }) => {
19 | const [email, setEmail] = useState();
20 | const [validated, setValidated] = useState(false);
21 |
22 | const handleSubmit = (event: TFormEvent) => {
23 | event.stopPropagation();
24 | event.preventDefault();
25 | const form = event.currentTarget;
26 | // this sets form validation feedback to visible
27 | setValidated(true);
28 | if (form.checkValidity() === true) {
29 | HandleSubmit(JSON.stringify({ email }));
30 | }
31 | };
32 |
33 | return (
34 |
35 |
36 |
37 | {success ? (
38 |
39 |
40 | If a matching account with that email has been found we will send you a reset link.
41 |
42 |
43 | If you do not recieve a email, please check your spam folder
44 |
45 |
46 | ) : (
47 |
57 |
67 |
68 |
69 | )}
70 |
71 |
72 |
73 | );
74 | };
75 |
76 | export default ForgotPasswordForm;
77 |
--------------------------------------------------------------------------------
/site/pages/downloads.tsx:
--------------------------------------------------------------------------------
1 | import { LayoutMain } from '../components/Layout';
2 | import Image from 'react-bootstrap/Image';
3 | import Container from 'react-bootstrap/Container';
4 | import Row from 'react-bootstrap/Row';
5 | import Col from 'react-bootstrap/Col';
6 | import Button from 'react-bootstrap/Button';
7 | import SuccessMessage from '../components/SuccessMessage';
8 | import { useRouter } from 'next/router';
9 |
10 | export default function DownloadsPage() {
11 | const router = useRouter();
12 | const signedup = router.query.signedup;
13 |
14 | const chromeLink =
15 | 'https://chrome.google.com/webstore/detail/%C3%A9irevpn-beta-free-vpn-ad/fmhlhjegfkgemampnpomkajhdpjnhmie?hl=en-GB';
16 | const firefoxLink =
17 | 'https://addons.mozilla.org/en-US/firefox/addon/%C3%A9irevpn-free-vpn-ad-blocker/';
18 | return (
19 |
20 |
73 |
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/site/components/SubscriptionCard.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { IconContext } from 'react-icons';
3 | import { IoIosCalendar } from 'react-icons/io';
4 | import Row from 'react-bootstrap/Row';
5 | import Col from 'react-bootstrap/Col';
6 | import Badge from 'react-bootstrap/Badge';
7 | import Card from 'react-bootstrap/Card';
8 | import dayjs from 'dayjs';
9 | import useAsync from '../hooks/useAsync';
10 | import API from '../service/APIService';
11 |
12 | interface SubscriptionCardProps {
13 | userid: string;
14 | }
15 |
16 | const SubscriptionCard: React.FC = ({ userid }) => {
17 | const { data, loading, error } = useAsync(() => API.GetUserPlanByUserID(userid));
18 |
19 | if (loading || data === undefined) {
20 | return ;
21 | }
22 |
23 | const userplan = data?.userplan;
24 | let status = userplan.active ? 'Active' : 'Disabled';
25 | return (
26 |
27 |
28 |
29 | Subscription
30 | {userplan.plan_type == 'FREE' ? (
31 |
32 | Trial
33 |
34 | ) : (
35 |
36 | {status}
37 |
38 | )}
39 |
40 |
41 |
42 |
43 |
44 |
45 | {userplan.plan_name}
46 |
47 |
48 |
49 |
50 |
58 |
59 | {dayjs(userplan.start_date)
60 | .format('DD-MM-YYYY')
61 | .toString()}
62 |
63 |
64 |
65 |
73 |
74 | {dayjs(userplan.expiry_date)
75 | .format('DD-MM-YYYY')
76 | .toString()}
77 |
78 |
79 |
80 |
81 |
82 |
83 | );
84 | };
85 |
86 | export default SubscriptionCard;
87 |
--------------------------------------------------------------------------------
/browser/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const {
2 | getHTMLPlugins,
3 | getOutput,
4 | getCopyPlugins,
5 | getFirefoxCopyPlugins,
6 | getEntry,
7 | getDefinePlugin,
8 | getChromeEntry
9 | } = require('./webpack.utils');
10 | const config = require('./config.json');
11 |
12 | const generalConfig = {
13 | mode: 'development',
14 | devtool: 'source-map',
15 | module: {
16 | rules: [
17 | {
18 | loader: 'babel-loader',
19 | exclude: /node_modules/,
20 | test: /\.(js|jsx)$/,
21 | options: {
22 | presets: ['@babel/preset-env', '@babel/preset-react'],
23 | plugins: ['@babel/plugin-proposal-class-properties']
24 | },
25 | resolve: {
26 | extensions: ['.js', '.jsx']
27 | }
28 | },
29 | {
30 | test: /\.(js|jsx)$/,
31 | exclude: /node_modules/,
32 | use: ['eslint-loader']
33 | },
34 | {
35 | test: /\.(scss|css)$/,
36 | use: [
37 | {
38 | loader: 'style-loader'
39 | },
40 | {
41 | loader: 'css-loader'
42 | },
43 | {
44 | loader: 'sass-loader'
45 | }
46 | ]
47 | }
48 | ]
49 | }
50 | };
51 |
52 | module.exports = [
53 | {
54 | ...generalConfig,
55 | entry: getChromeEntry(config.chromePath),
56 | output: getOutput('chrome', config.devDirectory),
57 | plugins: [
58 | ...getHTMLPlugins('chrome', config.devDirectory, config.chromePath),
59 | ...getCopyPlugins('chrome', config.devDirectory, config.chromePath),
60 | ...getDefinePlugin(
61 | JSON.stringify('http://localhost:3000'),
62 | JSON.stringify('http://localhost:3001')
63 | )
64 | // ...getDefinePlugin(
65 | // JSON.stringify('https://eirevpn.ie'),
66 | // JSON.stringify('https://api.eirevpn.ie')
67 | // )
68 | ]
69 | },
70 | {
71 | ...generalConfig,
72 | entry: getEntry(config.operaPath),
73 | output: getOutput('opera', config.devDirectory),
74 | plugins: [
75 | ...getHTMLPlugins('opera', config.devDirectory, config.operaPath),
76 | ...getCopyPlugins('opera', config.devDirectory, config.operaPath)
77 | // new webpack.DefinePlugin({
78 | // 'process.env.API_URL': 'http://localhost:3001/'
79 | // })
80 | ]
81 | },
82 | {
83 | ...generalConfig,
84 | entry: getEntry(config.firefoxPath),
85 | output: getOutput('firefox', config.devDirectory),
86 | plugins: [
87 | ...getFirefoxCopyPlugins('firefox', config.devDirectory, config.firefoxPath),
88 | ...getHTMLPlugins('firefox', config.devDirectory, config.firefoxPath),
89 | ...getDefinePlugin(
90 | JSON.stringify('http://localhost:3000'),
91 | JSON.stringify('http://localhost:3001')
92 | )
93 | // ...getDefinePlugin(
94 | // JSON.stringify('https://eirevpn.ie'),
95 | // JSON.stringify('https://api.eirevpn.ie')
96 | // )
97 | ]
98 | }
99 | ];
100 |
--------------------------------------------------------------------------------
/site/components/user/forms/ContactForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import ButtonMain from '../../ButtonMain';
4 | import Card from 'react-bootstrap/Card';
5 | import ErrorMessage from '../../ErrorMessage';
6 | import FormInput from '../../FormInput';
7 |
8 | interface ContactFormProps {
9 | success: boolean;
10 | HandleSend: (body: string) => Promise;
11 | }
12 |
13 | type TFormEvent = React.FormEvent;
14 |
15 | const ContactForm: React.FC = ({ success, HandleSend }) => {
16 | const [email, setEmail] = useState();
17 | const [subject, setSubject] = useState();
18 | const [message, setMessage] = useState();
19 | const [validated, setValidated] = useState(false);
20 |
21 | // if saved successfully clear password fields
22 | useEffect(() => {
23 | if (success) {
24 | setEmail('');
25 | setSubject('');
26 | setMessage('');
27 | setValidated(false);
28 | }
29 | });
30 |
31 | const handleSubmit = (event: TFormEvent) => {
32 | event.stopPropagation();
33 | event.preventDefault();
34 | const form = event.currentTarget;
35 | // this sets form validation feedback to visible
36 | setValidated(true);
37 | if (form.checkValidity() === true) {
38 | HandleSend(JSON.stringify({ email, subject, message }));
39 | }
40 | };
41 |
42 | return (
43 |
44 |
45 |
46 |
54 |
64 |
65 |
66 |
75 |
76 |
77 |
87 |
88 |
89 |
90 |
91 |
92 | );
93 | };
94 |
95 | export default ContactForm;
96 |
--------------------------------------------------------------------------------
/api/integrations/sendgrid/sendgrid.go:
--------------------------------------------------------------------------------
1 | package sendgrid
2 |
3 | import (
4 | cfg "eirevpn/api/config"
5 | "eirevpn/api/models"
6 | "fmt"
7 |
8 | "github.com/sendgrid/rest"
9 | "github.com/sendgrid/sendgrid-go"
10 | "github.com/sendgrid/sendgrid-go/helpers/mail"
11 | )
12 |
13 | // SendGrid type
14 | type SendGrid struct {
15 | Request rest.Request
16 | }
17 |
18 | // Send builds the request
19 | func Send() *SendGrid {
20 | sg := SendGrid{}
21 | request := sendgrid.GetRequest(cfg.Load().SendGrid.APIKey, "/v3/mail/send", "https://api.sendgrid.com")
22 | request.Method = "POST"
23 | sg.Request = request
24 | return &sg
25 | }
26 |
27 | func (sg *SendGrid) makeRequest() error {
28 | _, err := sendgrid.API(sg.Request)
29 | if err != nil {
30 | fmt.Println("err", err)
31 | return err
32 | }
33 | return nil
34 | }
35 |
36 | // RegistrationMail builds the body for sending a registration email
37 | func (sg *SendGrid) RegistrationMail(user models.User, token string) error {
38 | if !cfg.Load().SendGrid.IntegrationActive {
39 | return nil
40 | }
41 | m := mail.NewV3Mail()
42 | address := "info@eirevpn.ie"
43 | name := "ÉireVPN"
44 | e := mail.NewEmail(name, address)
45 | m.SetFrom(e)
46 | m.SetTemplateID(cfg.Load().SendGrid.Templates.Registration)
47 | p := mail.NewPersonalization()
48 | p.AddTos(mail.NewEmail(user.FirstName+" "+user.LastName, user.Email))
49 | p.SetDynamicTemplateData("confirm_email_url", cfg.Load().App.Domain+"/confirm_email?token="+token)
50 | m.AddPersonalizations(p)
51 | sg.Request.Body = mail.GetRequestBody(m)
52 | return sg.makeRequest()
53 | }
54 |
55 | // RegistrationMail builds the body for sending a registration email
56 | func (sg *SendGrid) SupportRequest(email, subject, message string) error {
57 | if !cfg.Load().SendGrid.IntegrationActive {
58 | return nil
59 | }
60 | m := mail.NewV3Mail()
61 | name := "ÉireVPN Mail Service"
62 | e := mail.NewEmail(name, "mailservice@eirevpn.ie")
63 | m.SetFrom(e)
64 | m.SetTemplateID(cfg.Load().SendGrid.Templates.SupportRequest)
65 | p := mail.NewPersonalization()
66 | p.AddTos(mail.NewEmail("Support", "support@eirevpn.ie"))
67 | p.AddCCs(mail.NewEmail("", email))
68 | p.Subject = subject
69 | p.SetDynamicTemplateData("subject", subject)
70 | p.SetDynamicTemplateData("message", message)
71 | m.AddPersonalizations(p)
72 | sg.Request.Body = mail.GetRequestBody(m)
73 | return sg.makeRequest()
74 | }
75 |
76 | // ForgotPassword builds the body for sending an email to the user
77 | // with a link+token to allow them to reset their password
78 | func (sg *SendGrid) ForgotPassword(email, token string) error {
79 | if !cfg.Load().SendGrid.IntegrationActive {
80 | return nil
81 | }
82 | m := mail.NewV3Mail()
83 | address := "mailservice@eirevpn.ie"
84 | name := "ÉireVPN Mail Service"
85 | e := mail.NewEmail(name, address)
86 | m.SetFrom(e)
87 | m.SetTemplateID(cfg.Load().SendGrid.Templates.ForgotPassword)
88 | p := mail.NewPersonalization()
89 | p.AddTos(mail.NewEmail("", email))
90 | p.SetDynamicTemplateData("password_reset_url", "https://"+cfg.Load().App.Domain+"/forgot_pass/"+token)
91 | m.AddPersonalizations(p)
92 | sg.Request.Body = mail.GetRequestBody(m)
93 | return sg.makeRequest()
94 | }
95 |
--------------------------------------------------------------------------------
/browser/webpack.config.dist.js:
--------------------------------------------------------------------------------
1 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
2 | const {
3 | getHTMLPlugins,
4 | getOutput,
5 | getCopyPlugins,
6 | getZipPlugin,
7 | getFirefoxCopyPlugins,
8 | getEntry,
9 | getChromeEntry,
10 | getDefinePlugin
11 | } = require('./webpack.utils');
12 | const config = require('./config.json');
13 | const CleanWebpackPlugin = require('clean-webpack-plugin');
14 |
15 | const generalConfig = {
16 | mode: 'production',
17 | module: {
18 | rules: [
19 | {
20 | loader: 'babel-loader',
21 | exclude: /node_modules/,
22 | test: /\.(js|jsx)$/,
23 | query: {
24 | presets: ['@babel/preset-env', '@babel/preset-react'],
25 | plugins: ['@babel/plugin-proposal-class-properties']
26 | },
27 | resolve: {
28 | extensions: ['.js', '.jsx']
29 | }
30 | },
31 | {
32 | test: /\.(js|jsx)$/,
33 | exclude: /node_modules/,
34 | use: ['eslint-loader']
35 | },
36 | {
37 | test: /\.(scss|css)$/,
38 | use: [
39 | {
40 | loader: 'style-loader'
41 | },
42 | {
43 | loader: 'css-loader'
44 | },
45 | {
46 | loader: 'sass-loader'
47 | }
48 | ]
49 | }
50 | ]
51 | }
52 | };
53 |
54 | module.exports = [
55 | {
56 | ...generalConfig,
57 | output: getOutput('chrome', config.tempDirectory),
58 | entry: getChromeEntry(config.chromePath),
59 | plugins: [
60 | new CleanWebpackPlugin(['dist', 'temp']),
61 | new UglifyJsPlugin(),
62 | ...getHTMLPlugins('chrome', config.tempDirectory, config.chromePath),
63 | ...getCopyPlugins('chrome', config.tempDirectory, config.chromePath),
64 | getZipPlugin('chrome', config.distDirectory),
65 | ...getDefinePlugin(
66 | JSON.stringify('https://eirevpn.ie'),
67 | JSON.stringify('https://api.eirevpn.ie')
68 | )
69 | ]
70 | },
71 | {
72 | ...generalConfig,
73 | output: getOutput('opera', config.tempDirectory),
74 | entry: getEntry(config.operaPath),
75 | plugins: [
76 | new CleanWebpackPlugin(['dist', 'temp']),
77 | new UglifyJsPlugin(),
78 | ...getHTMLPlugins('opera', config.tempDirectory, config.operaPath),
79 | ...getCopyPlugins('opera', config.tempDirectory, config.operaPath),
80 | getZipPlugin('opera', config.distDirectory)
81 | ]
82 | },
83 | {
84 | ...generalConfig,
85 | entry: getEntry(config.firefoxPath),
86 | output: getOutput('firefox', config.tempDirectory),
87 | plugins: [
88 | new CleanWebpackPlugin(['dist', 'temp']),
89 | new UglifyJsPlugin(),
90 | ...getHTMLPlugins('firefox', config.tempDirectory, config.firefoxPath),
91 | ...getFirefoxCopyPlugins('firefox', config.tempDirectory, config.firefoxPath),
92 | getZipPlugin('firefox', config.distDirectory),
93 | ...getDefinePlugin(
94 | JSON.stringify('https://eirevpn.ie'),
95 | JSON.stringify('https://api.eirevpn.ie')
96 | )
97 | ]
98 | }
99 | ];
100 |
--------------------------------------------------------------------------------
/site/components/admin/forms/LoginForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import Card from 'react-bootstrap/Card';
4 | import ErrorMessage from '../../ErrorMessage';
5 | import SuccessMessage from '../../SuccessMessage';
6 | import APIError from '../../../interfaces/error';
7 | import ButtonMain from '../../ButtonMain';
8 | import FormInput from '../../FormInput';
9 | import { IconContext } from 'react-icons';
10 | import { MdAccountCircle } from 'react-icons/md';
11 | import Link from 'next/link';
12 |
13 | interface LoginFormProps {
14 | signedup?: boolean;
15 | error: APIError;
16 | HandleLogin: (body: string) => Promise;
17 | }
18 |
19 | type TFormEvent = React.FormEvent;
20 |
21 | const LoginForm: React.FC = ({ HandleLogin, signedup, error }) => {
22 | const [email, setEmail] = useState('');
23 | const [password, setPassword] = useState('');
24 | const [validated, setValidated] = useState(false);
25 |
26 | const handleSubmit = (event: TFormEvent) => {
27 | event.stopPropagation();
28 | event.preventDefault();
29 | const form = event.currentTarget;
30 | if (form.checkValidity() === true) {
31 | HandleLogin(JSON.stringify({ email, password }));
32 | }
33 | // this sets form validation feedback to visible
34 | setValidated(true);
35 | };
36 |
37 | return (
38 |
39 |
40 |
41 | Login
42 |
43 |
44 |
46 |
56 |
57 |
58 |
68 |
69 |
70 |
71 |
72 |
73 |
86 |
87 |
88 | );
89 | };
90 |
91 | export default LoginForm;
92 |
--------------------------------------------------------------------------------
/site/components/admin/forms/ServerCreateForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import Col from 'react-bootstrap/Col';
4 | import Row from 'react-bootstrap/Row';
5 | import Card from 'react-bootstrap/Card';
6 | import FormInput from '../../FormInput';
7 | import Button from 'react-bootstrap/Button';
8 | import ErrorMessage from '../../ErrorMessage';
9 | import APIError from '../../../interfaces/error';
10 |
11 | interface ServerCreateFormProps {
12 | HandleSave: (body: FormData) => Promise;
13 | error: APIError;
14 | }
15 |
16 | type TFormEvent = React.FormEvent;
17 |
18 | const ServerCreateForm: React.FC = ({ HandleSave, error }) => {
19 | const [country, setCountry] = useState('');
20 | const [countryCode, setCountryCode] = useState('');
21 | const [ip, setIp] = useState('');
22 | const [port, setPort] = useState('');
23 | const [username, setUsername] = useState('');
24 | const [password, setPassword] = useState('');
25 |
26 | const hasError = !!error;
27 | const handleSubmit = (event: TFormEvent) => {
28 | event.preventDefault();
29 | console.log(event.target);
30 | const data = new FormData(event.target as HTMLFormElement);
31 | for (var pair of data.entries()) {
32 | console.log(pair[0] + ', ' + pair[1]);
33 | }
34 | HandleSave(data);
35 | };
36 |
37 | return (
38 |
83 | );
84 | };
85 |
86 | export default ServerCreateForm;
87 |
--------------------------------------------------------------------------------
/browser/webpack.utils.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin');
2 | const CopyWebpackPlugin = require('copy-webpack-plugin');
3 | const ZipPlugin = require('zip-webpack-plugin');
4 | const path = require('path');
5 | const webpack = require('webpack');
6 |
7 | const getHTMLPlugins = (browserDir, outputDir = 'dev', sourceDir = 'src') => [
8 | new HtmlWebpackPlugin({
9 | title: 'Popup',
10 | filename: path.resolve(__dirname, `${outputDir}/${browserDir}/popup/index.html`),
11 | template: `${sourceDir}/popup/index.html`,
12 | chunks: ['popup']
13 | })
14 | ];
15 |
16 | const getOutput = (browserDir, outputDir = 'dev') => ({
17 | path: path.resolve(__dirname, `${outputDir}/${browserDir}`),
18 | filename: '[name]/[name].js'
19 | });
20 |
21 | const getEntry = (sourceDir = 'src') => ({
22 | popup: path.resolve(__dirname, `${sourceDir}/popup/popup.jsx`),
23 | background: path.resolve(__dirname, `${sourceDir}/background/background.js`),
24 | hotreload: path.resolve(__dirname, `${sourceDir}/utils/hot-reload.js`)
25 | });
26 |
27 | const getChromeEntry = (sourceDir = 'src') => ({
28 | popup: path.resolve(__dirname, `${sourceDir}/popup/popup.jsx`),
29 | background: path.resolve(__dirname, `${sourceDir}/background/background-chrome.js`),
30 | hotreload: path.resolve(__dirname, `${sourceDir}/utils/hot-reload.js`)
31 | });
32 |
33 | const getCopyPlugins = (browserDir, outputDir = 'dev', sourceDir = 'src') => [
34 | new CopyWebpackPlugin([
35 | {
36 | from: `${sourceDir}/assets`,
37 | to: path.resolve(__dirname, `${outputDir}/${browserDir}/assets`)
38 | },
39 | {
40 | from: `${sourceDir}/_locales`,
41 | to: path.resolve(__dirname, `${outputDir}/${browserDir}/_locales`)
42 | },
43 | {
44 | from: `${sourceDir}/manifest.json`,
45 | to: path.resolve(__dirname, `${outputDir}/${browserDir}/manifest.json`)
46 | }
47 | ])
48 | ];
49 |
50 | const getDefinePlugin = (domain, api) => [
51 | new webpack.DefinePlugin({
52 | 'process.env.API_URL': api,
53 | 'process.env.DOMAIN': domain
54 | })
55 | ];
56 |
57 | const getFirefoxCopyPlugins = (browserDir, outputDir = 'dev', sourceDir = 'src') => [
58 | new CopyWebpackPlugin([
59 | {
60 | from: `${sourceDir}/assets`,
61 | to: path.resolve(__dirname, `${outputDir}/${browserDir}/assets`)
62 | },
63 | {
64 | from: `${sourceDir}/_locales`,
65 | to: path.resolve(__dirname, `${outputDir}/${browserDir}/_locales`)
66 | },
67 | {
68 | from: `${sourceDir}/manifest-ff.json`,
69 | to: path.resolve(__dirname, `${outputDir}/${browserDir}/manifest.json`)
70 | }
71 | ])
72 | ];
73 |
74 | const getZipPlugin = (browserDir, outputDir = 'dist') =>
75 | new ZipPlugin({
76 | path: path.resolve(__dirname, `${outputDir}/${browserDir}`),
77 | filename: browserDir,
78 | extension: 'zip',
79 | fileOptions: {
80 | mtime: new Date(),
81 | mode: 0o100664,
82 | compress: true,
83 | forceZip64Format: false
84 | },
85 | zipOptions: {
86 | forceZip64Format: false
87 | }
88 | });
89 |
90 | module.exports = {
91 | getHTMLPlugins,
92 | getOutput,
93 | getCopyPlugins,
94 | getDefinePlugin,
95 | getFirefoxCopyPlugins,
96 | getZipPlugin,
97 | getEntry,
98 | getChromeEntry
99 | };
100 |
--------------------------------------------------------------------------------
/api/models/plan.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "eirevpn/api/integrations/stripe"
8 |
9 | "github.com/jinzhu/gorm"
10 | )
11 |
12 | type AllPlans []Plan
13 |
14 | type PlanType string
15 |
16 | var (
17 | PlanTypePayAsYouGo PlanType = "PAYG"
18 | PlanTypeSubscription PlanType = "SUB"
19 | PlanTypeFreeTrial PlanType = "FREE"
20 | )
21 |
22 | // Plan holds the detilas for a given vpn plan on offer
23 | type Plan struct {
24 | BaseModel
25 | Name string `json:"name" binding:"required"`
26 | Amount int64 `json:"amount" binding:"required"`
27 | Interval string `json:"interval" binding:"required"`
28 | IntervalCount int64 `json:"interval_count" binding:"required"`
29 | PlanType PlanType `json:"plan_type" binding:"required"`
30 | Currency string `json:"currency" binding:"required"`
31 | StripePlanID string `json:"stripe_plan_id"`
32 | StripeProductID string `json:"stripe_product_id"`
33 | }
34 |
35 | // BeforeCreate sets the CreatedAt column to the current time
36 | func (p *Plan) BeforeCreate(scope *gorm.Scope) error {
37 | scope.SetColumn("CreatedAt", time.Now())
38 |
39 | return nil
40 | }
41 |
42 | // BeforeUpdate sets the UpdatedAt column to the current time
43 | func (p *Plan) BeforeUpdate(scope *gorm.Scope) error {
44 | scope.SetColumn("UpdatedAt", time.Now())
45 | return nil
46 | }
47 |
48 | func (p *Plan) Find() error {
49 | if err := db().Where(&p).First(&p).Error; err != nil {
50 | return err
51 | }
52 | return nil
53 | }
54 |
55 | func (ap *AllPlans) FindAll() error {
56 | if err := db().Find(&ap).Error; err != nil {
57 | return err
58 | }
59 | return nil
60 | }
61 |
62 | func (p *Plan) Create() error {
63 | if p.PlanType == PlanTypeSubscription {
64 | if err := p.createStripePlan(); err != nil {
65 | return err
66 | }
67 | }
68 | if err := db().Create(&p).Error; err != nil {
69 | return err
70 | }
71 | return nil
72 | }
73 |
74 | func (p *Plan) Delete() error {
75 | if p.PlanType == PlanTypeSubscription {
76 | if err := p.deleteStripePlan(); err != nil {
77 | return err
78 | }
79 | }
80 | if err := db().Delete(&p).Error; err != nil {
81 | return err
82 | }
83 | return nil
84 | }
85 |
86 | func (p *Plan) Save() error {
87 | if p.PlanType == PlanTypeSubscription {
88 | if err := p.updateStripePlan(); err != nil {
89 | return err
90 | }
91 | }
92 | if err := db().Save(&p).Error; err != nil {
93 | return err
94 | }
95 | return nil
96 | }
97 |
98 | func (p *Plan) createStripePlan() error {
99 | stripePlanID, stripeProductID, err := stripe.CreatePlan(p.Amount, p.IntervalCount, p.Interval, p.Name, p.Currency)
100 | if stripePlanID != nil && stripeProductID != nil {
101 | p.StripePlanID = *stripePlanID
102 | p.StripeProductID = *stripeProductID
103 | }
104 | return err
105 | }
106 |
107 | func (p *Plan) updateStripePlan() error {
108 | return stripe.UpdatePlan(p.StripeProductID, p.Name)
109 | }
110 |
111 | func (p *Plan) deleteStripePlan() error {
112 | return stripe.DeletePlan(p.StripePlanID, p.StripeProductID)
113 | }
114 |
115 | // BeforeCreate sets the CreatedAt column to the current time
116 | func (p *Plan) String() string {
117 | return fmt.Sprintf(
118 | "ID: %d, Name: %s, Amount: %s, Interval: %d, IntervalCount: %d, Currency: %d",
119 | p.ID,
120 | p.Name,
121 | p.Amount,
122 | p.Interval,
123 | p.IntervalCount,
124 | p.Currency,
125 | )
126 | }
127 |
--------------------------------------------------------------------------------
/site/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Navbar from 'react-bootstrap/Navbar';
3 | import Badge from 'react-bootstrap/Badge';
4 | import Button from 'react-bootstrap/Button';
5 | import Image from 'react-bootstrap/Image';
6 | import Auth from '../service/Auth';
7 | import Link from 'next/link';
8 |
9 | const handleLogout = async () => {
10 | Auth.Logout();
11 | };
12 |
13 | const HeaderBase: React.FC = ({ children }) => {
14 | return (
15 |
16 |
17 |
18 | {children}
19 |
20 | );
21 | };
22 |
23 | const HeaderDashboard = () => {
24 | const [loginStatus, setLoginStatus] = useState('Login');
25 | useEffect(() => {
26 | setLoginStatus(Auth.IsLoggedIn() ? 'Logout' : 'Login');
27 | });
28 |
29 | return (
30 |
31 |
32 |
33 |
34 |
35 | {loginStatus}
36 |
37 |
38 |
46 |
47 | );
48 | };
49 |
50 | const HeaderMain = () => {
51 | const [loginStatus, setLoginStatus] = useState('Login');
52 | const [loginLink, setLoginLink] = useState('Login');
53 | useEffect(() => {
54 | setLoginStatus(Auth.IsLoggedIn() ? 'My Account' : 'Login');
55 | setLoginLink(Auth.IsLoggedIn() ? '/account' : '/login');
56 | });
57 |
58 | return (
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | );
70 | };
71 |
72 | const HeaderBrand = () => {
73 | return (
74 |
75 |
76 |
77 |
78 |
79 | ÉireVPN
80 |
81 | Beta
82 |
83 |
84 |
85 |
86 |
94 |
95 | );
96 | };
97 |
98 | interface NavLinkProps {
99 | href: string;
100 | text: string;
101 | }
102 |
103 | const NavLink: React.FC = ({ href, text }) => {
104 | return (
105 |
106 |
107 |
108 | {text}
109 |
110 |
111 |
119 |
120 | );
121 | };
122 |
123 | export { HeaderDashboard, HeaderMain };
124 |
--------------------------------------------------------------------------------
/site/components/admin/forms/UserPlanCreateForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import dayjs from 'dayjs';
3 | import Form from 'react-bootstrap/Form';
4 | import FormInput from '../../FormInput';
5 | import FormDropdown from '../../FormDropdown';
6 | import FormDatetime from '../../FormDatetime';
7 | import Card from 'react-bootstrap/Card';
8 | import ErrorMessage from '../../ErrorMessage';
9 | import APIError from '../../../interfaces/error';
10 | import Plan from '../../../interfaces/plan';
11 | import ButtonMain from '../../ButtonMain';
12 |
13 | interface UserPlanCreateFormProps {
14 | userid: string;
15 | planlist: Plan[];
16 | error: APIError;
17 | HandleSave: (body: string) => Promise;
18 | }
19 |
20 | const UserPlanCreateForm: React.FC = ({
21 | userid,
22 | planlist,
23 | error,
24 | HandleSave
25 | }) => {
26 | const [planid, setPlanID] = useState('');
27 | const [active, setActive] = useState('');
28 | const [start_date, setStartDate] = useState(new Date().toString());
29 | const [expiry_date, setExpiryDate] = useState(new Date().toString());
30 |
31 | const hasError = !!error;
32 |
33 | let plans = planlist.map((p, i) => {
34 | return {
35 | value: p.id,
36 | name: p.name
37 | };
38 | });
39 |
40 | const handleSaveClick = () => {
41 | HandleSave(
42 | JSON.stringify({
43 | user_id: parseInt(userid),
44 | plan_id: parseInt(planid),
45 | active,
46 | start_date: dayjs(start_date).format('YYYY-MM-DD H:mm'),
47 | expiry_date: dayjs(expiry_date).format('YYYY-MM-DD H:mm')
48 | })
49 | );
50 | };
51 |
52 | return (
53 |
54 |
55 |
56 |
57 | Create User Plan
58 |
59 |
60 |
61 |
62 |
65 |
66 |
67 |
68 |
76 |
77 |
78 |
86 |
87 |
88 |
94 |
95 |
96 |
102 |
103 |
104 |
105 |
106 |
107 | );
108 | };
109 |
110 | export default UserPlanCreateForm;
111 |
--------------------------------------------------------------------------------
/site/components/user/forms/UpdatePasswordForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import ButtonMain from '../../ButtonMain';
4 | import Card from 'react-bootstrap/Card';
5 | import ErrorMessage from '../../ErrorMessage';
6 | import FormInput from '../../FormInput';
7 | import APIError from '../../../interfaces/error';
8 | import SuccessMessage from '../../SuccessMessage';
9 |
10 | interface UpdatePasswordProps {
11 | success: boolean;
12 | error: APIError;
13 | HandleSubmit: (body: string) => Promise;
14 | }
15 |
16 | type TFormEvent = React.FormEvent;
17 |
18 | const UpdatePasswordForm: React.FC = ({ success, error, HandleSubmit }) => {
19 | const [err, setError] = useState();
20 | const [password, setPassword] = useState();
21 | const [confirmPassword, setConfirmPassword] = useState();
22 | const [validated, setValidated] = useState(false);
23 |
24 | const handleSubmit = (event: TFormEvent) => {
25 | event.stopPropagation();
26 | event.preventDefault();
27 | const form = event.currentTarget;
28 | // this sets form validation feedback to visible
29 | setValidated(true);
30 | if (form.checkValidity() === true) {
31 | if (password == confirmPassword) {
32 | setError(undefined);
33 | HandleSubmit(JSON.stringify({ password }));
34 | } else {
35 | setError({
36 | status: 0,
37 | code: '',
38 | title: '',
39 | detail: 'Passwords do not match'
40 | });
41 | setValidated(false);
42 | }
43 | }
44 | };
45 |
46 | if (!success) {
47 | return (
48 |
49 |
50 |
51 | );
52 | }
53 |
54 | if (error) {
55 | return (
56 |
57 |
58 |
59 | );
60 | }
61 |
62 | return (
63 |
64 |
65 |
66 |
76 |
86 |
87 |
88 |
98 |
99 |
100 |
101 |
102 |
103 | );
104 | };
105 |
106 | export default UpdatePasswordForm;
107 |
--------------------------------------------------------------------------------
/site/components/admin/forms/SettingsForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import Card from 'react-bootstrap/Card';
4 | import Settings from '../../../interfaces/settings';
5 | import ButtonMain from '../../ButtonMain';
6 | import ErrorMessage from '../../ErrorMessage';
7 | import APIError from '../../../interfaces/error';
8 | import FormDropdown from '../../FormDropdown';
9 | import FormInput from '../../FormInput';
10 | import SuccessMessage from '../../SuccessMessage';
11 |
12 | interface SettingsFormProps {
13 | settings: Settings;
14 | error: APIError;
15 | success: boolean;
16 | HandleSave: (body: string) => Promise;
17 | }
18 |
19 | const SettingsForm: React.FC = ({ settings, error, success, HandleSave }) => {
20 | const hasError = !!error;
21 | const [enableCsrf, setEnableCsrf] = useState(settings.enableCsrf);
22 | const [enableSubscriptions, setEnableSubs] = useState(settings.enableSubscriptions);
23 | const [enableAuth, setEnableAuth] = useState(settings.enableAuth);
24 | const [enableStripe, setEnableStripe] = useState(settings.enableStripe);
25 | const [allowedOrigins, setAllowedOrigins] = useState(settings.allowedOrigins.join(',\n'));
26 |
27 | const handleSaveClick = () => {
28 | HandleSave(
29 | JSON.stringify({
30 | enableCsrf,
31 | enableSubscriptions,
32 | enableAuth,
33 | enableStripe,
34 | allowedOrigins: allowedOrigins.replace(/\r?\n|\r/g, '').split(',')
35 | })
36 | );
37 | };
38 |
39 | return (
40 |
41 |
42 |
43 |
44 | Edit Settings
45 |
46 |
47 |
48 |
49 |
50 |
51 |
53 |
60 |
67 |
68 |
69 |
76 |
83 |
84 |
85 |
94 |
95 |
96 |
97 |
98 |
99 | );
100 | };
101 |
102 | export default SettingsForm;
103 |
--------------------------------------------------------------------------------
/api/test/main_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "eirevpn/api/config"
5 | "eirevpn/api/logger"
6 | "eirevpn/api/router"
7 | "flag"
8 | "net/http"
9 | "net/http/httptest"
10 | "os"
11 | "testing"
12 | "time"
13 | )
14 |
15 | var logging bool
16 |
17 | func TestMain(m *testing.M) {
18 |
19 | flag.BoolVar(&logging, "logging", false, "enable logging")
20 | flag.Parse()
21 |
22 | config.Init("../config.test.yaml")
23 |
24 | InitDB()
25 | r = router.Init(logging)
26 | logger.Init(logging)
27 | code := m.Run()
28 |
29 | os.Exit(code)
30 | }
31 |
32 | func TestAuthTokens(t *testing.T) {
33 | conf := config.Load()
34 |
35 | makeRequest := func(t *testing.T, authToken, refreshToken, csrfToken string) *httptest.ResponseRecorder {
36 | t.Helper()
37 | w := httptest.NewRecorder()
38 | req, _ := http.NewRequest("GET", "/api/private/servers", nil)
39 | if authToken != "" {
40 | req.AddCookie(&http.Cookie{Name: conf.App.AuthCookieName, Value: authToken, Expires: time.Now().Add(time.Minute * 5)})
41 | }
42 | if refreshToken != "" {
43 | req.AddCookie(&http.Cookie{Name: conf.App.RefreshCookieName, Value: refreshToken, Expires: time.Now().Add(time.Minute * 5)})
44 | }
45 | req.Header.Set("X-CSRF-Token", csrfToken)
46 | r.ServeHTTP(w, req)
47 | return w
48 | }
49 |
50 | t.Run("Successful Authentification", func(t *testing.T) {
51 | user := CreateUser()
52 | _ = CreateServer()
53 | authToken, refreshToken, csrfToken := GetTokens(user)
54 | want := 200
55 | got := makeRequest(t, authToken, refreshToken, csrfToken)
56 | assertCorrectStatus(t, want, got.Code)
57 | CreateCleanDB()
58 | })
59 |
60 | t.Run("No Auth cookie", func(t *testing.T) {
61 | user := CreateUser()
62 | _, refreshToken, csrfToken := GetTokens(user)
63 | wantStatus := 403
64 | wantCode := "AUTHCOOKMISS"
65 | resp := makeRequest(t, "", refreshToken, csrfToken)
66 | apiErr := bindError(resp)
67 | assertCorrectStatus(t, wantStatus, apiErr.Status)
68 | assertCorrectCode(t, wantCode, apiErr.Code)
69 | CreateCleanDB()
70 | })
71 |
72 | t.Run("No Refresh cookie", func(t *testing.T) {
73 | user := CreateUser()
74 | auth, _, csrfToken := GetTokens(user)
75 | wantStatus := 403
76 | wantCode := "REFCOOKMISS"
77 | resp := makeRequest(t, auth+"1", "", csrfToken)
78 | apiErr := bindError(resp)
79 | assertCorrectStatus(t, wantStatus, apiErr.Status)
80 | assertCorrectCode(t, wantCode, apiErr.Code)
81 | CreateCleanDB()
82 | })
83 |
84 | t.Run("Token invalid", func(t *testing.T) {
85 | user := CreateUser()
86 | authToken, refreshToken, csrfToken := GetTokens(user)
87 | wantStatus := 403
88 | wantCode := "TOKENINVALID"
89 | resp := makeRequest(t, authToken+"p", refreshToken+"p", csrfToken)
90 | apiErr := bindError(resp)
91 | assertCorrectStatus(t, wantStatus, apiErr.Status)
92 | assertCorrectCode(t, wantCode, apiErr.Code)
93 | CreateCleanDB()
94 | })
95 |
96 | t.Run("Invalid identifier", func(t *testing.T) {
97 | user := CreateUser()
98 | authToken, refreshToken, csrfToken := GetTokens(user)
99 | DeleteIdentifier(user)
100 | wantStatus := 403
101 | wantCode := "INVIDENTIFIER"
102 | resp := makeRequest(t, authToken, refreshToken, csrfToken)
103 | apiErr := bindError(resp)
104 | assertCorrectStatus(t, wantStatus, apiErr.Status)
105 | assertCorrectCode(t, wantCode, apiErr.Code)
106 | CreateCleanDB()
107 | })
108 |
109 | t.Run("CSRF Invalid", func(t *testing.T) {
110 | user := CreateUser()
111 | authToken, refreshToken, _ := GetTokens(user)
112 | wantStatus := 403
113 | wantCode := "CSRFTOKEN"
114 | resp := makeRequest(t, authToken, refreshToken, "")
115 | apiErr := bindError(resp)
116 | assertCorrectStatus(t, wantStatus, apiErr.Status)
117 | assertCorrectCode(t, wantCode, apiErr.Code)
118 | CreateCleanDB()
119 | })
120 | }
121 |
--------------------------------------------------------------------------------
/site/components/admin/forms/PlanEditForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import Plan from '../../../interfaces/plan';
4 | import Card from 'react-bootstrap/Card';
5 | import dayjs from 'dayjs';
6 | import APIError from '../../../interfaces/error';
7 | import FormInput from '../../FormInput';
8 | import SuccessMessage from '../../SuccessMessage';
9 | import ErrorMessage from '../../ErrorMessage';
10 | import ButtonMain from '../../ButtonMain';
11 |
12 | interface PlanEditFormProps {
13 | plan: Plan;
14 | error: APIError;
15 | success: boolean;
16 | HandleSave: (body: string) => Promise;
17 | HandleDelete: () => Promise;
18 | }
19 |
20 | const UserForm: React.FC = ({
21 | plan,
22 | error,
23 | success,
24 | HandleSave,
25 | HandleDelete
26 | }) => {
27 | const hasError = !!error;
28 | const [name, setName] = useState(plan.name);
29 |
30 | const handleSaveClick = () => {
31 | HandleSave(JSON.stringify({ name }));
32 | };
33 |
34 | const handleDeleteClick = () => {
35 | HandleDelete();
36 | };
37 |
38 | return (
39 |
40 |
41 |
42 |
43 | Edit Plan
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
54 |
62 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
95 |
101 |
102 |
103 |
104 |
105 |
106 | );
107 | };
108 |
109 | export default UserForm;
110 |
--------------------------------------------------------------------------------
/site/components/user/forms/ChangePasswordForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Form from 'react-bootstrap/Form';
3 | import ButtonMain from '../../ButtonMain';
4 | import Card from 'react-bootstrap/Card';
5 | import ErrorMessage from '../../ErrorMessage';
6 | import FormInput from '../../FormInput';
7 |
8 | interface ChangePasswordFormProps {
9 | success: boolean;
10 | HandleSave: (body: string) => Promise;
11 | }
12 |
13 | type TFormEvent = React.FormEvent;
14 |
15 | const ChangePasswordForm: React.FC = ({ success, HandleSave }) => {
16 | const [err, setError] = useState();
17 | const [current_password, setCurrentPassword] = useState();
18 | const [new_password, setNewPassword] = useState();
19 | const [confirmNewPassword, setConfirmNewPassword] = useState();
20 | const [validated, setValidated] = useState(false);
21 |
22 | // if saved successfully clear password fields
23 | useEffect(() => {
24 | if (success) {
25 | setCurrentPassword('');
26 | setNewPassword('');
27 | setConfirmNewPassword('');
28 | setValidated(false);
29 | }
30 | });
31 |
32 | const handleSubmit = (event: TFormEvent) => {
33 | event.stopPropagation();
34 | event.preventDefault();
35 | const form = event.currentTarget;
36 | // this sets form validation feedback to visible
37 | setValidated(true);
38 | if (form.checkValidity() === true) {
39 | if (new_password == confirmNewPassword) {
40 | setError(undefined);
41 | HandleSave(JSON.stringify({ current_password, new_password }));
42 | } else {
43 | setError({
44 | status: 0,
45 | code: '',
46 | title: '',
47 | detail: 'Passwords do not match'
48 | });
49 | setValidated(false);
50 | }
51 | }
52 | };
53 |
54 | return (
55 |
56 |
57 |
58 |
67 |
77 |
78 |
79 |
89 |
90 |
91 |
101 |
102 |
103 |
104 |
105 |
106 | );
107 | };
108 |
109 | export default ChangePasswordForm;
110 |
--------------------------------------------------------------------------------
/api/util/jwt/jwt.go:
--------------------------------------------------------------------------------
1 | package jwt
2 |
3 | import (
4 | "eirevpn/api/config"
5 | "eirevpn/api/models"
6 | "fmt"
7 | "time"
8 |
9 | jwt_lib "github.com/dgrijalva/jwt-go"
10 | )
11 |
12 | type JWTClaims struct {
13 | UserID uint `json:"user_id"`
14 | CSRF string `json:"csrf"`
15 | SessionIdentifier string `json:"Identifier"`
16 | jwt_lib.StandardClaims
17 | }
18 |
19 | // Tokens creates a jwt token from the user ID
20 | func Tokens(usersession models.UserAppSession) (string, string, string, error) {
21 | conf := config.Load()
22 | authExpiry := time.Hour * time.Duration(conf.App.AuthTokenExpiry)
23 | refreshExpiry := time.Hour * time.Duration(conf.App.AuthTokenExpiry)
24 |
25 | // Set claims
26 | csrfTokenClaims := JWTClaims{
27 | StandardClaims: jwt_lib.StandardClaims{
28 | ExpiresAt: time.Now().Add(authExpiry).Unix(),
29 | },
30 | }
31 | // Create csrf token
32 | csrfToken := jwt_lib.NewWithClaims(jwt_lib.GetSigningMethod("HS256"), csrfTokenClaims)
33 | csrfTokenString, err := csrfToken.SignedString([]byte(conf.App.JWTSecret))
34 |
35 | authTokenClaims := JWTClaims{
36 | UserID: usersession.UserID,
37 | CSRF: csrfTokenString,
38 | StandardClaims: jwt_lib.StandardClaims{
39 | ExpiresAt: time.Now().Add(authExpiry).Unix(),
40 | },
41 | }
42 | // Create refresh token
43 | authToken := jwt_lib.NewWithClaims(jwt_lib.GetSigningMethod("HS256"), authTokenClaims)
44 | // Sign and get the complete encoded token as a string
45 | authTokenString, err := authToken.SignedString([]byte(conf.App.JWTSecret))
46 |
47 | refreshTokenClaims := JWTClaims{
48 | UserID: usersession.UserID,
49 | SessionIdentifier: usersession.Identifier,
50 | StandardClaims: jwt_lib.StandardClaims{
51 | ExpiresAt: time.Now().Add(refreshExpiry).Unix(),
52 | },
53 | }
54 | // Create refresh token
55 | refreshToken := jwt_lib.NewWithClaims(jwt_lib.GetSigningMethod("HS256"), refreshTokenClaims)
56 | // Sign and get the complete encoded token as a string
57 | refreshTokenString, err := refreshToken.SignedString([]byte(conf.App.JWTSecret))
58 |
59 | if err != nil {
60 | return "", "", "", err
61 | }
62 |
63 | return authTokenString, refreshTokenString, csrfTokenString, nil
64 | }
65 |
66 | // PasswordResetToken creates a one time us jwt token from the users old password
67 | func PasswordResetToken(id string) (string, error) {
68 | conf := config.Load()
69 | // Create the token
70 | token := jwt_lib.New(jwt_lib.GetSigningMethod("HS256"))
71 | // Set some claims
72 | token.Claims = jwt_lib.MapClaims{
73 | "Id": id,
74 | "exp": time.Now().Add(time.Hour * 1).Unix(),
75 | }
76 | // Sign and get the complete encoded token as a string
77 | tokenString, err := token.SignedString([]byte(conf.App.JWTSecret))
78 | if err != nil {
79 | return "", err
80 | }
81 | return tokenString, nil
82 | }
83 |
84 | // ValidateAuthToken todo
85 | func ValidateToken(refreshToken string) (*JWTClaims, error) {
86 | conf := config.Load()
87 | token, err := jwt_lib.ParseWithClaims(refreshToken, &JWTClaims{}, func(token *jwt_lib.Token) (interface{}, error) {
88 | // Don't forget to validate the alg is what you expect:
89 | if _, ok := token.Method.(*jwt_lib.SigningMethodHMAC); !ok {
90 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
91 | }
92 | return []byte(conf.App.JWTSecret), nil
93 | })
94 | claims, ok := token.Claims.(*JWTClaims)
95 | if ok && token.Valid {
96 | return claims, nil
97 | }
98 | return claims, err
99 | }
100 |
101 | // ValidateString Validate token string
102 | func ValidateString(token string) (bool, error) {
103 | conf := config.Load()
104 | _, err := jwt_lib.Parse(token, func(token *jwt_lib.Token) (interface{}, error) {
105 | // Don't forget to validate the alg is what you expect:
106 | if _, ok := token.Method.(*jwt_lib.SigningMethodHMAC); !ok {
107 | return false, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
108 | }
109 | return []byte(conf.App.JWTSecret), nil
110 | })
111 | if err != nil {
112 | return false, err
113 | }
114 | return true, nil
115 | }
116 |
--------------------------------------------------------------------------------