mirror of https://github.com/wg-easy/wg-easy
26 changed files with 316 additions and 1460 deletions
@ -0,0 +1,11 @@ |
|||||
|
{ |
||||
|
"[javascript]": { |
||||
|
"editor.formatOnSave": true, |
||||
|
"editor.formatOnType": true |
||||
|
}, |
||||
|
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false, |
||||
|
"javascript.format.semicolons": "insert", |
||||
|
"editor.tabSize": 2, |
||||
|
"editor.indentSize": "tabSize", |
||||
|
"editor.detectIndentation": false |
||||
|
} |
||||
@ -0,0 +1,96 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
const path = require('path'); |
||||
|
const { v4: uuidv4 } = require('uuid'); |
||||
|
const debug = require('debug')('Database'); |
||||
|
const bcrypt = require('bcryptjs'); |
||||
|
|
||||
|
const ServerError = require('../ServerError'); |
||||
|
const DatabaseInterface = require('./AbstractDatabase'); |
||||
|
|
||||
|
const { WG_PATH } = require('../../config'); |
||||
|
|
||||
|
/** * |
||||
|
* |
||||
|
* @typedef {Object} Role |
||||
|
* @property {string} ADMIN - Represents an ADMIN user |
||||
|
* |
||||
|
* @typedef {Object} User |
||||
|
* @property {number} id - The unique identifier of user |
||||
|
* @property {string} username - The username of user |
||||
|
* @property {string} password - The password of user |
||||
|
* @property {Array<Role[keyof Role]>} roles - All roles of user |
||||
|
* |
||||
|
* @typedef {Object} Model |
||||
|
* @property {Array<User>} users |
||||
|
*/ |
||||
|
|
||||
|
/** * |
||||
|
* @type {Role} |
||||
|
*/ |
||||
|
const Role = { |
||||
|
ADMIN: 'admin', |
||||
|
}; |
||||
|
|
||||
|
/** * |
||||
|
* @type {Model} |
||||
|
*/ |
||||
|
const Model = {}; |
||||
|
|
||||
|
module.exports = class Lowdb extends DatabaseInterface { |
||||
|
|
||||
|
constructor() { |
||||
|
super(); |
||||
|
this.dbPath = path.join(WG_PATH, 'db.json'); |
||||
|
this.db = null; |
||||
|
} |
||||
|
|
||||
|
async initDb() { |
||||
|
debug('Loading database...'); |
||||
|
// lowdb is esm module
|
||||
|
// eslint-disable-next-line node/no-unsupported-features/es-syntax, import/no-unresolved, node/no-missing-import
|
||||
|
const { JSONFilePreset } = await import('lowdb/node'); |
||||
|
this.db = await JSONFilePreset(this.dbPath, Model); |
||||
|
await this.db.read(); |
||||
|
|
||||
|
if (!this.db.data.users) { |
||||
|
this.db.data = { users: [] }; |
||||
|
const defaultPassword = process.env.PASSWORD_HASH || await bcrypt.hash(process.env.PASSWORD || 'admin', 12); |
||||
|
const adminUser = { |
||||
|
id: uuidv4(), |
||||
|
username: 'admin', |
||||
|
password: defaultPassword, |
||||
|
roles: [Role.ADMIN], |
||||
|
}; |
||||
|
this.db.data.users.push(adminUser); |
||||
|
await this.db.write(); |
||||
|
debug(`Created new database at ${this.dbPath}`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async comparePassword(username, password) { |
||||
|
debug('Compare password'); |
||||
|
const user = this.db.data.users.find((u) => u.username === username); |
||||
|
if (!user) { |
||||
|
throw new ServerError('User not found'); |
||||
|
} |
||||
|
const isMatch = await bcrypt.compare(password, user.password); |
||||
|
return isMatch; |
||||
|
} |
||||
|
|
||||
|
async updatePassword(username, oldPassword, newPassword) { |
||||
|
debug('Update password'); |
||||
|
const user = this.db.data.users.find((u) => u.username === username); |
||||
|
if (!user) { |
||||
|
throw new ServerError('User not found'); |
||||
|
} |
||||
|
const isMatch = await bcrypt.compare(oldPassword, user.password); |
||||
|
if (!isMatch) { |
||||
|
throw new ServerError('Old password is incorrect'); |
||||
|
} |
||||
|
// lowdb is atomic
|
||||
|
user.password = await bcrypt.hash(newPassword, 12); |
||||
|
await this.db.write(); |
||||
|
} |
||||
|
|
||||
|
}; |
||||
@ -1,101 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
const path = require('path'); |
|
||||
const debug = require('debug')('Database'); |
|
||||
const bcrypt = require('bcryptjs'); |
|
||||
const sqlite = require('sqlite3'); |
|
||||
|
|
||||
const DatabaseInterface = require('./Interface'); |
|
||||
const ServerError = require('../ServerError'); |
|
||||
|
|
||||
const { WG_PATH } = require('../../config'); |
|
||||
|
|
||||
module.exports = class SQLiteDatabase extends DatabaseInterface { |
|
||||
|
|
||||
constructor() { |
|
||||
super(); |
|
||||
this.dbPath = path.join(WG_PATH, 'db.sqlite'); |
|
||||
this.provider = 'SQLite'; |
|
||||
this.db = null; |
|
||||
} |
|
||||
|
|
||||
async initDb() { |
|
||||
return new Promise((resolve, reject) => { |
|
||||
this.db = new sqlite.Database(this.dbPath, async (err) => { |
|
||||
if (err) { |
|
||||
reject(new ServerError('Could not initialize the database')); |
|
||||
} |
|
||||
debug(`Connected to the SQLite database at ${this.dbPath}`); |
|
||||
await this.__setupDatabase(); |
|
||||
resolve(true); |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
async __setupDatabase() { |
|
||||
const createAdminTableQuery = ` |
|
||||
CREATE TABLE IF NOT EXISTS admin ( |
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|
||||
username TEXT NOT NULL UNIQUE, |
|
||||
password TEXT NOT NULL |
|
||||
) |
|
||||
`;
|
|
||||
|
|
||||
this.db.run(createAdminTableQuery, (err) => { |
|
||||
if (err) { |
|
||||
throw new ServerError('Could not create admin table'); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
/* Default username is 'admin' */ |
|
||||
|
|
||||
this.db.get('SELECT * FROM admin WHERE username = ?', ['admin'], async (err, row) => { |
|
||||
if (err) { |
|
||||
throw new ServerError('Could not query admin table'); |
|
||||
} |
|
||||
|
|
||||
if (!row) { |
|
||||
const hashedPassword = await bcrypt.hash('admin', 12); |
|
||||
this.db.run('INSERT INTO admin (username, password) VALUES (?, ?)', ['admin', hashedPassword], (err) => { |
|
||||
if (err) { |
|
||||
throw new ServerError('Could not create initial admin user'); |
|
||||
} |
|
||||
debug('Init admin username:password "admin:admin"'); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
async comparePassword(username, password) { |
|
||||
return new Promise((resolve, reject) => { |
|
||||
this.db.get('SELECT password FROM admin WHERE username = ?', [username], async (err, row) => { |
|
||||
if (err || !row) { |
|
||||
return reject(new ServerError('Could not query admin table')); |
|
||||
} |
|
||||
|
|
||||
const isMatch = await bcrypt.compare(password, row.password); |
|
||||
resolve(isMatch); |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
async updatePassword(username, oldPassword, newPassword) { |
|
||||
const isMatch = await this.comparePassword(username, oldPassword); |
|
||||
if (!isMatch) { |
|
||||
throw new ServerError('Invalid credentials', 401); |
|
||||
} |
|
||||
|
|
||||
const hashedPassword = await bcrypt.hash(newPassword, 10); |
|
||||
|
|
||||
return new Promise((resolve, reject) => { |
|
||||
this.db.run('UPDATE admin SET password = ? WHERE username = ?', [hashedPassword, username], (err) => { |
|
||||
if (err) { |
|
||||
return reject(new ServerError('Could not update password')); |
|
||||
} |
|
||||
|
|
||||
resolve(true); |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
}; |
|
||||
File diff suppressed because it is too large
@ -1,13 +1,14 @@ |
|||||
'use strict'; |
'use strict'; |
||||
|
|
||||
const SQLiteDatabase = require('../lib/database/SQLite'); |
const Lowdb = require('../lib/database/Lowdb'); |
||||
// const MySqlDatabase = require('../lib/database/MySql');
|
// const MySqlDatabase = require('../lib/database/MySql');
|
||||
|
|
||||
/* |
/* |
||||
module.exports = { |
module.exports = { |
||||
SQLiteDatabase: new SQLiteDatabase(), |
SQLiteDatabase: new SQLiteDatabase(), |
||||
|
MySqlDatabase: new MySqlDatabase(), |
||||
... |
... |
||||
}; |
}; |
||||
*/ |
*/ |
||||
|
|
||||
module.exports = new SQLiteDatabase(); |
module.exports = new Lowdb(); |
||||
|
|||||
@ -0,0 +1,54 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
// Import needed libraries
|
||||
|
import bcrypt from 'bcryptjs'; |
||||
|
|
||||
|
// Function to generate hash
|
||||
|
const generateHash = async (password) => { |
||||
|
try { |
||||
|
const salt = await bcrypt.genSalt(12); |
||||
|
const hash = await bcrypt.hash(password, salt); |
||||
|
// eslint-disable-next-line no-console
|
||||
|
console.log(`PASSWORD_HASH='${hash}'`); |
||||
|
} catch (error) { |
||||
|
throw new Error(`Failed to generate hash : ${error}`); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Function to compare password with hash
|
||||
|
const comparePassword = async (password, hash) => { |
||||
|
try { |
||||
|
const match = await bcrypt.compare(password, hash); |
||||
|
if (match) { |
||||
|
// eslint-disable-next-line no-console
|
||||
|
console.log('Password matches the hash !'); |
||||
|
} else { |
||||
|
// eslint-disable-next-line no-console
|
||||
|
console.log('Password does not match the hash.'); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
throw new Error(`Failed to compare password and hash : ${error}`); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
(async () => { |
||||
|
try { |
||||
|
// Retrieve command line arguments
|
||||
|
const args = process.argv.slice(2); // Ignore the first two arguments
|
||||
|
if (args.length > 2) { |
||||
|
throw new Error('Usage : wgpw YOUR_PASSWORD [HASH]'); |
||||
|
} |
||||
|
|
||||
|
const [password, hash] = args; |
||||
|
if (password && hash) { |
||||
|
await comparePassword(password, hash); |
||||
|
} else if (password) { |
||||
|
await generateHash(password); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
// eslint-disable-next-line no-console
|
||||
|
console.error(error); |
||||
|
// eslint-disable-next-line no-process-exit
|
||||
|
process.exit(1); |
||||
|
} |
||||
|
})(); |
||||
@ -0,0 +1,5 @@ |
|||||
|
#!/bin/sh |
||||
|
# This script is intended to be run only inside a docker container, not on the development host machine |
||||
|
set -e |
||||
|
# proxy command |
||||
|
node /app/wgpw.mjs "$@" |
||||
@ -1,2 +0,0 @@ |
|||||
(function(e,t){"object"===typeof exports&&"object"===typeof module?module.exports=t():"function"===typeof define&&define.amd?define([],t):"object"===typeof exports?exports["VueDashboard"]=t():e["VueDashboard"]=t()})("undefined"!==typeof self?self:this,(()=>(()=>{"use strict";var e={};(()=>{e.d=(t,r)=>{for(var s in r)e.o(r,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:r[s]})}})(),(()=>{e.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)})(),(()=>{e.p=""})();var t={};if(e.d(t,{default:()=>c}),"undefined"!==typeof window){var r=window.document.currentScript,s=r&&r.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);s&&(e.p=s[1])}var o=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"w-full shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden"},[r("div",{staticClass:"flex flex-row flex-auto items-center p-3 px-5 border-b-2 border-neutral-500/50 dark:border-neutral-600"},[r("div",{staticClass:"flex-grow"},[r("p",{staticClass:"text-2xl font-medium dark:text-neutral-200"},[e._v(e._s(e.$t("dashboard")))])])]),r("div",{staticClass:"container p-2 flex flex-col md:items-center py-8"},[r("form",{staticClass:"w-full md:w-[75%]",on:{submit:e.updatePassword}},[r("div",{staticClass:"mb-4"},[r("label",{staticClass:"block text-sm font-medium dark:text-neutral-200 mb-4",attrs:{for:"currentPassword"}},[e._v(" "+e._s(e.$t("currentPassword"))+" ")]),r("input",{directives:[{name:"model",rawName:"v-model",value:e.currentPassword,expression:"currentPassword"}],staticClass:"outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full",attrs:{id:"currentPassword",type:"password",placeholder:"Current Password",required:""},domProps:{value:e.currentPassword},on:{input:function(t){t.target.composing||(e.currentPassword=t.target.value)}}})]),r("div",{staticClass:"mb-4"},[r("label",{staticClass:"block text-sm font-medium dark:text-neutral-200 mb-4",attrs:{for:"newPassword"}},[e._v(" "+e._s(e.$t("newPassword"))+" ")]),r("input",{directives:[{name:"model",rawName:"v-model",value:e.newPassword,expression:"newPassword"}],staticClass:"outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full",attrs:{id:"newPassword",type:"password",placeholder:"New Password",required:""},domProps:{value:e.newPassword},on:{input:function(t){t.target.composing||(e.newPassword=t.target.value)}}})]),r("div",{staticClass:"mb-4"},[r("label",{staticClass:"block text-sm font-medium dark:text-neutral-200 mb-4",attrs:{for:"confirmNewPassword"}},[e._v(" "+e._s(e.$t("confirmNewPassword"))+" ")]),r("input",{directives:[{name:"model",rawName:"v-model",value:e.confirmNewPassword,expression:"confirmNewPassword"}],staticClass:"outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full",attrs:{id:"confirmNewPassword",type:"password",placeholder:"Confirm New Password",required:""},domProps:{value:e.confirmNewPassword},on:{input:function(t){t.target.composing||(e.confirmNewPassword=t.target.value)}}})]),r("button",{staticClass:"bg-neutral-400 text-white p-2 rounded-md w-full hover:bg-red-800 transition",attrs:{type:"submit"}},[e._v(" "+e._s(e.$t("updatePassword"))+" ")])])])])},a=[];const n={name:"Dashboard",data(){return{currentPassword:"",newPassword:"",confirmNewPassword:""}},methods:{async updatePassword(e){e.preventDefault(),this.newPassword===this.confirmNewPassword?this.api.updatePassword({username:"admin",oldPassword:this.currentPassword,newPassword:this.newPassword}).then((e=>{this.currentPassword="",this.newPassword="",this.confirmNewPassword="",alert("Password updated")})).catch((e=>{alert(e.message||e.toString())})):alert("Password Mismatch")}},mounted(){this.api=new API}},d=n;function i(e,t,r,s,o,a,n,d){var i,l="function"===typeof e?e.options:e;if(t&&(l.render=t,l.staticRenderFns=r,l._compiled=!0),s&&(l.functional=!0),a&&(l._scopeId="data-v-"+a),n?(i=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(n)},l._ssrRegister=i):o&&(i=d?function(){o.call(this,(l.functional?this.parent:this).$root.$options.shadowRoot)}:o),i)if(l.functional){l._injectStyles=i;var u=l.render;l.render=function(e,t){return i.call(t),u(e,t)}}else{var c=l.beforeCreate;l.beforeCreate=c?[].concat(c,i):[i]}return{exports:e,options:l}}var l=i(d,o,a,!1,null,null,null);const u=l.exports,c=u;return t=t["default"],t})())); |
|
||||
//# sourceMappingURL=vue-dashboard.umd.min.js.map
|
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,2 @@ |
|||||
|
(function(e,t){"object"===typeof exports&&"object"===typeof module?module.exports=t():"function"===typeof define&&define.amd?define([],t):"object"===typeof exports?exports["VueSettings"]=t():e["VueSettings"]=t()})("undefined"!==typeof self?self:this,(()=>(()=>{"use strict";var e={};(()=>{e.d=(t,s)=>{for(var r in s)e.o(s,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:s[r]})}})(),(()=>{e.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)})(),(()=>{e.p=""})();var t={};if(e.d(t,{default:()=>c}),"undefined"!==typeof window){var s=window.document.currentScript,r=s&&s.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);r&&(e.p=r[1])}var o=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"w-full shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden"},[s("div",{staticClass:"flex flex-row flex-auto items-center p-3 px-5 border-b-2 border-neutral-500/50 dark:border-neutral-600"},[s("div",{staticClass:"flex-grow"},[s("p",{staticClass:"text-2xl font-medium dark:text-neutral-200"},[e._v(e._s(e.$t("settings")))])])]),s("div",{staticClass:"container p-2 flex flex-col md:items-center py-8"},[s("form",{staticClass:"w-full md:w-[75%]",on:{submit:e.updatePassword}},[s("div",{staticClass:"mb-4"},[s("label",{staticClass:"block text-sm font-medium dark:text-neutral-200 mb-4",attrs:{for:"currentPassword"}},[e._v(" "+e._s(e.$t("currentPassword"))+" ")]),s("input",{directives:[{name:"model",rawName:"v-model",value:e.currentPassword,expression:"currentPassword"}],staticClass:"outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full",attrs:{id:"currentPassword",type:"password",placeholder:e.$t("currentPassword"),required:""},domProps:{value:e.currentPassword},on:{input:function(t){t.target.composing||(e.currentPassword=t.target.value)}}})]),s("div",{staticClass:"mb-4"},[s("label",{staticClass:"block text-sm font-medium dark:text-neutral-200 mb-4",attrs:{for:"newPassword"}},[e._v(" "+e._s(e.$t("newPassword"))+" ")]),s("input",{directives:[{name:"model",rawName:"v-model",value:e.newPassword,expression:"newPassword"}],staticClass:"outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full",attrs:{id:"newPassword",type:"password",placeholder:e.$t("newPassword"),required:""},domProps:{value:e.newPassword},on:{input:function(t){t.target.composing||(e.newPassword=t.target.value)}}})]),s("div",{staticClass:"mb-4"},[s("label",{staticClass:"block text-sm font-medium dark:text-neutral-200 mb-4",attrs:{for:"confirmNewPassword"}},[e._v(" "+e._s(e.$t("confirmNewPassword"))+" ")]),s("input",{directives:[{name:"model",rawName:"v-model",value:e.confirmNewPassword,expression:"confirmNewPassword"}],staticClass:"outline-none bg-transparent border border-neutral-500 p-2 rounded-md w-full",attrs:{id:"confirmNewPassword",type:"password",placeholder:e.$t("confirmNewPassword"),required:""},domProps:{value:e.confirmNewPassword},on:{input:function(t){t.target.composing||(e.confirmNewPassword=t.target.value)}}})]),s("button",{staticClass:"bg-neutral-400 text-white p-2 rounded-md w-full hover:bg-red-800 transition",attrs:{type:"submit"}},[e._v(" "+e._s(e.$t("updatePassword"))+" ")])])])])},a=[];const n={name:"Settings",data(){return{currentPassword:"",newPassword:"",confirmNewPassword:""}},methods:{async updatePassword(e){e.preventDefault(),this.newPassword===this.confirmNewPassword?this.api.updatePassword({username:"admin",oldPassword:this.currentPassword,newPassword:this.newPassword}).then((e=>{this.currentPassword="",this.newPassword="",this.confirmNewPassword="",alert("Password updated")})).catch((e=>{alert(e.message||e.toString())})):alert("Password Mismatch")}},mounted(){this.api=new API}},d=n;function i(e,t,s,r,o,a,n,d){var i,l="function"===typeof e?e.options:e;if(t&&(l.render=t,l.staticRenderFns=s,l._compiled=!0),r&&(l.functional=!0),a&&(l._scopeId="data-v-"+a),n?(i=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"===typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(n)},l._ssrRegister=i):o&&(i=d?function(){o.call(this,(l.functional?this.parent:this).$root.$options.shadowRoot)}:o),i)if(l.functional){l._injectStyles=i;var u=l.render;l.render=function(e,t){return i.call(t),u(e,t)}}else{var c=l.beforeCreate;l.beforeCreate=c?[].concat(c,i):[i]}return{exports:e,options:l}}var l=i(d,o,a,!1,null,null,null);const u=l.exports,c=u;return t=t["default"],t})())); |
||||
|
//# sourceMappingURL=vue-settings.umd.min.js.map
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue