1
0
Fork 0
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:
nvc8996 2021-09-14 15:49:36 +03:00
parent e538a5edf8
commit 2ed19f6a40
18 changed files with 133 additions and 141 deletions

View file

@ -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');

View file

@ -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);

View file

@ -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');
} }
} }

View file

@ -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);
});
});
} }
} }

View file

@ -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); });
});
} }
} }

View file

@ -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;

View file

@ -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();
});
} }

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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')),
}; };

View 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,
});
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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,

View file

@ -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(/&nbsp;/g, ' ') .replace(/&nbsp;/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 () => {