- Linty fresh...
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2023-05-02 02:04:09 -04:00
parent 14fe45fc9c
commit 34acea15a2
17 changed files with 1203 additions and 559 deletions

5
.eslintrc.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
extends: [
'semistandard'
]
};

View File

@@ -18,10 +18,10 @@ app.use(cookie());
app.keys = [process.env.SESSION_KEYS as string]; app.keys = [process.env.SESSION_KEYS as string];
app.use(session({}, app)); app.use(session({}, app));
app.use(passport.initialize()) app.use(passport.initialize());
app.use(passport.session()) app.use(passport.session());
// Application error logging. // Application error logging.
app.on('error', console.error); app.on('error', console.error);
export default app; export default app;

View File

@@ -1,7 +1,7 @@
export enum STRATEGIES { export enum STRATEGIES {
LOCAL, LOCAL,
APPLE, APPLE,
FACEBOOK, FACEBOOK,
FIDO2, FIDO2,
GOOGLE, GOOGLE,
} }

View File

@@ -1,8 +1,149 @@
// const errors = require('restify-errors'); // // const errors = require('restify-errors');
// const config = require('../config'); // // const config = require('../config');
// const handlePassportResponse = (req, res, next) => (err, user, info) => { // // const handlePassportResponse = (req, res, next) => (err, user, info) => {
// // if (err) {
// // return next(err);
// // }
// // const isVerifiedUser = user &&
// // user.isRegistrationVerified();
// // if (user && isVerifiedUser) {
// // return res.send({ ...user.toAuthJSON() });
// // } else if (user && !isVerifiedUser){
// // return res.send({
// // registrationSuccess: true,
// // nextSteps: 'Check your email for our confirmation email, you will not be able to login without confirming.'
// // });
// // }
// // return res.send(400, info);
// // };
// // module.exports = function (server, auth) {
// // const { passport } = auth;
// // /* Local Auth */
// // server.post('/auth', (req, res, next) => {
// // const { body: { username = null, password = null } = {} } = req;
// // if (!username || !password) {
// // let errors = {};
// // if (!username) {
// // errors.username = 'is required';
// // }
// // if (!password) {
// // errors.password = 'is required';
// // }
// // return res.send(422, { errors });
// // }
// // const callback = handlePassportResponse(req, res, next);
// // return passport.authenticate('local', { session: false }.then(callback)(req, res, next);
// // });
// // /**
// // * SERVICES
// // */
// // /* Google */
// // server.get(
// // '/auth/google',
// // passport.authenticate('google', { scope: 'profile email', session: false }),
// // );
// // server.get(
// // '/auth/google/callback',
// // (req, res, next) => {
// // const callback = handlePassportResponse(req, res, next);
// // return passport.authenticate(
// // 'google',
// // { failureRedirect: '/login' },
// // callback,
// // )(req, res, next);
// // },
// // );
// // /* Facebook */
// // server.get(
// // '/auth/facebook/login',
// // passport.authenticate('facebook', {
// // scope: ['email', 'public_profile'],
// // session: false,
// // }),
// // );
// // server.get(
// // '/auth/facebook/loggedin',
// // (req, res, next) => {
// // const callback = handlePassportResponse(req, res, next);
// // return passport.authenticate(
// // 'facebook',
// // { failureRedirect: '/login' },
// // callback,
// // )(req, res, next);
// // }
// // );
// // server.get(
// // '/auth/facebook/link',
// // auth.secure,
// // (req, res, next) => {
// // req.user.record.setLinkCheckBit((err, linkCheckBit) => {
// // passport.authenticate('facebookLink', {
// // scope: ['email', 'public_profile'],
// // session: false,
// // state: linkCheckbit,
// // })(req, res, next);
// // });
// // },
// // );
// //
// // server.get(
// // '/auth/facebook/linked',
// // (req, res, next) => {
// // const linkCheckBit = req.query.state;
// //
// // return passport.authenticate(
// // 'facebook',
// // { failureRedirect: '/profile' },
// // (err, profile) => {
// // if (err) {
// // return next(err);
// // }
// //
// // User.linkFacebookProfile(linkCheckBit, profile, (err, user) => {
// // if (err) {
// // return next(err);
// // }
// //
// // if (!user) {
// // return next(err, false, 'Linking the account to Facebook was unsuccessful, please try again.');
// // }
// //
// // res.send({
// // success: true,
// // info: 'Facerbook account successfully linked',
// // });
// // });
// // },
// // )(req, res, next);
// // }
// // );
// };
// import Koa from 'koa';
// import Router from 'koa-router';
// import { StatusCodes } from 'http-status-codes';
// import Users from 'grow-db/lib/models/users';
// const handlePassportResponse = (ctx: Koa.Context) => (err, user, info) => {
// if (err) { // if (err) {
// return next(err); // return next(err);
// } // }
@@ -22,210 +163,65 @@
// return res.send(400, info); // return res.send(400, info);
// }; // };
// module.exports = function (server, auth) { // const routerOpts: Router.IRouterOptions = {
// const { passport } = auth; // prefix: '/auth',
// };
// /* Local Auth */ // const router: Router = new Router(routerOpts);
// server.post('/auth', (req, res, next) => {
// const { body: { username = null, password = null } = {} } = req;
// if (!username || !password) { // router.get('/', async (ctx: Koa.Context) => {
// let errors = {}; // const data = await Customers.find({}).exec();
// ctx.body = { data };
// });
// if (!username) { // router.get('/:customer_id', async (ctx: Koa.Context) => {
// errors.username = 'is required'; // const data = await Customers.findById(ctx.params.customer_id).populate('person').exec();
// } // if (!data) {
// ctx.throw(StatusCodes.NOT_FOUND);
// }
// ctx.body = { data };
// });
// if (!password) { // router.delete('/:customer_id', async (ctx: Koa.Context) => {
// errors.password = 'is required'; // const data = await Customers.findByIdAndDelete(ctx.params.customer_id).exec();
// } // if (!data) {
// ctx.throw(StatusCodes.NOT_FOUND);
// }
// ctx.body = { success: true, data };
// });
// return res.send(422, { errors }); // router.post('/', async (ctx: Koa.Context) => {
// const data = await Customers.create(ctx.body);
// data.save();
// ctx.body = { success: true, data };
// });
// router.post('/', async (ctx: Koa.Context) => {
// const { body: { username = null, password = null } = {} } = ctx;
// if (!username || !password) {
// let errors = {};
// if (!username) {
// errors.username = 'is required';
// } // }
// const callback = handlePassportResponse(req, res, next); // if (!password) {
// return passport.authenticate('local', { session: false }.then(callback)(req, res, next); // errors.password = 'is required';
// }); // }
// /** // ctx.status = StatusCodes.UNPROCESSABLE_ENTITY;
// * SERVICES // ctx.throw(422, { errors });
// */ // }
// /* Google */ // const callback = handlePassportResponse(req, res, next);
// server.get( // return passport.authenticate('local', { session: false }, callback)(req, res, next);
// '/auth/google', // });
// passport.authenticate('google', { scope: 'profile email', session: false }),
// );
// server.get( // router.patch('/:customer_id', async (ctx: Koa.Context) => {
// '/auth/google/callback', // const data = await Customers.findByIdAndUpdate(ctx.params.customer_id);
// (req, res, next) => { // if (!data) {
// const callback = handlePassportResponse(req, res, next); // ctx.throw(StatusCodes.NOT_FOUND);
// return passport.authenticate( // }
// 'google', // ctx.body = { success: true, data };
// { failureRedirect: '/login' }, // });
// callback,
// )(req, res, next);
// },
// );
// /* Facebook */
// server.get(
// '/auth/facebook/login',
// passport.authenticate('facebook', {
// scope: ['email', 'public_profile'],
// session: false,
// }),
// );
// server.get(
// '/auth/facebook/loggedin',
// (req, res, next) => {
// const callback = handlePassportResponse(req, res, next);
// return passport.authenticate(
// 'facebook',
// { failureRedirect: '/login' },
// callback,
// )(req, res, next);
// }
// );
// server.get(
// '/auth/facebook/link',
// auth.secure,
// (req, res, next) => {
// req.user.record.setLinkCheckBit((err, linkCheckBit) => {
// passport.authenticate('facebookLink', {
// scope: ['email', 'public_profile'],
// session: false,
// state: linkCheckbit,
// })(req, res, next);
// });
// },
// );
//
// server.get(
// '/auth/facebook/linked',
// (req, res, next) => {
// const linkCheckBit = req.query.state;
//
// return passport.authenticate(
// 'facebook',
// { failureRedirect: '/profile' },
// (err, profile) => {
// if (err) {
// return next(err);
// }
//
// User.linkFacebookProfile(linkCheckBit, profile, (err, user) => {
// if (err) {
// return next(err);
// }
//
// if (!user) {
// return next(err, false, 'Linking the account to Facebook was unsuccessful, please try again.');
// }
//
// res.send({
// success: true,
// info: 'Facerbook account successfully linked',
// });
// });
// },
// )(req, res, next);
// }
// );
};
import Koa from 'koa';
import Router from 'koa-router';
import { StatusCodes } from 'http-status-codes';
import Users from 'grow-db/lib/models/users';
const handlePassportResponse = (ctx: Koa.Context) => (err, user, info) => {
if (err) {
return next(err);
}
const isVerifiedUser = user &&
user.isRegistrationVerified();
if (user && isVerifiedUser) {
return res.send({ ...user.toAuthJSON() });
} else if (user && !isVerifiedUser){
return res.send({
registrationSuccess: true,
nextSteps: 'Check your email for our confirmation email, you will not be able to login without confirming.'
});
}
return res.send(400, info);
};
const routerOpts: Router.IRouterOptions = {
prefix: '/auth',
};
const router: Router = new Router(routerOpts);
router.get('/', async (ctx: Koa.Context) => {
const data = await Customers.find({}).exec();
ctx.body = { data };
});
router.get('/:customer_id', async (ctx: Koa.Context) => {
const data = await Customers.findById(ctx.params.customer_id).populate('person').exec();
if (!data) {
ctx.throw(StatusCodes.NOT_FOUND);
}
ctx.body = { data };
});
router.delete('/:customer_id', async (ctx: Koa.Context) => {
const data = await Customers.findByIdAndDelete(ctx.params.customer_id).exec();
if (!data) {
ctx.throw(StatusCodes.NOT_FOUND);
}
ctx.body = { success: true, data };
});
router.post('/', async (ctx: Koa.Context) => {
const data = await Customers.create(ctx.body);
data.save();
ctx.body = { success: true, data };
});
router.post('/', async (ctx: Koa.Context) => {
const { body: { username = null, password = null } = {} } = ctx;
if (!username || !password) {
let errors = {};
if (!username) {
errors.username = 'is required';
}
if (!password) {
errors.password = 'is required';
}
ctx.status = StatusCodes.UNPROCESSABLE_ENTITY;
ctx.throw(422, { errors });
}
const callback = handlePassportResponse(req, res, next);
return passport.authenticate('local', { session: false }, callback)(req, res, next);
});
router.patch('/:customer_id', async (ctx: Koa.Context) => {
const data = await Customers.findByIdAndUpdate(ctx.params.customer_id);
if (!data) {
ctx.throw(StatusCodes.NOT_FOUND);
}
ctx.body = { success: true, data };
});

View File

@@ -7,5 +7,5 @@ const DB_PORT = process.env.DB_PORT || 27017;
const DB_NAME = process.env.DB_NAME || 'auth'; const DB_NAME = process.env.DB_NAME || 'auth';
export const connection = mongoose.connect( export const connection = mongoose.connect(
`${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_NAME}` `${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_NAME}`
); );

View File

@@ -1,13 +1,14 @@
import { StatusCodes } from "http-status-codes"; import { StatusCodes } from 'http-status-codes';
import { Context, Next } from "koa"; import { Context, Next } from 'koa';
export const errorHandler = async (ctx: Context, next: Next) => { export const errorHandler = async (ctx: Context, next: Next) => {
try { try {
await next(); await next();
} catch (error: any) { } catch (error: any) {
ctx.status = error.statusCode || error.status || StatusCodes.INTERNAL_SERVER_ERROR; ctx.status =
error.status = ctx.status; error.statusCode || error.status || StatusCodes.INTERNAL_SERVER_ERROR;
ctx.body = { error }; error.status = ctx.status;
ctx.app.emit('error', error, ctx); ctx.body = { error };
} ctx.app.emit('error', error, ctx);
}
}; };

View File

@@ -2,14 +2,14 @@ import { Next } from 'koa';
import { KoaContext } from '../types/KoaContext'; import { KoaContext } from '../types/KoaContext';
export const performanceLogger = async (ctx: KoaContext, next: Next) => { export const performanceLogger = async (ctx: KoaContext, next: Next) => {
await next(); await next();
const rt = ctx.response.get('X-Response-Time'); const rt = ctx.response.get('X-Response-Time');
console.log(`${ctx.method} ${ctx.url} - ${rt}`); console.log(`${ctx.method} ${ctx.url} - ${rt}`);
}; };
export const perfromanceTimer = async (ctx: KoaContext, next: Next) => { export const perfromanceTimer = async (ctx: KoaContext, next: Next) => {
const start = Date.now(); const start = Date.now();
await next(); await next();
const ms = Date.now() - start; const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`); ctx.set('X-Response-Time', `${ms}ms`);
}; };

View File

@@ -1,5 +1,11 @@
import { JwtPayload } from 'jsonwebtoken'; import { JwtPayload } from 'jsonwebtoken';
import { Document, InferSchemaType, Model, Schema, StringSchemaDefinition, Types } from 'mongoose'; import {
InferSchemaType,
Model,
Schema,
StringSchemaDefinition,
Types,
} from 'mongoose';
import { Strategy } from './strategy'; import { Strategy } from './strategy';
import { STRATEGIES } from '../constants/strategies'; import { STRATEGIES } from '../constants/strategies';
@@ -8,127 +14,139 @@ import { encrypt, verify as verifyPassword } from '../utils/password';
import { generateResetToken } from '../utils/tokens'; import { generateResetToken } from '../utils/tokens';
export type Auth = { export type Auth = {
is2FA?: boolean; is2FA?: boolean;
record: StringSchemaDefinition; record: StringSchemaDefinition;
username: string; username: string;
} };
export type AuthPrivate = Auth & { export type AuthPrivate = Auth & {
strategies: Types.ArraySubdocument<Strategy>; strategies: Types.ArraySubdocument<Strategy>;
} };
export interface AuthMethods { export interface AuthMethods {
authenticate(password: string): boolean; authenticate(password: string): boolean;
getAuthStrategy(method?: STRATEGIES): Strategy | false; getAuthStrategy(method?: STRATEGIES): Strategy | false;
getResetLink(route: string): Promise<string | undefined>; getResetLink(route: string): Promise<string | undefined>;
getResetToken(): Promise<string | undefined>; getResetToken(): Promise<string | undefined>;
getToken(props?: Omit<TokenProps, 'sub'>): string; getToken(props?: Omit<TokenProps, 'sub'>): string;
setPassword(password: string): Promise<boolean>; setPassword(password: string): Promise<boolean>;
} }
export interface AuthModel extends Model<AuthPrivate, {}, AuthMethods> { export interface AuthModel extends Model<AuthPrivate, {}, AuthMethods> {
authenticate(password: any): boolean; authenticate(password: any): boolean;
findByUsername(username: string): Promise<AuthModel & AuthPrivate>; findByUsername(username: string): Promise<AuthModel & AuthPrivate>;
isUsernameAvailable(username: string): Promise<boolean>; isUsernameAvailable(username: string): Promise<boolean>;
findUserForReset(strategy: STRATEGIES, token: string): Promise<Strategy | undefined>; findUserForReset(
resetPassword(token: string, password: string): Promise<boolean>; strategy: STRATEGIES,
} token: string
): Promise<Strategy | undefined>;
resetPassword(token: string, password: string): Promise<boolean>;
}
export const AuthSchema = new Schema<AuthPrivate, AuthModel, AuthMethods>( export const AuthSchema = new Schema<AuthPrivate, AuthModel, AuthMethods>(
{ {
is2FA: { type: Boolean, default: false }, is2FA: { type: Boolean, default: false },
record: { type: Types.ObjectId }, record: { type: Types.ObjectId },
strategies: { type: Types.ArraySubdocument<Strategy>, required: true }, strategies: { type: Types.ArraySubdocument<Strategy>, required: true },
username: { type: String, required: true, unique: true }, username: { type: String, required: true, unique: true },
}, },
{ {
minimize: true, minimize: true,
timestamps: true, timestamps: true,
}, }
); );
AuthSchema.methods = { AuthSchema.methods = {
authenticate: function(password: string) { authenticate: function (password: string) {
const strategy = this.getAuthStrategy(STRATEGIES.LOCAL); const strategy = this.getAuthStrategy(STRATEGIES.LOCAL);
return !!strategy && verifyPassword(password, strategy.key); return !!strategy && verifyPassword(password, strategy.key);
}, },
getAuthStrategy: function(method = STRATEGIES.LOCAL) { getAuthStrategy: function (method = STRATEGIES.LOCAL) {
return this.strategies.filter((strategy: Strategy) => strategy.method === method).pop() || false; return (
}, this.strategies
.filter((strategy: Strategy) => strategy.method === method)
.pop() || false
);
},
getToken: function(props = {}) { getToken: function (props = {}) {
return sign({ return sign({
sub: this._id, sub: this._id,
...props, ...props,
}); });
}, },
getResetLink: async function (route) { getResetLink: async function (route) {
const resetToken = await this.getResetToken(); const resetToken = await this.getResetToken();
if (resetToken) { if (resetToken) {
let resetRoute = route; let resetRoute = route;
resetRoute = resetRoute.replace(':user_id', this._id); resetRoute = resetRoute.replace(':user_id', this._id);
resetRoute = resetRoute.replace(':reset_token?', resetToken); resetRoute = resetRoute.replace(':reset_token?', resetToken);
const resetUrl = `${process.env.URL}${resetRoute}`; const resetUrl = `${process.env.URL}${resetRoute}`;
console.log('[sendPasswordReset] resetUrl:', resetUrl); console.log('[sendPasswordReset] resetUrl:', resetUrl);
return resetUrl; return resetUrl;
} }
}, },
getResetToken: async function () { getResetToken: async function () {
const { key, token } = generateResetToken(this._id); const { key, token } = generateResetToken(this._id);
this.resetCheckBit = key; this.resetCheckBit = key;
await this.save().catch(() => undefined); await this.save().catch(() => undefined);
return token; return token;
}, },
setPassword: async function (password) { setPassword: async function (password) {
const key = encrypt(password); const key = encrypt(password);
const hasLocalStrategy = !!this.getAuthStrategy(STRATEGIES.LOCAL); const hasLocalStrategy = !!this.getAuthStrategy(STRATEGIES.LOCAL);
const strategy = { const strategy = {
key, key,
method: STRATEGIES.LOCAL, method: STRATEGIES.LOCAL,
resetToken: undefined, resetToken: undefined,
}; };
if (hasLocalStrategy) { if (hasLocalStrategy) {
await this.model('User').findOneAndUpdate( await this.model('User')
{ _id: this._id, 'strategies.method': STRATEGIES.LOCAL }, .findOneAndUpdate(
{ $set: { 'strategies.$': strategy } }, { _id: this._id, 'strategies.method': STRATEGIES.LOCAL },
{ upsert: true }, { $set: { 'strategies.$': strategy } },
).catch(); { upsert: true },
return true; )
} .catch();
this.credentials.push(strategy); return true;
await this.save().catch(() => false); }
return true; this.credentials.push(strategy);
}, await this.save().catch(() => false);
return true;
}
}; };
AuthSchema.statics = { AuthSchema.statics = {
// authenticateAndGetRecordLocator: async function (username, password) { // authenticateAndGetRecordLocator: async function (username, password) {
// const auth = await this.findByUserName(username); // const auth = await this.findByUserName(username);
// if (auth && auth.authenticate(password)) { // if (auth && auth.authenticate(password)) {
// return auth?.record; // return auth?.record;
// } // }
// return false; // return false;
// }, // },
findByUsername: async function (username) { findByUsername: async function (username) {
return this.findOne({ username }); return this.findOne({ username });
}, },
isUsernameAvailable: async function (username) { isUsernameAvailable: async function (username) {
return !!!this.findByUsername(username); return !this.findByUsername(username);
}, },
resetPassword: async function (token, password) { resetPassword: async function (token, password) {
const decoded = verifyJwt(token); const decoded = verifyJwt(token);
const { sub, key } = decoded as JwtPayload; const { sub, key } = decoded as JwtPayload;
const auth = await this.findOne({ _id: sub, 'strategies.resetToken': key }).catch(); const auth = await this.findOne({
return !!auth && auth.setPassword(password); _id: sub,
}, 'strategies.resetToken': key,
}).catch();
return !!auth && auth.setPassword(password);
}
}; };
export type AuthSchema = InferSchemaType<typeof AuthSchema>; export type AuthSchema = InferSchemaType<typeof AuthSchema>;

View File

@@ -2,24 +2,24 @@ import { InferSchemaType, Schema, Types } from 'mongoose';
import { STRATEGIES } from '../constants/strategies'; import { STRATEGIES } from '../constants/strategies';
export const Strategy = new Schema( export const Strategy = new Schema(
{ {
method: { method: {
type: Number, type: Number,
enum: Object.values(STRATEGIES), enum: Object.values(STRATEGIES),
index: true, index: true,
required: true, required: true,
unique: true, unique: true
},
externalId: { type: String, index: true },
key: { type: String, required: true, trim: true },
profile: {},
resetToken: { type: String },
forceReset: { type: Boolean },
},
{
minimize: true,
timestamps: true,
}, },
externalId: { type: String, index: true },
key: { type: String, required: true, trim: true },
profile: {},
resetToken: { type: String },
forceReset: { type: Boolean }
},
{
minimize: true,
timestamps: true
}
); );
export type Strategy = InferSchemaType<typeof Strategy>; export type Strategy = InferSchemaType<typeof Strategy>;

View File

@@ -1,12 +1,12 @@
import dotenv from 'dotenv'; import dotenv from 'dotenv';
dotenv.config();
import app from './app'; import app from './app';
import { connection } from './database/database.connection'; import { connection } from './database/database.connection';
dotenv.config();
const PORT: number = Number(process.env.PORT) || 9000; const PORT: number = Number(process.env.PORT) || 9000;
connection.then( connection.then(
() => app.listen(PORT), () => app.listen(PORT),
(err) => console.error('ERROR!', err), (err) => console.error('ERROR!', err),
); );

View File

@@ -1,20 +1,19 @@
import passport from 'koa-passport'; import passport from 'koa-passport';
import { Strategy } from 'passport-local'; import { Strategy } from 'passport-local';
import bcrypt from 'bcrypt';
import Auth from '../model/auth'; import Auth from '../model/auth';
import { AuthSchema } from '../schema/auth';
export const LocalStrategy = passport.use(new Strategy(async (username, password, done) => { export const localStrategy = passport.use(
new Strategy(async (username, password, done) => {
const user = await Auth.findOne({ const user = await Auth.findOne({
where: { where: {
username, username,
} }
}).catch(); }).catch();
if (user && user.authenticate(password)) { if (user && user.authenticate(password)) {
done(null, user); done(null, user);
} else { } else {
done(null, false); done(null, false);
} }
} })
)); );

View File

@@ -2,13 +2,16 @@ import Auth from '../model/auth';
import { AuthModel, AuthPrivate } from '../schema/auth'; import { AuthModel, AuthPrivate } from '../schema/auth';
import { sign } from './jwt'; import { sign } from './jwt';
export const getAuthenticationBundle = async (username: string, password: string) => { export const getAuthenticationBundle = async (
const auth = await Auth.findByUsername(username).catch(); username: string,
const isAuthenticated = !!auth && (auth as AuthModel).authenticate(password); password: string,
const record = isAuthenticated ? (auth as AuthPrivate).record : null; ) => {
const token = isAuthenticated ? (auth as AuthModel).getToken() : sign(); const auth = await Auth.findByUsername(username).catch();
return { const isAuthenticated = !!auth && (auth as AuthModel).authenticate(password);
record, const record = isAuthenticated ? (auth as AuthPrivate).record : null;
token, const token = isAuthenticated ? (auth as AuthModel).getToken() : sign();
}; return {
record,
token,
};
}; };

View File

@@ -1,31 +1,40 @@
import jwt, { JwtPayload } from 'jsonwebtoken'; import jwt, { JwtPayload } from 'jsonwebtoken';
export interface TokenProps { export interface TokenProps {
aud?: string; aud?: string;
exp?: number | Date; exp?: number | Date;
iss?: string; iss?: string;
sub: string | null; sub: string | null;
[key: string]: any; [key: string]: any;
} }
export type SignProps = string | TokenProps | void; export type SignProps = string | TokenProps | void;
export const sign = (props: SignProps) => { export const sign = (props: SignProps) => {
const today = new Date(); const today = new Date();
const { sub = null, ...rest }: TokenProps = typeof props === 'string' || typeof props === 'undefined' ? { sub: props || null } : props; const { sub = null, ...rest }: TokenProps =
let exp = rest.exp; typeof props === 'string' || typeof props === 'undefined'
if (!exp) { ? { sub: props || null }
exp = new Date(today); : props;
exp.setDate(today.getDate() + parseInt(process.env.JWT_DAYS_VALID as string)); let exp = rest.exp;
exp = exp.getTime() / 1000; if (!exp) {
} exp = new Date(today);
return jwt.sign({ exp.setDate(
aud: rest.aud || process.env.JWT_AUDIENCE, today.getDate() + parseInt(process.env.JWT_DAYS_VALID as string),
exp, );
iat: today.getTime(), exp = exp.getTime() / 1000;
iss: rest.iss || process.env.JWT_ISSUER, }
sub, return jwt.sign(
}, process.env.JWT_SECRET || 'secret'); {
exp,
sub,
aud: rest.aud || process.env.JWT_AUDIENCE,
iat: today.getTime(),
iss: rest.iss || process.env.JWT_ISSUER,
},
process.env.JWT_SECRET || 'secret',
);
}; };
export const verify = (token: string) => jwt.verify(token, process.env.JWT_SECRET || 'secret'); export const verify = (token: string) =>
jwt.verify(token, process.env.JWT_SECRET || 'secret');

View File

@@ -1,12 +1,12 @@
import { pbkdf2Sync, randomBytes } from 'crypto'; import { pbkdf2Sync, randomBytes } from 'crypto';
export const encrypt = (password: string) => { export const encrypt = (password: string) => {
const salt = randomBytes(16).toString('hex'); const salt = randomBytes(16).toString('hex');
const hash = pbkdf2Sync(password, salt, 10000, 512, 'sha512').toString('hex'); const hash = pbkdf2Sync(password, salt, 10000, 512, 'sha512').toString('hex');
return `${salt}:${hash}`; return `${salt}:${hash}`;
}; };
export const verify = (test: string, secret: string) => { export const verify = (test: string, secret: string) => {
const [salt, hash] = secret.split(':'); const [salt, hash] = secret.split(':');
return pbkdf2Sync(test, salt, 10000, 512, 'sha512').toString('hex') === hash; return pbkdf2Sync(test, salt, 10000, 512, 'sha512').toString('hex') === hash;
}; };

View File

@@ -1,13 +1,13 @@
import crypto from 'crypto'; import crypto from 'crypto';
import { sign } from "./jwt"; import { sign } from './jwt';
export const generateResetToken = (sub: string) => { export const generateResetToken = (sub: string) => {
const key = crypto.randomBytes(16).toString('hex'); const key = crypto.randomBytes(16).toString('hex');
const token = sign({ const token = sign({
sub, sub,
key, key,
exp: (Date.now() + (24 * 60 * 60 * 1000)), exp: Date.now() + 24 * 60 * 60 * 1000,
}); });
return { key, token }; return { key, token };
}; };

View File

@@ -6,14 +6,12 @@
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"lint": "tslint --project tsconfig.json --format stylish", "lint": "tslint --project tsconfig.json --format stylish",
"prettier:fix": "prettier-eslint --eslint-config-path ./.eslintrc.js --write '**/*.ts'", "prettier:fix": "prettier-eslint --write '**/*.ts'",
"serve": "ts-node src/server.ts", "serve": "ts-node src/server.ts",
"start": "nodemon" "start": "nodemon"
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/node16": "^1.0.3", "@tsconfig/node16": "^1.0.3",
"@types/bcrypt": "^5.0.0",
"@types/crypto-js": "^4.1.1",
"@types/dotenv": "^8.2.0", "@types/dotenv": "^8.2.0",
"@types/http-status-codes": "^1.2.0", "@types/http-status-codes": "^1.2.0",
"@types/jsonwebtoken": "^9.0.1", "@types/jsonwebtoken": "^9.0.1",
@@ -32,6 +30,12 @@
"@types/passport-google-oauth": "^1.0.42", "@types/passport-google-oauth": "^1.0.42",
"@types/passport-jwt": "^3.0.8", "@types/passport-jwt": "^3.0.8",
"@types/passport-local": "^1.0.35", "@types/passport-local": "^1.0.35",
"eslint": "^8.13.0",
"eslint-config-semistandard": "latest",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.0.0",
"eslint-plugin-promise": "^6.0.0",
"jest": "^29.4.2", "jest": "^29.4.2",
"nodemon": "^2.0.20", "nodemon": "^2.0.20",
"prettier": "^2.8.4", "prettier": "^2.8.4",
@@ -45,8 +49,6 @@
}, },
"dependencies": { "dependencies": {
"@simplewebauthn/server": "^7.2.0", "@simplewebauthn/server": "^7.2.0",
"bcrypt": "^5.1.0",
"crypto": "^1.0.1",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"http-status-codes": "^2.2.0", "http-status-codes": "^2.2.0",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",

919
yarn.lock

File diff suppressed because it is too large Load Diff