├── 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 | ![landing](https://i.imgur.com/zaaLRsF.png) 23 | ### Create an account 24 | ![create account](https://i.imgur.com/nJ1ZEem.png) 25 | ### User account page 26 | ![user account](https://i.imgur.com/N59hX9L.png) 27 | ### Change details 28 | ![user change details](https://i.imgur.com/L6M9b3p.png) 29 | ### Downloads 30 | ![downloads](https://i.imgur.com/vvl52Fr.png) 31 | ### Contact Support 32 | ![support](https://i.imgur.com/PnOvHI9.png) 33 | ### Admin panel 34 | ![admin users](https://i.imgur.com/aOjqPif.png) 35 | ### Admin panel 36 | ![admin settings](https://i.imgur.com/nUkR4oK.png) 37 | ### Extension 38 | ![extension instructions](https://i.imgur.com/dgVpfXM.png) 39 | ![extension login](https://i.imgur.com/6BrvmEx.png) 40 | ![ext connected](https://i.imgur.com/nBJmjel.png) 41 | ![ext list](https://i.imgur.com/hiHCFII.png) 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 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {connections.map(c => ( 29 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | ))} 41 | 42 |
#CreatedUser IDServer IDCountry
{c.id} 32 | {dayjs(c.createdAt) 33 | .format('DD-MM-YYYY H:mm') 34 | .toString()} 35 | {c.user_id}{c.server_id}{c.server_country}
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 |
7 |
    8 |
  • 9 | 10 | Users 11 | 12 |
  • 13 |
  • 14 | 15 | User Plans 16 | 17 |
  • 18 |
  • 19 | 20 | Plans 21 | 22 |
  • 23 |
  • 24 | 25 | Servers 26 | 27 |
  • 28 |
  • 29 | 30 | Connections 31 | 32 |
  • 33 |
  • 34 | 35 | Settings 36 | 37 |
  • 38 | 50 |
51 |
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 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {users?.map(user => ( 30 | Router.push('/admin/users/[id]', '/admin/users/' + user.id)} 33 | > 34 | 35 | 40 | 41 | 42 | 43 | 44 | 45 | ))} 46 | 47 |
#CreatedFirst NameLast NameEmailStripe ID
{user.id} 36 | {dayjs(user.createdAt) 37 | .format('DD-MM-YYYY H:mm') 38 | .toString()} 39 | {user.firstname}{user.lastname}{user.email}{user.stripe_customer_id}
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 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {plans.map(plan => ( 32 | Router.push('/admin/plans/[id]', '/admin/plans/' + plan.id)} 35 | > 36 | 37 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ))} 50 | 51 |
#CreatedNameCostIntervalInterval CountStripe Plan IDStripe Product ID
{plan.id} 38 | {dayjs(plan.createdAt) 39 | .format('DD-MM-YYYY H:mm') 40 | .toString()} 41 | {plan.name}{plan.amount}{plan.interval}{plan.interval_count}{plan.stripe_plan_id}{plan.stripe_product_id}
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 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {servers.map(server => ( 33 | Router.push('/admin/servers/[id]', '/admin/servers/' + server.id)} 36 | > 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ))} 52 | 53 |
#CreatedCountryCodeTypeIPPortUsernamePassword
{server.id} 39 | {dayjs(server.createdAt) 40 | .format('DD-MM-YYYY H:mm') 41 | .toString()} 42 | {server.country}{server.country_code}{server.type}{server.ip}{server.port}{server.username}{server.password}
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 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {userplans.map(up => ( 31 | 34 | Router.push('/admin/userplans/[user_id]', '/admin/userplans/' + up.user_id) 35 | } 36 | > 37 | 38 | 43 | 44 | 45 | 46 | 51 | 56 | 57 | ))} 58 | 59 |
#CreatedUser IDPlan IDActiveStart DateEnd Date
{up.id} 39 | {dayjs(up.createdAt) 40 | .format('DD-MM-YYYY H:mm') 41 | .toString()} 42 | {up.user_id}{up.plan_id}{up.active.toString()} 47 | {dayjs(up.start_date) 48 | .format('DD-MM-YYYY H:mm') 49 | .toString()} 50 | 52 | {dayjs(up.expiry_date) 53 | .format('DD-MM-YYYY H:mm') 54 | .toString()} 55 |
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 |
33 | 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 |
37 | 38 | 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 |
44 | 45 | 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 |
handleSubmit(e)}> 48 | 49 | Forgotten Password 50 |
51 | 52 |
53 |
54 |

Please enter your email and we will send you a reset link

55 | 56 | 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 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 33 |

Downloads

34 | 35 |
36 | 37 | 38 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 |
53 | 54 | 55 | 60 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 70 |
71 |
72 |
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 |
handleSubmit(e)}> 47 | 48 | Contact Support 49 |
50 | 51 |
52 |
53 | 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 |
handleSubmit(e)}> 45 | 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 |
39 |
handleSubmit(e)}> 40 | 41 | 42 | 43 | Create Server 44 |
45 | 48 |
49 |
50 | 51 | 52 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Image 73 | 74 | 75 | 76 | 77 | 78 | 79 |
80 |
81 |
82 |
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 |
63 | 64 | 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 |
handleSubmit(e)}> 67 | 68 | Reset Password 69 |
70 | 71 |
72 |
73 |

Please enter your new password

74 | 75 | 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 |
52 | 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 |
53 | 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 |
handleSubmit(e)}> 59 | 60 | Change Password 61 |
62 | 63 |
64 |
65 | 66 | 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 | --------------------------------------------------------------------------------