From 65e30ba1c77db48ac9aa744c3818a83fede972e3 Mon Sep 17 00:00:00 2001 From: mifi Date: Wed, 24 May 2023 10:33:41 -0400 Subject: [PATCH] Prettier... --- src/api/authenticate.ts | 24 +++---- src/api/deleteStrategy.ts | 20 +++--- src/api/resetPasswordGet.ts | 26 +++---- src/api/resetPasswordPost.ts | 61 ++++++++-------- src/constants/action.ts | 14 ++-- src/constants/auth.ts | 12 ++-- src/constants/db.ts | 8 +-- src/constants/env.ts | 23 ++++--- src/constants/errors.ts | 9 +-- src/constants/strategies.ts | 10 +-- src/constants/tokens.ts | 4 +- src/dao/create.ts | 76 +++++++++++--------- src/dao/deleteById.ts | 27 +++++--- src/dao/readAll.ts | 14 ++-- src/dao/readOneById.ts | 4 +- src/dao/readOneByRecord.ts | 7 +- src/dao/readOneByUsername.ts | 5 +- src/index.ts | 34 +++++---- src/model/auth.ts | 11 +-- src/model/log.ts | 6 +- src/model/strategy.ts | 15 ++-- src/model/token.ts | 15 ++-- src/schema/auth.ts | 111 ++++++++++++++++++------------ src/schema/log.ts | 60 ++++++++-------- src/schema/strategy.ts | 109 ++++++++++++++++------------- src/schema/token.ts | 106 ++++++++++++++++------------ src/utils/getDefaultExpiresFor.ts | 24 ++++--- src/utils/getLoginToken.ts | 18 ++--- src/utils/jwt.ts | 54 ++++++++------- src/utils/links.ts | 12 +++- src/utils/parseTimeoutToMs.ts | 22 +++--- src/utils/password.ts | 12 ++-- src/utils/tokens.ts | 18 ++--- 33 files changed, 548 insertions(+), 423 deletions(-) diff --git a/src/api/authenticate.ts b/src/api/authenticate.ts index bec6a76..62bb408 100644 --- a/src/api/authenticate.ts +++ b/src/api/authenticate.ts @@ -1,17 +1,17 @@ -import { Auth, Log } from '..'; -import { Action } from '../constants/action'; -import { getLoginToken } from '../utils/getLoginToken'; +import { Auth, Log } from ".."; +import { Action } from "../constants/action"; +import { getLoginToken } from "../utils/getLoginToken"; export const authenticate = async (username: string, password: string) => { - const doc = await Auth.findByUsername(username).catch(); - if (!!doc && (await doc.authenticate(password))) { - Log.add(doc.id, Action.AUTHENTICATE); - return { ...doc, token: getLoginToken(doc) }; - } + const doc = await Auth.findByUsername(username).catch(); + if (!!doc && (await doc.authenticate(password))) { + Log.add(doc.id, Action.AUTHENTICATE); + return { ...doc, token: getLoginToken(doc) }; + } - if (doc) { - Log.add(doc.id, Action.AUTHENTICATE_FAILURE); - } + if (doc) { + Log.add(doc.id, Action.AUTHENTICATE_FAILURE); + } - return false; + return false; }; diff --git a/src/api/deleteStrategy.ts b/src/api/deleteStrategy.ts index 676d63d..1ec20f7 100644 --- a/src/api/deleteStrategy.ts +++ b/src/api/deleteStrategy.ts @@ -1,15 +1,15 @@ -import { StringSchemaDefinition } from 'mongoose'; -import { Auth, Strategy } from '..'; +import { StringSchemaDefinition } from "mongoose"; +import { Auth, Strategy } from ".."; export const deleteStrategy = async (id: StringSchemaDefinition) => { - const strategy = await Strategy.findById(id); + const strategy = await Strategy.findById(id); - if (strategy) { - const parentId = strategy.parent; - await strategy.deleteOne(); - await Auth.findOneAndUpdate({ id: parentId, strategies: { $pull: id } }); - return true; - } + if (strategy) { + const parentId = strategy.parent; + await strategy.deleteOne(); + await Auth.findOneAndUpdate({ id: parentId, strategies: { $pull: id } }); + return true; + } - return false; + return false; }; diff --git a/src/api/resetPasswordGet.ts b/src/api/resetPasswordGet.ts index 2854885..08e4693 100644 --- a/src/api/resetPasswordGet.ts +++ b/src/api/resetPasswordGet.ts @@ -1,18 +1,18 @@ -import { readOneByUsername } from '../dao/readOneByUsername'; -import { Log, Token } from '..'; -import { TokenType } from '../constants/tokens'; -import { Action } from '../constants/action'; +import { readOneByUsername } from "../dao/readOneByUsername"; +import { Log, Token } from ".."; +import { TokenType } from "../constants/tokens"; +import { Action } from "../constants/action"; export const resetPasswordGet = async (username: string) => { - const doc = await readOneByUsername(username); + const doc = await readOneByUsername(username); - if (doc) { - Log.add(doc._id, Action.RESET_REQUEST); - return { - record: doc.record, - token: Token.getToken(TokenType.RESET, doc._id), - }; - } + if (doc) { + Log.add(doc._id, Action.RESET_REQUEST); + return { + record: doc.record, + token: Token.getToken(TokenType.RESET, doc._id), + }; + } - return false; + return false; }; diff --git a/src/api/resetPasswordPost.ts b/src/api/resetPasswordPost.ts index 63d28c2..742f4b0 100644 --- a/src/api/resetPasswordPost.ts +++ b/src/api/resetPasswordPost.ts @@ -1,38 +1,41 @@ -import { Types } from 'mongoose'; +import { Types } from "mongoose"; -import { Log, Strategy, Token } from '..'; -import { STRATEGIES } from '../constants/strategies'; -import { AuthDocument } from '../schema/auth'; -import { getLoginToken } from '../utils/getLoginToken'; -import { StrategyDocument } from '../schema/strategy'; -import { Action } from '../constants/action'; +import { Log, Strategy, Token } from ".."; +import { STRATEGIES } from "../constants/strategies"; +import { AuthDocument } from "../schema/auth"; +import { getLoginToken } from "../utils/getLoginToken"; +import { StrategyDocument } from "../schema/strategy"; +import { Action } from "../constants/action"; export const resetPasswordPost = async (token: string, password: string) => { - const parentId = await Token.validateResetToken(token); + const parentId = await Token.validateResetToken(token); - if (parentId) { - let parent: AuthDocument; - let strategy: StrategyDocument | null = await Strategy.findOne({ parent: parentId, method: STRATEGIES.LOCAL }); + if (parentId) { + let parent: AuthDocument; + let strategy: StrategyDocument | null = await Strategy.findOne({ + parent: parentId, + method: STRATEGIES.LOCAL, + }); - if (strategy) { - parent = await strategy.getAuthRecord(); - strategy.key = password; - await strategy.save(); - } else { - strategy = await Strategy.create({ - key: password, - method: STRATEGIES.LOCAL, - parent: parentId, - }); + if (strategy) { + parent = await strategy.getAuthRecord(); + strategy.key = password; + await strategy.save(); + } else { + strategy = await Strategy.create({ + key: password, + method: STRATEGIES.LOCAL, + parent: parentId, + }); - parent = await strategy.getAuthRecord(); - parent.strategies.push(strategy._id); - await parent.save(); - } - - Log.add(parent._id, Action.RESET); - return { record: parent.record, token: getLoginToken(parent) }; + parent = await strategy.getAuthRecord(); + parent.strategies.push(strategy._id); + await parent.save(); } - return false; + Log.add(parent._id, Action.RESET); + return { record: parent.record, token: getLoginToken(parent) }; + } + + return false; }; diff --git a/src/constants/action.ts b/src/constants/action.ts index a10460e..c679fdf 100644 --- a/src/constants/action.ts +++ b/src/constants/action.ts @@ -1,9 +1,9 @@ export enum Action { - AUTHENTICATE = 'AUTHENTICATE', - AUTHENTICATE_FAILURE = 'AUTHENTICATE_FAILURE', - CREATE = 'CREATE', - DELETE = 'DELETE', - RESET = 'RESET', - RESET_REQUEST = 'RESET_REQUEST', - UPDATE = 'UPDATE', + AUTHENTICATE = "AUTHENTICATE", + AUTHENTICATE_FAILURE = "AUTHENTICATE_FAILURE", + CREATE = "CREATE", + DELETE = "DELETE", + RESET = "RESET", + RESET_REQUEST = "RESET_REQUEST", + UPDATE = "UPDATE", } diff --git a/src/constants/auth.ts b/src/constants/auth.ts index 9e2094c..8c50655 100644 --- a/src/constants/auth.ts +++ b/src/constants/auth.ts @@ -1,8 +1,8 @@ export enum Status { - ACTIVE, - BLOCK_HARD, - BLOCK_SOFT, - DELETED, - INACTIVE, - UNVERIFIED, + ACTIVE, + BLOCK_HARD, + BLOCK_SOFT, + DELETED, + INACTIVE, + UNVERIFIED, } diff --git a/src/constants/db.ts b/src/constants/db.ts index ae52656..1a6f00c 100644 --- a/src/constants/db.ts +++ b/src/constants/db.ts @@ -4,7 +4,7 @@ export const DB_USERNAME = process.env.DB_USERNAME; export const DB_PASSWORD = process.env.DB_PASSWORD; export const DB_NAME = process.env.DB_NAME; -export const COLL_AUTH = 'Auth'; -export const COLL_LOG = 'Log'; -export const COLL_STRATEGY = 'Strategy'; -export const COLL_TOKEN = 'Token'; +export const COLL_AUTH = "Auth"; +export const COLL_LOG = "Log"; +export const COLL_STRATEGY = "Strategy"; +export const COLL_TOKEN = "Token"; diff --git a/src/constants/env.ts b/src/constants/env.ts index 929d33c..9dc0c1d 100644 --- a/src/constants/env.ts +++ b/src/constants/env.ts @@ -1,20 +1,21 @@ -export const PACKAGE_NAME = '@mifi/auth'; +export const PACKAGE_NAME = "@mifi/auth"; export const PORT = process.env.PORT || 9000; -export const SESSION_KEY = process.env.SESSION_KEY || 'secret-key'; +export const SESSION_KEY = process.env.SESSION_KEY || "secret-key"; -export const JWT_AUDIENCE = process.env.JWT_AUDIENCE || 'mifi.dev'; +export const JWT_AUDIENCE = process.env.JWT_AUDIENCE || "mifi.dev"; export const JWT_ISSUER = process.env.JWT_ISSUER || PACKAGE_NAME; -export const JWT_SECRET = process.env.JWT_SECRET || 'secret'; +export const JWT_SECRET = process.env.JWT_SECRET || "secret"; -export const LOGIN_VALID_TIMEOUT = process.env.LOGIN_VALID_TIMEOUT || '12h'; // ###d|h|m -export const RESET_VALID_TIMEOUT = process.env.RESET_VALID_TIMEOUT || '15m'; // ###d|h|m -export const VERIFY_VALID_TIMEOUT = process.env.VERIFY_VALID_TIMEOUT || '60d'; // ###d|h|m +export const LOGIN_VALID_TIMEOUT = process.env.LOGIN_VALID_TIMEOUT || "12h"; // ###d|h|m +export const RESET_VALID_TIMEOUT = process.env.RESET_VALID_TIMEOUT || "15m"; // ###d|h|m +export const VERIFY_VALID_TIMEOUT = process.env.VERIFY_VALID_TIMEOUT || "60d"; // ###d|h|m export const DEFAULT_TOKEN_DAYS = process.env.DEFAULT_TOKEN_DAYS || 365; -export const ROUTE_PREFIX = process.env.ROUTE_PREFIX || '/auth'; -export const LOGIN_ROUTE = process.env.LOGIN_ROUTE || '/login'; -export const RESET_ROUTE = process.env.RESET_ROUTE || '/reset'; -export const VERIFICATION_ROUTE = process.env.VERIFICATION_ROUTE || '/verification'; +export const ROUTE_PREFIX = process.env.ROUTE_PREFIX || "/auth"; +export const LOGIN_ROUTE = process.env.LOGIN_ROUTE || "/login"; +export const RESET_ROUTE = process.env.RESET_ROUTE || "/reset"; +export const VERIFICATION_ROUTE = + process.env.VERIFICATION_ROUTE || "/verification"; export const REQUIRE_VERIFICATION = process.env.REQUIRE_VERIFICATION || true; diff --git a/src/constants/errors.ts b/src/constants/errors.ts index 8761979..8027e65 100644 --- a/src/constants/errors.ts +++ b/src/constants/errors.ts @@ -1,12 +1,13 @@ export enum ErrorCodes { - RESET_REQUEST_DATA = 'RESET_REQUEST_DATA', + RESET_REQUEST_DATA = "RESET_REQUEST_DATA", } export const ErrorMessages = { - [ErrorCodes.RESET_REQUEST_DATA]: 'A valid username and password must be provided', + [ErrorCodes.RESET_REQUEST_DATA]: + "A valid username and password must be provided", }; export const getErrorBody = (code: ErrorCodes) => ({ - code, - message: ErrorMessages[code], + code, + message: ErrorMessages[code], }); diff --git a/src/constants/strategies.ts b/src/constants/strategies.ts index ea91c22..2b56a03 100644 --- a/src/constants/strategies.ts +++ b/src/constants/strategies.ts @@ -1,7 +1,7 @@ export enum STRATEGIES { - LOCAL, - APPLE, - FACEBOOK, - FIDO2, - GOOGLE, + LOCAL, + APPLE, + FACEBOOK, + FIDO2, + GOOGLE, } diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index b9dbd84..023f4d5 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -1,4 +1,4 @@ export enum TokenType { - RESET = 'RESET', - VERIFICATION = 'VERIFICATION', + RESET = "RESET", + VERIFICATION = "VERIFICATION", } diff --git a/src/dao/create.ts b/src/dao/create.ts index e58d4f0..efd2b17 100644 --- a/src/dao/create.ts +++ b/src/dao/create.ts @@ -1,37 +1,51 @@ -import { DatabaseError } from '@mifi/services-common/lib/domain/errors/DatabaseError'; +import { DatabaseError } from "@mifi/services-common/lib/domain/errors/DatabaseError"; -import { Auth, Log, Strategy, Token } from '..'; -import { Auth as AuthProps } from '../schema/auth'; -import { STRATEGIES } from '../constants/strategies'; -import { REQUIRE_VERIFICATION } from '../constants/env'; -import { TokenType } from '../constants/tokens'; -import { Status } from '../constants/auth'; -import { Action } from '../constants/action'; +import { Auth, Log, Strategy, Token } from ".."; +import { Auth as AuthProps } from "../schema/auth"; +import { STRATEGIES } from "../constants/strategies"; +import { REQUIRE_VERIFICATION } from "../constants/env"; +import { TokenType } from "../constants/tokens"; +import { Status } from "../constants/auth"; +import { Action } from "../constants/action"; -export const create = async ({ record, username, password }: AuthProps & { password: string }) => { - const status = REQUIRE_VERIFICATION ? Status.UNVERIFIED : Status.ACTIVE; - const doc = await Auth.create({ - record, - status, - username, +export const create = async ({ + record, + username, + password, +}: AuthProps & { password: string }) => { + const status = REQUIRE_VERIFICATION ? Status.UNVERIFIED : Status.ACTIVE; + const doc = await Auth.create({ + record, + status, + username, + }).catch((err) => { + throw new DatabaseError("failed to create user", { err }); + }); + if (doc) { + const strategy = await Strategy.create({ + method: STRATEGIES.LOCAL, + key: password, + parent: doc._id, }).catch((err) => { - throw new DatabaseError('failed to create user', { err }); + throw new DatabaseError("failed to create strategy", { err }); }); - if (doc) { - const strategy = await Strategy.create({ method: STRATEGIES.LOCAL, key: password, parent: doc._id }).catch( - (err) => { - throw new DatabaseError('failed to create strategy', { err }); - }, - ); - if (strategy) { - doc.strategies.push(strategy._id); - await doc.save(); - Log.add(doc._id, Action.CREATE); - return { doc, token: REQUIRE_VERIFICATION && (await Token.getToken(TokenType.VERIFICATION, doc._id)) }; - } - await doc.deleteOne((err) => { - throw new DatabaseError('failed to remove invalid auth record', { err, doc }); - }); + if (strategy) { + doc.strategies.push(strategy._id); + await doc.save(); + Log.add(doc._id, Action.CREATE); + return { + doc, + token: + REQUIRE_VERIFICATION && + (await Token.getToken(TokenType.VERIFICATION, doc._id)), + }; } - return null; + await doc.deleteOne((err) => { + throw new DatabaseError("failed to remove invalid auth record", { + err, + doc, + }); + }); + } + return null; }; diff --git a/src/dao/deleteById.ts b/src/dao/deleteById.ts index c141cab..c8ea15b 100644 --- a/src/dao/deleteById.ts +++ b/src/dao/deleteById.ts @@ -1,15 +1,20 @@ -import { StringSchemaDefinition } from 'mongoose'; +import { StringSchemaDefinition } from "mongoose"; -import { Auth, Log, Strategy, Token } from '..'; -import { Status } from '../constants/auth'; -import { Action } from '../constants/action'; +import { Auth, Log, Strategy, Token } from ".."; +import { Status } from "../constants/auth"; +import { Action } from "../constants/action"; export const deleteById = async (id: StringSchemaDefinition) => { - if (await Auth.findByIdAndUpdate(id, { status: Status.DELETED, strategies: [] }).catch()) { - await Strategy.deleteMany({ parent: id }); - await Token.deleteMany({ auth: id }); - Log.add(id, Action.DELETE); - return true; - } - return false; + if ( + await Auth.findByIdAndUpdate(id, { + status: Status.DELETED, + strategies: [], + }).catch() + ) { + await Strategy.deleteMany({ parent: id }); + await Token.deleteMany({ auth: id }); + Log.add(id, Action.DELETE); + return true; + } + return false; }; diff --git a/src/dao/readAll.ts b/src/dao/readAll.ts index 9abbdf6..eafc3f4 100644 --- a/src/dao/readAll.ts +++ b/src/dao/readAll.ts @@ -1,9 +1,11 @@ -import { FilterQuery } from 'mongoose'; +import { FilterQuery } from "mongoose"; -import { Auth } from '../model/auth'; -import { Status } from '../constants/auth'; -import { AuthDocument } from '../schema/auth'; +import { Auth } from "../model/auth"; +import { Status } from "../constants/auth"; +import { AuthDocument } from "../schema/auth"; -export const readAll = async (query: FilterQuery = {}) => Auth.find(query); +export const readAll = async (query: FilterQuery = {}) => + Auth.find(query); -export const readAllActive = async () => readAll({ status: { $ne: Status.DELETED } }); +export const readAllActive = async () => + readAll({ status: { $ne: Status.DELETED } }); diff --git a/src/dao/readOneById.ts b/src/dao/readOneById.ts index d8aee08..5fd02e6 100644 --- a/src/dao/readOneById.ts +++ b/src/dao/readOneById.ts @@ -1,5 +1,5 @@ -import { Types } from 'mongoose'; +import { Types } from "mongoose"; -import { Auth } from '../model/auth'; +import { Auth } from "../model/auth"; export const readOneById = async (id: Types.ObjectId) => Auth.findById(id); diff --git a/src/dao/readOneByRecord.ts b/src/dao/readOneByRecord.ts index 70ff0cb..126392b 100644 --- a/src/dao/readOneByRecord.ts +++ b/src/dao/readOneByRecord.ts @@ -1,5 +1,6 @@ -import { Types } from 'mongoose'; +import { Types } from "mongoose"; -import { Auth } from '../model/auth'; +import { Auth } from "../model/auth"; -export const readOneByRecord = async (record: Types.ObjectId) => Auth.findOne({ record }); +export const readOneByRecord = async (record: Types.ObjectId) => + Auth.findOne({ record }); diff --git a/src/dao/readOneByUsername.ts b/src/dao/readOneByUsername.ts index d6e4945..6241106 100644 --- a/src/dao/readOneByUsername.ts +++ b/src/dao/readOneByUsername.ts @@ -1,3 +1,4 @@ -import { Auth } from '../model/auth'; +import { Auth } from "../model/auth"; -export const readOneByUsername = async (username: string) => Auth.findOne({ username }); +export const readOneByUsername = async (username: string) => + Auth.findOne({ username }); diff --git a/src/index.ts b/src/index.ts index 8ca1ab7..3aa08fe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,27 @@ -import mongoose from 'mongoose'; +import mongoose from "mongoose"; -import { DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_USERNAME } from './constants/db'; -import { Auth } from './model/auth'; -import { Log } from './model/log'; -import { Strategy } from './model/strategy'; -import { Token } from './model/token'; +import { + DB_HOST, + DB_NAME, + DB_PASSWORD, + DB_PORT, + DB_USERNAME, +} from "./constants/db"; +import { Auth } from "./model/auth"; +import { Log } from "./model/log"; +import { Strategy } from "./model/strategy"; +import { Token } from "./model/token"; const connection = mongoose - .connect(`mongodb://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}`) - .then((c) => console.debug('Database connection established', { connection: c })) - .catch((error) => { - console.error('Mongo connection failure', error); - process.exit(1); - }); + .connect( + `mongodb://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}` + ) + .then((c) => + console.debug("Database connection established", { connection: c }) + ) + .catch((error) => { + console.error("Mongo connection failure", error); + process.exit(1); + }); export { connection, Auth, Log, Strategy, Token }; diff --git a/src/model/auth.ts b/src/model/auth.ts index 5658dcb..5d9cd9e 100644 --- a/src/model/auth.ts +++ b/src/model/auth.ts @@ -1,6 +1,9 @@ -import mongoose from 'mongoose'; +import mongoose from "mongoose"; -import { AuthDocument, AuthModel, AuthSchema } from '../schema/auth'; -import { COLL_AUTH } from '../constants/db'; +import { AuthDocument, AuthModel, AuthSchema } from "../schema/auth"; +import { COLL_AUTH } from "../constants/db"; -export const Auth = mongoose.model(COLL_AUTH, AuthSchema); +export const Auth = mongoose.model( + COLL_AUTH, + AuthSchema +); diff --git a/src/model/log.ts b/src/model/log.ts index 4aacf04..8bcd8ae 100644 --- a/src/model/log.ts +++ b/src/model/log.ts @@ -1,6 +1,6 @@ -import mongoose from 'mongoose'; +import mongoose from "mongoose"; -import { LogModel, Log as LogDocument, LogSchema } from '../schema/log'; -import { COLL_LOG } from '../constants/db'; +import { LogModel, Log as LogDocument, LogSchema } from "../schema/log"; +import { COLL_LOG } from "../constants/db"; export const Log = mongoose.model(COLL_LOG, LogSchema); diff --git a/src/model/strategy.ts b/src/model/strategy.ts index 6a5f022..8456ff9 100644 --- a/src/model/strategy.ts +++ b/src/model/strategy.ts @@ -1,6 +1,13 @@ -import mongoose from 'mongoose'; +import mongoose from "mongoose"; -import { StrategyDocument, StrategyModel, StrategySchema } from '../schema/strategy'; -import { COLL_STRATEGY } from '../constants/db'; +import { + StrategyDocument, + StrategyModel, + StrategySchema, +} from "../schema/strategy"; +import { COLL_STRATEGY } from "../constants/db"; -export const Strategy = mongoose.model(COLL_STRATEGY, StrategySchema); +export const Strategy = mongoose.model( + COLL_STRATEGY, + StrategySchema +); diff --git a/src/model/token.ts b/src/model/token.ts index 796bf33..f283c4f 100644 --- a/src/model/token.ts +++ b/src/model/token.ts @@ -1,6 +1,13 @@ -import mongoose from 'mongoose'; +import mongoose from "mongoose"; -import { TokenModel, Token as TokenDocument, TokenSchema } from '../schema/token'; -import { COLL_TOKEN } from '../constants/db'; +import { + TokenModel, + Token as TokenDocument, + TokenSchema, +} from "../schema/token"; +import { COLL_TOKEN } from "../constants/db"; -export const Token = mongoose.model(COLL_TOKEN, TokenSchema); +export const Token = mongoose.model( + COLL_TOKEN, + TokenSchema +); diff --git a/src/schema/auth.ts b/src/schema/auth.ts index 0d54871..6afdfc8 100644 --- a/src/schema/auth.ts +++ b/src/schema/auth.ts @@ -1,75 +1,98 @@ -import { Document, InferSchemaType, Model, Schema, StringSchemaDefinition, Types } from 'mongoose'; +import { + Document, + InferSchemaType, + Model, + Schema, + StringSchemaDefinition, + Types, +} from "mongoose"; -import { Status } from '../constants/auth'; -import { COLL_STRATEGY } from '../constants/db'; -import { STRATEGIES } from '../constants/strategies'; -import { StrategyDocument } from './strategy'; -import { verify } from '../utils/password'; +import { Status } from "../constants/auth"; +import { COLL_STRATEGY } from "../constants/db"; +import { STRATEGIES } from "../constants/strategies"; +import { StrategyDocument } from "./strategy"; +import { verify } from "../utils/password"; export interface Auth { - is2FA?: boolean; - record: StringSchemaDefinition; - username: string; - status: Status; - strategies: Types.ObjectId[] | StrategyDocument[]; + is2FA?: boolean; + record: StringSchemaDefinition; + username: string; + status: Status; + strategies: Types.ObjectId[] | StrategyDocument[]; } interface AuthBaseDocument extends Auth, Document { - authenticate(password: string): Promise; - getStrategy(method?: STRATEGIES): Promise; + authenticate(password: string): Promise; + getStrategy(method?: STRATEGIES): Promise; } export interface AuthDocument extends AuthBaseDocument { - strategies: Types.ObjectId[]; + strategies: Types.ObjectId[]; } export interface AuthPopulatedDocument extends AuthBaseDocument { - strategies: StrategyDocument[]; + strategies: StrategyDocument[]; } export interface AuthModel extends Model { - findByUsername(username: string): Promise; - getLocalStrategyForUsername(username: string): Promise; - isUsernameAvailable(username: string): Promise; + findByUsername(username: string): Promise; + getLocalStrategyForUsername(username: string): Promise; + isUsernameAvailable(username: string): Promise; } export const AuthSchema = new Schema( - { - is2FA: { type: Boolean, default: false }, - record: { type: Types.ObjectId, unique: true }, - status: { type: Number, enum: Status, default: Status.UNVERIFIED, index: true }, - strategies: [{ type: Types.ObjectId, ref: COLL_STRATEGY, default: [] }], - username: { type: String, required: true, unique: true }, - }, - { - minimize: true, - timestamps: true, + { + is2FA: { type: Boolean, default: false }, + record: { type: Types.ObjectId, unique: true }, + status: { + type: Number, + enum: Status, + default: Status.UNVERIFIED, + index: true, }, + strategies: [{ type: Types.ObjectId, ref: COLL_STRATEGY, default: [] }], + username: { type: String, required: true, unique: true }, + }, + { + minimize: true, + timestamps: true, + } ); -AuthSchema.methods.authenticate = async function (this: AuthBaseDocument, password: string) { - const strategy = await this.getStrategy(); - return !!strategy && verify(password, strategy.key); +AuthSchema.methods.authenticate = async function ( + this: AuthBaseDocument, + password: string +) { + const strategy = await this.getStrategy(); + return !!strategy && verify(password, strategy.key); }; -AuthSchema.methods.getStrategy = async function (this: AuthBaseDocument, method = STRATEGIES.LOCAL) { - const doc = await this.populate<{ strategies: StrategyDocument[] }>('strategies'); - return doc.strategies.filter((strategy) => strategy.method === method).pop() || null; +AuthSchema.methods.getStrategy = async function ( + this: AuthBaseDocument, + method = STRATEGIES.LOCAL +) { + const doc = await this.populate<{ strategies: StrategyDocument[] }>( + "strategies" + ); + return ( + doc.strategies.filter((strategy) => strategy.method === method).pop() || + null + ); }; AuthSchema.statics = { - async findByUsername(username) { - return this.findOne({ username }); - }, + async findByUsername(username) { + return this.findOne({ username }); + }, - async getLocalStrategyForUsername(username) { - const doc = await this.findByUsername(username); - return !!doc && doc.getStrategy(); - }, + async getLocalStrategyForUsername(username) { + const doc = await this.findByUsername(username); + return !!doc && doc.getStrategy(); + }, - async isUsernameAvailable(username) { - return !this.findByUsername(username); - }, + async isUsernameAvailable(username) { + return !this.findByUsername(username); + }, }; export type AuthSchema = InferSchemaType; diff --git a/src/schema/log.ts b/src/schema/log.ts index f799631..4dcc24c 100644 --- a/src/schema/log.ts +++ b/src/schema/log.ts @@ -1,45 +1,51 @@ -import { InferSchemaType, Model, Schema, StringSchemaDefinition, Types } from 'mongoose'; +import { + InferSchemaType, + Model, + Schema, + StringSchemaDefinition, + Types, +} from "mongoose"; -import { Payload } from '@mifi/services-common/lib/types/Payload'; +import { Payload } from "@mifi/services-common/lib/types/Payload"; -import { Action } from '../constants/action'; +import { Action } from "../constants/action"; export interface Log { - action: Action; - auth: StringSchemaDefinition; - payload?: Payload; + action: Action; + auth: StringSchemaDefinition; + payload?: Payload; } export interface LogModel extends Model { - add(id: StringSchemaDefinition, action: Action, payload?: Payload): void; - historyForUser(id: StringSchemaDefinition, action?: Action): Array; - loginsForUser(id: StringSchemaDefinition): Array; + add(id: StringSchemaDefinition, action: Action, payload?: Payload): void; + historyForUser(id: StringSchemaDefinition, action?: Action): Array; + loginsForUser(id: StringSchemaDefinition): Array; } export const LogSchema = new Schema( - { - action: { type: String, enum: Action, required: true }, - auth: { type: Types.ObjectId, index: true, required: true }, - payload: { type: Object }, - }, - { - minimize: true, - timestamps: true, - }, + { + action: { type: String, enum: Action, required: true }, + auth: { type: Types.ObjectId, index: true, required: true }, + payload: { type: Object }, + }, + { + minimize: true, + timestamps: true, + } ); LogSchema.statics = { - add(id, action, payload) { - this.create({ action, auth: id, payload }).catch(); - }, + add(id, action, payload) { + this.create({ action, auth: id, payload }).catch(); + }, - async historyForUser(id, action) { - return this.find({ auth: id, action }); - }, + async historyForUser(id, action) { + return this.find({ auth: id, action }); + }, - async loginsForUser(id) { - return this.find({ auth: id, action: Action.AUTHENTICATE }); - }, + async loginsForUser(id) { + return this.find({ auth: id, action: Action.AUTHENTICATE }); + }, }; export type LogSchema = InferSchemaType; diff --git a/src/schema/strategy.ts b/src/schema/strategy.ts index f811554..614a3e6 100644 --- a/src/schema/strategy.ts +++ b/src/schema/strategy.ts @@ -1,81 +1,92 @@ -import { Document, InferSchemaType, Model, Schema, StringSchemaDefinition, Types } from 'mongoose'; +import { + Document, + InferSchemaType, + Model, + Schema, + StringSchemaDefinition, + Types, +} from "mongoose"; -import { STRATEGIES } from '../constants/strategies'; -import { encrypt } from '../utils/password'; -import { COLL_AUTH } from '../constants/db'; -import { AuthDocument } from './auth'; -import { Strategy } from '..'; +import { STRATEGIES } from "../constants/strategies"; +import { encrypt } from "../utils/password"; +import { COLL_AUTH } from "../constants/db"; +import { AuthDocument } from "./auth"; +import { Strategy } from ".."; export interface Strategy { - method: STRATEGIES; - parent: StringSchemaDefinition | AuthDocument; - externalId?: string; - key: string; - profile?: { [key: string]: string | boolean | number }; - forceReset?: boolean; + method: STRATEGIES; + parent: StringSchemaDefinition | AuthDocument; + externalId?: string; + key: string; + profile?: { [key: string]: string | boolean | number }; + forceReset?: boolean; } interface StrategyBaseDocument extends Strategy, Document { - getAuthRecord(): Promise; - getPopulatedStrategy(): Promise; + getAuthRecord(): Promise; + getPopulatedStrategy(): Promise; } export interface StrategyDocument extends StrategyBaseDocument { - parent: StringSchemaDefinition; + parent: StringSchemaDefinition; } export interface StrategyPopulatedDocument extends StrategyBaseDocument { - parent: AuthDocument; + parent: AuthDocument; } export type StrategyModel = Model; export const StrategySchema = new Schema( - { - method: { - type: Number, - enum: STRATEGIES, - index: true, - }, - externalId: { type: String, index: true }, - forceReset: { type: Boolean }, - key: { type: String, required: true, trim: true }, - parent: { - type: Types.ObjectId, - ref: COLL_AUTH, - required: true, - }, - profile: {}, + { + method: { + type: Number, + enum: STRATEGIES, + index: true, }, - { - minimize: true, - timestamps: true, + externalId: { type: String, index: true }, + forceReset: { type: Boolean }, + key: { type: String, required: true, trim: true }, + parent: { + type: Types.ObjectId, + ref: COLL_AUTH, + required: true, }, + profile: {}, + }, + { + minimize: true, + timestamps: true, + } ); -StrategySchema.methods.getPopulatedStrategy = async function (this: StrategyDocument) { - return this.populate('parent'); +StrategySchema.methods.getPopulatedStrategy = async function ( + this: StrategyDocument +) { + return this.populate("parent"); }; StrategySchema.methods.getAuthRecord = async function (this: StrategyDocument) { - return (await this.getPopulatedStrategy()).parent; + return (await this.getPopulatedStrategy()).parent; }; -StrategySchema.pre('save', async function save(next) { - if (typeof this.method === 'undefined') { - return next(new Error(`Strategy requires a method.`)); - } +StrategySchema.pre("save", async function save(next) { + if (typeof this.method === "undefined") { + return next(new Error(`Strategy requires a method.`)); + } - if (await Strategy.findOne({ method: this.method, parent: this.parent })) { - return next(new Error(`${this.method} strategy already exists for this user.`)); - } + if (await Strategy.findOne({ method: this.method, parent: this.parent })) { + return next( + new Error(`${this.method} strategy already exists for this user.`) + ); + } - if (this.method !== STRATEGIES.LOCAL || !this.isModified('key')) { - return next(); - } - - this.key = encrypt(this.key); + if (this.method !== STRATEGIES.LOCAL || !this.isModified("key")) { return next(); + } + + this.key = encrypt(this.key); + return next(); }); export type StrategySchema = InferSchemaType; diff --git a/src/schema/token.ts b/src/schema/token.ts index aa4fd13..1c632fd 100644 --- a/src/schema/token.ts +++ b/src/schema/token.ts @@ -1,65 +1,81 @@ -import { InferSchemaType, Model, Schema, StringSchemaDefinition, Types } from 'mongoose'; +import { + InferSchemaType, + Model, + Schema, + StringSchemaDefinition, + Types, +} from "mongoose"; -import { TokenType } from '../constants/tokens'; -import { getDefaultExpiresFor } from '../utils/getDefaultExpiresFor'; -import { sign, verify } from '../utils/jwt'; +import { TokenType } from "../constants/tokens"; +import { getDefaultExpiresFor } from "../utils/getDefaultExpiresFor"; +import { sign, verify } from "../utils/jwt"; export interface Token { - auth: StringSchemaDefinition; - expires?: number; - type: TokenType; + auth: StringSchemaDefinition; + expires?: number; + type: TokenType; } export interface TokenModel extends Model { - cleanupExpiredTokens(): { success: boolean; deletedCount: number }; - getToken(type: TokenType, auth: Types.ObjectId, expires?: number): string; - validateResetToken(token: string): Types.ObjectId | false; + cleanupExpiredTokens(): { success: boolean; deletedCount: number }; + getToken(type: TokenType, auth: Types.ObjectId, expires?: number): string; + validateResetToken(token: string): Types.ObjectId | false; } export const TokenSchema = new Schema( - { - auth: { type: Types.ObjectId, index: true }, - expires: { type: Number, required: true }, - type: { type: String, enum: TokenType, required: true }, - }, - { - minimize: true, - timestamps: true, - }, + { + auth: { type: Types.ObjectId, index: true }, + expires: { type: Number, required: true }, + type: { type: String, enum: TokenType, required: true }, + }, + { + minimize: true, + timestamps: true, + } ); TokenSchema.statics = { - async cleanupExpiredTokens() { - const { acknowledged, deletedCount } = await this.deleteMany({ expires: { $lte: Date.now() } }); - return { success: acknowledged, deletedCount }; - }, + async cleanupExpiredTokens() { + const { acknowledged, deletedCount } = await this.deleteMany({ + expires: { $lte: Date.now() }, + }); + return { success: acknowledged, deletedCount }; + }, - async getToken(type: TokenType, auth: StringSchemaDefinition, expires?: number) { - const existing = await this.findOne({ type, auth }); - if (existing) { - await existing.deleteOne(); - } + async getToken( + type: TokenType, + auth: StringSchemaDefinition, + expires?: number + ) { + const existing = await this.findOne({ type, auth }); + if (existing) { + await existing.deleteOne(); + } - const doc = await this.create({ type, auth, expires: expires || getDefaultExpiresFor(type) }); - return sign({ - sub: `${doc._id}`, - exp: doc.expires, - }); - }, + const doc = await this.create({ + type, + auth, + expires: expires || getDefaultExpiresFor(type), + }); + return sign({ + sub: `${doc._id}`, + exp: doc.expires, + }); + }, - async validateResetToken(token: string) { - const { sub } = verify(token); + async validateResetToken(token: string) { + const { sub } = verify(token); - if (sub) { - const record = await this.findById(sub); - if (record) { - await record.deleteOne(); - return !!record?.expires && record.expires >= Date.now() && record.auth; - } - } + if (sub) { + const record = await this.findById(sub); + if (record) { + await record.deleteOne(); + return !!record?.expires && record.expires >= Date.now() && record.auth; + } + } - return false; - }, + return false; + }, }; export type TokenSchema = InferSchemaType; diff --git a/src/utils/getDefaultExpiresFor.ts b/src/utils/getDefaultExpiresFor.ts index c9d86d8..e392dd3 100644 --- a/src/utils/getDefaultExpiresFor.ts +++ b/src/utils/getDefaultExpiresFor.ts @@ -1,15 +1,19 @@ -import { LOGIN_VALID_TIMEOUT, RESET_VALID_TIMEOUT, VERIFY_VALID_TIMEOUT } from '../constants/env'; -import { TokenType } from '../constants/tokens'; -import { parseTimeoutToMs } from '../utils/parseTimeoutToMs'; +import { + LOGIN_VALID_TIMEOUT, + RESET_VALID_TIMEOUT, + VERIFY_VALID_TIMEOUT, +} from "../constants/env"; +import { TokenType } from "../constants/tokens"; +import { parseTimeoutToMs } from "../utils/parseTimeoutToMs"; export const getDefaultExpiresFor = (type: TokenType | void) => { - if (type === TokenType.RESET) { - return Date.now() + parseTimeoutToMs(RESET_VALID_TIMEOUT); - } + if (type === TokenType.RESET) { + return Date.now() + parseTimeoutToMs(RESET_VALID_TIMEOUT); + } - if (type === TokenType.VERIFICATION) { - return Date.now() + parseTimeoutToMs(VERIFY_VALID_TIMEOUT); - } + if (type === TokenType.VERIFICATION) { + return Date.now() + parseTimeoutToMs(VERIFY_VALID_TIMEOUT); + } - return Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT); + return Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT); }; diff --git a/src/utils/getLoginToken.ts b/src/utils/getLoginToken.ts index 4f98d35..e6eb311 100644 --- a/src/utils/getLoginToken.ts +++ b/src/utils/getLoginToken.ts @@ -1,11 +1,11 @@ -import { sign } from '../utils/jwt'; -import { LOGIN_VALID_TIMEOUT } from '../constants/env'; -import { parseTimeoutToMs } from '../utils/parseTimeoutToMs'; -import { AuthDocument } from '../schema/auth'; +import { sign } from "../utils/jwt"; +import { LOGIN_VALID_TIMEOUT } from "../constants/env"; +import { parseTimeoutToMs } from "../utils/parseTimeoutToMs"; +import { AuthDocument } from "../schema/auth"; export const getLoginToken = ({ record: sub, status }: AuthDocument) => - sign({ - sub: sub, - status, - exp: Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT), - }); + sign({ + sub: sub, + status, + exp: Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT), + }); diff --git a/src/utils/jwt.ts b/src/utils/jwt.ts index b6a9a51..e05290d 100644 --- a/src/utils/jwt.ts +++ b/src/utils/jwt.ts @@ -1,35 +1,39 @@ -import jwt from 'jsonwebtoken'; -import { JWT_AUDIENCE, JWT_ISSUER, JWT_SECRET } from '../constants/env'; +import jwt from "jsonwebtoken"; +import { JWT_AUDIENCE, JWT_ISSUER, JWT_SECRET } from "../constants/env"; export interface TokenProps { - aud?: string; - exp?: number | Date; - iss?: string; - sub: string | null; - [key: string]: any; + aud?: string; + exp?: number | Date; + iss?: string; + sub: string | null; + [key: string]: any; } export type SignProps = string | TokenProps | void; export const sign = (props: SignProps) => { - const today = new Date(); - const { sub = null, ...rest }: TokenProps = - typeof props === 'string' || typeof props === 'undefined' ? { sub: props || null } : props; - let { exp } = rest; - if (!exp) { - exp = new Date(today); - exp.setDate(today.getDate() + parseInt(process.env.JWT_DAYS_VALID as string)); - exp = exp.getTime() / 1000; - } - return jwt.sign( - { - exp, - sub, - aud: rest.aud || JWT_AUDIENCE, - iat: today.getTime(), - iss: rest.iss || JWT_ISSUER, - }, - JWT_SECRET, + const today = new Date(); + const { sub = null, ...rest }: TokenProps = + typeof props === "string" || typeof props === "undefined" + ? { sub: props || null } + : props; + let { exp } = rest; + if (!exp) { + exp = new Date(today); + exp.setDate( + today.getDate() + parseInt(process.env.JWT_DAYS_VALID as string) ); + exp = exp.getTime() / 1000; + } + return jwt.sign( + { + exp, + sub, + aud: rest.aud || JWT_AUDIENCE, + iat: today.getTime(), + iss: rest.iss || JWT_ISSUER, + }, + JWT_SECRET + ); }; export const verify = (token: string) => jwt.verify(token, JWT_SECRET); diff --git a/src/utils/links.ts b/src/utils/links.ts index 306ce13..754b4b3 100644 --- a/src/utils/links.ts +++ b/src/utils/links.ts @@ -1,5 +1,11 @@ -import { RESET_ROUTE, ROUTE_PREFIX, VERIFICATION_ROUTE } from '../constants/env'; +import { + RESET_ROUTE, + ROUTE_PREFIX, + VERIFICATION_ROUTE, +} from "../constants/env"; -export const getPasswordResetPath = (token: string) => `${ROUTE_PREFIX}${RESET_ROUTE}?t=${token}`; +export const getPasswordResetPath = (token: string) => + `${ROUTE_PREFIX}${RESET_ROUTE}?t=${token}`; -export const getVerificationPath = (token: string) => `${ROUTE_PREFIX}${VERIFICATION_ROUTE}?t=${token}`; +export const getVerificationPath = (token: string) => + `${ROUTE_PREFIX}${VERIFICATION_ROUTE}?t=${token}`; diff --git a/src/utils/parseTimeoutToMs.ts b/src/utils/parseTimeoutToMs.ts index f966836..c6da382 100644 --- a/src/utils/parseTimeoutToMs.ts +++ b/src/utils/parseTimeoutToMs.ts @@ -1,13 +1,13 @@ export const parseTimeoutToMs = (timeout: string) => { - const match = timeout.match(/(?\d+)(?d|h|m)/gi)?.groups || {}; - const { number, unit } = match; - switch (unit) { - case 'd': - return 1000 * 60 * 60 * 24 * parseInt(number); - case 'h': - return 1000 * 60 * 60 * parseInt(number); - case 'm': - default: - return 1000 * 60 * parseInt(number) || 1; - } + const match = timeout.match(/(?\d+)(?d|h|m)/gi)?.groups || {}; + const { number, unit } = match; + switch (unit) { + case "d": + return 1000 * 60 * 60 * 24 * parseInt(number); + case "h": + return 1000 * 60 * 60 * parseInt(number); + case "m": + default: + return 1000 * 60 * parseInt(number) || 1; + } }; diff --git a/src/utils/password.ts b/src/utils/password.ts index 922f138..bd369e8 100644 --- a/src/utils/password.ts +++ b/src/utils/password.ts @@ -1,12 +1,12 @@ -import { pbkdf2Sync, randomBytes } from 'crypto'; +import { pbkdf2Sync, randomBytes } from "crypto"; export const encrypt = (password: string) => { - const salt = randomBytes(16).toString('hex'); - const hash = pbkdf2Sync(password, salt, 10000, 512, 'sha512').toString('hex'); - return `${salt}:${hash}`; + const salt = randomBytes(16).toString("hex"); + const hash = pbkdf2Sync(password, salt, 10000, 512, "sha512").toString("hex"); + return `${salt}:${hash}`; }; export const verify = (test: string, secret: string) => { - const [salt, hash] = secret.split(':'); - return pbkdf2Sync(test, salt, 10000, 512, 'sha512').toString('hex') === hash; + const [salt, hash] = secret.split(":"); + return pbkdf2Sync(test, salt, 10000, 512, "sha512").toString("hex") === hash; }; diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index e9edda5..e3000fb 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -1,11 +1,11 @@ -import { sign } from './jwt'; -import { LOGIN_VALID_TIMEOUT } from '../constants/env'; -import { Status } from '../constants/auth'; -import { parseTimeoutToMs } from './parseTimeoutToMs'; +import { sign } from "./jwt"; +import { LOGIN_VALID_TIMEOUT } from "../constants/env"; +import { Status } from "../constants/auth"; +import { parseTimeoutToMs } from "./parseTimeoutToMs"; export const generateLoginToken = (sub: string, status: Status) => - sign({ - sub, - status, - exp: Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT), - }); + sign({ + sub, + status, + exp: Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT), + });