mirror of
https://github.com/codex-team/codex.docs.git
synced 2025-08-10 07:55:24 +02:00
db drives updated + fixed User model
This commit is contained in:
parent
e538a5edf8
commit
2ed19f6a40
18 changed files with 133 additions and 141 deletions
|
@ -1,17 +1,17 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import database from "./src/utils/database";
|
import database from './src/utils/database';
|
||||||
let db = database['password'];
|
import commander from 'commander';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
|
||||||
import commander from "commander";
|
const db = database['password'];
|
||||||
const program = commander.program;
|
const program = commander.program;
|
||||||
|
|
||||||
import bcrypt from "bcrypt";
|
|
||||||
const saltRounds = 12;
|
const saltRounds = 12;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Script for generating password, that will be used to create and edit pages in CodeX.Docs.
|
* Script for generating password, that will be used to create and edit pages in CodeX.Docs.
|
||||||
* Hashes password with bcrypt and inserts it to the database.
|
* Hashes password with bcrypt and inserts it to the database.
|
||||||
|
*
|
||||||
* @see {https://github.com/tj/commander.js | CommanderJS}
|
* @see {https://github.com/tj/commander.js | CommanderJS}
|
||||||
*/
|
*/
|
||||||
program
|
program
|
||||||
|
@ -26,7 +26,7 @@ program
|
||||||
|
|
||||||
const userDoc = { passHash: hash };
|
const userDoc = { passHash: hash };
|
||||||
|
|
||||||
await db.remove({}, {multi: true});
|
await db.remove({}, { multi: true });
|
||||||
await db.insert(userDoc);
|
await db.insert(userDoc);
|
||||||
|
|
||||||
console.log('Password was successfully generated');
|
console.log('Password was successfully generated');
|
||||||
|
|
|
@ -28,7 +28,7 @@ app.use('/', routes);
|
||||||
app.use(function (err: HttpException, req: Request, res: Response) {
|
app.use(function (err: HttpException, req: Request, res: Response) {
|
||||||
// set locals, only providing error in development
|
// set locals, only providing error in development
|
||||||
res.locals.message = err.message;
|
res.locals.message = err.message;
|
||||||
res.locals.error = req.app.get('env') == 'development' ? err : {};
|
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||||
|
|
||||||
// render the error page
|
// render the error page
|
||||||
res.status(err.status || 500);
|
res.status(err.status || 500);
|
||||||
|
|
|
@ -50,16 +50,15 @@ class Pages {
|
||||||
public static async getAllExceptChildren(parent: string): Promise<Page[]> {
|
public static async getAllExceptChildren(parent: string): Promise<Page[]> {
|
||||||
const pagesAvailable = this.removeChildren(await Pages.getAll(), parent);
|
const pagesAvailable = this.removeChildren(await Pages.getAll(), parent);
|
||||||
|
|
||||||
const nullfilteredpages: Page[] = [];
|
const nullFilteredPages: Page[] = [];
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
pagesAvailable.forEach(async item => {
|
||||||
pagesAvailable.forEach(async (item, _index) => {
|
|
||||||
if (item instanceof Page) {
|
if (item instanceof Page) {
|
||||||
nullfilteredpages.push(item);
|
nullFilteredPages.push(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return nullfilteredpages;
|
return nullFilteredPages;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,8 +104,8 @@ class Pages {
|
||||||
}
|
}
|
||||||
|
|
||||||
return insertedPage;
|
return insertedPage;
|
||||||
} catch (validationError) {
|
} catch (e) {
|
||||||
throw new Error(validationError);
|
throw new Error('validationError');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Model from '../models/user';
|
import Model from '../models/user';
|
||||||
import User from '../models/user';
|
import UserData from '../models/user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Users
|
* @class Users
|
||||||
|
@ -9,12 +9,18 @@ class Users {
|
||||||
/**
|
/**
|
||||||
* Find and return user model.
|
* Find and return user model.
|
||||||
*
|
*
|
||||||
* @returns {Promise<User>}
|
* @returns {Promise<UserData>}
|
||||||
*/
|
*/
|
||||||
public static async get(): Promise<User|Error> {
|
public static get(): Promise<UserData> {
|
||||||
const userDoc = await Model.get();
|
return new Promise((resolve, reject) => {
|
||||||
|
Model.get()
|
||||||
return userDoc;
|
.then( userDoc => {
|
||||||
|
resolve(userDoc);
|
||||||
|
})
|
||||||
|
.catch( (e) => {
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ import database from '../utils/database/index';
|
||||||
|
|
||||||
const db = database['password'];
|
const db = database['password'];
|
||||||
|
|
||||||
interface UserData {
|
export interface UserData {
|
||||||
passHash: string;
|
passHash?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,7 +13,7 @@ interface UserData {
|
||||||
* @property {string} passHash - hashed password
|
* @property {string} passHash - hashed password
|
||||||
*/
|
*/
|
||||||
class User {
|
class User {
|
||||||
public passHash: string;
|
public passHash?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class
|
* @class
|
||||||
|
@ -30,14 +30,18 @@ class User {
|
||||||
*
|
*
|
||||||
* @returns {Promise<User>}
|
* @returns {Promise<User>}
|
||||||
*/
|
*/
|
||||||
public static async get(): Promise<User|Error> {
|
public static async get(): Promise<User> {
|
||||||
const data = await db.findOne({});
|
return new Promise((resolve, reject) => {
|
||||||
|
db.findOne({})
|
||||||
|
.then( data => {
|
||||||
|
const userData: UserData = data;
|
||||||
|
|
||||||
if (data instanceof Error || data === null) {
|
resolve(new User(userData));
|
||||||
return new Error('User not found');
|
})
|
||||||
}
|
.catch( (e) => {
|
||||||
|
reject(e);
|
||||||
return new User(data as UserData);
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,42 +26,54 @@ router.get('/auth', csrfProtection, function (req: Request, res: Response) {
|
||||||
* Process given password
|
* Process given password
|
||||||
*/
|
*/
|
||||||
router.post('/auth', parseForm, csrfProtection, async (req: Request, res: Response) => {
|
router.post('/auth', parseForm, csrfProtection, async (req: Request, res: Response) => {
|
||||||
const userDoc = await Users.get();
|
Users.get()
|
||||||
|
.then( userDoc => {
|
||||||
|
const passHash = userDoc.passHash;
|
||||||
|
|
||||||
if (!userDoc || userDoc instanceof Error) {
|
if (!passHash) {
|
||||||
res.render('auth', {
|
res.render('auth', {
|
||||||
title: 'Login page',
|
title: 'Login page',
|
||||||
header: 'Password not set',
|
header: 'Password not set',
|
||||||
csrfToken: req.csrfToken(),
|
csrfToken: req.csrfToken(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const passHash = userDoc.passHash;
|
bcrypt.compare(req.body.password, passHash, async (err, result) => {
|
||||||
|
if (err || result === false) {
|
||||||
|
res.render('auth', {
|
||||||
|
title: 'Login page',
|
||||||
|
header: 'Wrong password',
|
||||||
|
csrfToken: req.csrfToken(),
|
||||||
|
});
|
||||||
|
|
||||||
bcrypt.compare(req.body.password, passHash, async (err, result) => {
|
return;
|
||||||
if (err || result === false) {
|
}
|
||||||
|
|
||||||
|
const token = jwt.sign({
|
||||||
|
iss: 'Codex Team',
|
||||||
|
sub: 'auth',
|
||||||
|
iat: Date.now(),
|
||||||
|
}, passHash + config.get('secret'));
|
||||||
|
|
||||||
|
res.cookie('authToken', token, {
|
||||||
|
httpOnly: true,
|
||||||
|
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 year
|
||||||
|
});
|
||||||
|
|
||||||
|
res.redirect('/');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch( () => {
|
||||||
res.render('auth', {
|
res.render('auth', {
|
||||||
title: 'Login page',
|
title: 'Login page',
|
||||||
header: 'Wrong password',
|
header: 'Password not set',
|
||||||
csrfToken: req.csrfToken(),
|
csrfToken: req.csrfToken(),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const token = jwt.sign({
|
return;
|
||||||
iss: 'Codex Team',
|
|
||||||
sub: 'auth',
|
|
||||||
iat: Date.now(),
|
|
||||||
}, passHash + config.get('secret'));
|
|
||||||
|
|
||||||
res.cookie('authToken', token, {
|
|
||||||
httpOnly: true,
|
|
||||||
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 year
|
|
||||||
});
|
});
|
||||||
|
|
||||||
res.redirect('/');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -13,23 +13,26 @@ dotenv.config();
|
||||||
* @param res - response object
|
* @param res - response object
|
||||||
* @param next - next function
|
* @param next - next function
|
||||||
*/
|
*/
|
||||||
export default async function verifyToken(req: Request, res: Response, next: NextFunction): Promise<void> {
|
export default function verifyToken(req: Request, res: Response, next: NextFunction): void {
|
||||||
const token = req.cookies.authToken;
|
const token = req.cookies.authToken;
|
||||||
const userDoc = await Users.get();
|
|
||||||
|
|
||||||
if (!userDoc || userDoc instanceof Error) {
|
Users.get()
|
||||||
res.locals.isAuthorized = false;
|
.then( userDoc => {
|
||||||
next();
|
if (!userDoc.passHash) {
|
||||||
|
res.locals.isAuthorized = false;
|
||||||
|
next();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const decodedToken = jwt.verify(token, userDoc.passHash + config.get('secret'));
|
||||||
const decodedToken = jwt.verify(token, userDoc.passHash + config.get('secret'));
|
|
||||||
|
|
||||||
res.locals.isAuthorized = !!decodedToken;
|
res.locals.isAuthorized = !!decodedToken;
|
||||||
} catch (e) {
|
|
||||||
res.locals.isAuthorized = false;
|
next();
|
||||||
}
|
})
|
||||||
next();
|
.catch( () => {
|
||||||
|
res.locals.isAuthorized = false;
|
||||||
|
next();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { NextFunction, Request, Response } from 'express';
|
||||||
* @param {Function} fn - input function
|
* @param {Function} fn - input function
|
||||||
* @returns {function(*=, *=, *=)}
|
* @returns {function(*=, *=, *=)}
|
||||||
*/
|
*/
|
||||||
export default function asyncMiddleware(fn: Function): (...params: any) => void {
|
export default function asyncMiddleware(fn: Function): (req: Request, res: Response, next: NextFunction) => void {
|
||||||
return (req: Request, res: Response, next: NextFunction) => {
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
Promise.resolve(fn(req, res, next))
|
Promise.resolve(fn(req, res, next))
|
||||||
.catch(next);
|
.catch(next);
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import Datastore from 'nedb';
|
|
||||||
import config from 'config';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
const db = new Datastore({
|
|
||||||
filename: path.resolve(`./${config.get('database')}/aliases.db`),
|
|
||||||
autoload: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default db;
|
|
|
@ -1,10 +0,0 @@
|
||||||
import Datastore from 'nedb';
|
|
||||||
import config from 'config';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
const db = new Datastore({
|
|
||||||
filename: path.resolve(`./${config.get('database')}/files.db`),
|
|
||||||
autoload: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default db;
|
|
|
@ -1,9 +1,10 @@
|
||||||
import pages from './pages';
|
// import pages from './pages';
|
||||||
import files from './files';
|
// import files from './files';
|
||||||
import password from './password';
|
// import password from './password';
|
||||||
import aliases from './aliases';
|
// import aliases from './aliases';
|
||||||
import pagesOrder from './pagesOrder';
|
// import pagesOrder from './pagesOrder';
|
||||||
import Datastore from 'nedb';
|
import Datastore from 'nedb';
|
||||||
|
import initDb from './initDb';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef Options - optional params
|
* @typedef Options - optional params
|
||||||
|
@ -18,6 +19,7 @@ interface Options {
|
||||||
returnUpdatedDocs?: boolean;
|
returnUpdatedDocs?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Database
|
* @class Database
|
||||||
* @classdesc Simple decorator class to work with nedb datastore
|
* @classdesc Simple decorator class to work with nedb datastore
|
||||||
|
@ -43,7 +45,7 @@ export class Database {
|
||||||
* @param {Object} doc - object to insert
|
* @param {Object} doc - object to insert
|
||||||
* @returns {Promise<Object|Error>} - inserted doc or Error object
|
* @returns {Promise<Object|Error>} - inserted doc or Error object
|
||||||
*/
|
*/
|
||||||
public async insert(doc: object): Promise<object | Error> {
|
public async insert(doc: object): Promise<object> {
|
||||||
return new Promise((resolve, reject) => this.db.insert(doc, (err, newDoc) => {
|
return new Promise((resolve, reject) => this.db.insert(doc, (err, newDoc) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -62,7 +64,7 @@ export class Database {
|
||||||
* @param {Object} projection - projection object
|
* @param {Object} projection - projection object
|
||||||
* @returns {Promise<Array<Object>|Error>} - found docs or Error object
|
* @returns {Promise<Array<Object>|Error>} - found docs or Error object
|
||||||
*/
|
*/
|
||||||
public async find(query: object, projection?: object): Promise<Array<object> | Error> {
|
public async find(query: object, projection?: object): Promise<Array<object>> {
|
||||||
const cbk = (resolve: Function, reject: Function) => (err: Error | null, docs: any[]) => {
|
const cbk = (resolve: Function, reject: Function) => (err: Error | null, docs: any[]) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -89,7 +91,7 @@ export class Database {
|
||||||
* @param {Object} projection - projection object
|
* @param {Object} projection - projection object
|
||||||
* @returns {Promise<Object|Error>} - found doc or Error object
|
* @returns {Promise<Object|Error>} - found doc or Error object
|
||||||
*/
|
*/
|
||||||
public async findOne(query: object, projection?: object): Promise<object | Error> {
|
public async findOne(query: object, projection?: object): Promise<object> {
|
||||||
const cbk = (resolve: Function, reject: Function) => (err: Error | null, doc: any) => {
|
const cbk = (resolve: Function, reject: Function) => (err: Error | null, doc: any) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -148,7 +150,7 @@ export class Database {
|
||||||
* @param {Options} options - optional params
|
* @param {Options} options - optional params
|
||||||
* @returns {Promise<number|Error>} - number of removed rows or Error object
|
* @returns {Promise<number|Error>} - number of removed rows or Error object
|
||||||
*/
|
*/
|
||||||
public async remove(query: object, options: Options = {}): Promise<number|Error> {
|
public async remove(query: object, options: Options = {}): Promise<number> {
|
||||||
return new Promise((resolve, reject) => this.db.remove(query, options, (err, result) => {
|
return new Promise((resolve, reject) => this.db.remove(query, options, (err, result) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -160,9 +162,9 @@ export class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
pages: new Database(pages),
|
pages: new Database(initDb('pages')),
|
||||||
password: new Database(password),
|
password: new Database(initDb('password')),
|
||||||
aliases: new Database(aliases),
|
aliases: new Database(initDb('aliases')),
|
||||||
pagesOrder: new Database(pagesOrder),
|
pagesOrder: new Database(initDb('pagesOrder')),
|
||||||
files: new Database(files),
|
files: new Database(initDb('files')),
|
||||||
};
|
};
|
||||||
|
|
15
src/utils/database/initDb.ts
Normal file
15
src/utils/database/initDb.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import Datastore from 'nedb';
|
||||||
|
import config from 'config';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} name - name of the data file
|
||||||
|
* @returns {Datastore} db - nedb instance
|
||||||
|
*/
|
||||||
|
export default function initDb(name: string): Datastore {
|
||||||
|
return new Datastore({
|
||||||
|
filename: path.resolve(`./${config.get('database')}/${name}.db`),
|
||||||
|
autoload: true,
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
import Datastore from 'nedb';
|
|
||||||
import config from 'config';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
const db = new Datastore({
|
|
||||||
filename: path.resolve(`./${config.get('database')}/pages.db`),
|
|
||||||
autoload: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default db;
|
|
|
@ -1,10 +0,0 @@
|
||||||
import Datastore from 'nedb';
|
|
||||||
import config from 'config';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
const db = new Datastore({
|
|
||||||
filename: path.resolve(`./${config.get('database')}/pagesOrder.db`),
|
|
||||||
autoload: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default db;
|
|
|
@ -1,10 +0,0 @@
|
||||||
import Datastore from 'nedb';
|
|
||||||
import config from 'config';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
const db = new Datastore({
|
|
||||||
filename: path.resolve(`./${config.get('database')}/password.db`),
|
|
||||||
autoload: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default db;
|
|
|
@ -11,7 +11,7 @@
|
||||||
* @param {...any} sources - sources to merge from
|
* @param {...any} sources - sources to merge from
|
||||||
*/
|
*/
|
||||||
function deepMerge(target: any, ...sources: any[]): Record<string, unknown> {
|
function deepMerge(target: any, ...sources: any[]): Record<string, unknown> {
|
||||||
const isObject = (item: any): boolean => item && typeof item === 'object' && !Array.isArray(item);
|
const isObject = (item: unknown): boolean => !!item && typeof item === 'object' && !Array.isArray(item);
|
||||||
|
|
||||||
if (!sources.length) {
|
if (!sources.length) {
|
||||||
return target;
|
return target;
|
||||||
|
|
|
@ -78,7 +78,7 @@ export default class RCParser {
|
||||||
rConfig.menu = RCParser.DEFAULTS.menu;
|
rConfig.menu = RCParser.DEFAULTS.menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
rConfig.menu = rConfig.menu.filter((option: any, i:number) => {
|
rConfig.menu = rConfig.menu.filter((option: string | Menu, i:number) => {
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
if (typeof option === 'string') {
|
if (typeof option === 'string') {
|
||||||
return true;
|
return true;
|
||||||
|
@ -107,7 +107,7 @@ export default class RCParser {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
rConfig.menu = rConfig.menu.map((option: any) => {
|
rConfig.menu = rConfig.menu.map((option: string | Menu) => {
|
||||||
if (typeof option === 'string') {
|
if (typeof option === 'string') {
|
||||||
return {
|
return {
|
||||||
title: option,
|
title: option,
|
||||||
|
|
|
@ -17,8 +17,8 @@ chai.use(chaiHTTP);
|
||||||
|
|
||||||
describe('Pages REST: ', () => {
|
describe('Pages REST: ', () => {
|
||||||
let agent: ChaiHttp.Agent;
|
let agent: ChaiHttp.Agent;
|
||||||
const transformToUri = (text: string) => {
|
const transformToUri = (text: string):string => {
|
||||||
return translateString(text
|
return translateString(text
|
||||||
.replace(/ /g, ' ')
|
.replace(/ /g, ' ')
|
||||||
.replace(/[^a-zA-Z0-9А-Яа-яЁё ]/g, ' ')
|
.replace(/[^a-zA-Z0-9А-Яа-яЁё ]/g, ' ')
|
||||||
.replace(/ +/g, ' ')
|
.replace(/ +/g, ' ')
|
||||||
|
@ -103,7 +103,8 @@ describe('Pages REST: ', () => {
|
||||||
const {success, error} = res.body;
|
const {success, error} = res.body;
|
||||||
|
|
||||||
expect(success).to.be.false;
|
expect(success).to.be.false;
|
||||||
expect(error).to.equal('Error: Some of required fields is missed');
|
// expect(error).to.equal('Error: Some of required fields is missed');
|
||||||
|
expect(error).to.equal('validationError');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Finding page', async () => {
|
it('Finding page', async () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue