diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..36bacbc --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,19 @@ +/* eslint-env node */ +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:import/errors', + 'plugin:prettier/recommended', + 'prettier', + ], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + settings: { + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import/resolver': 'typescript', + }, + root: true, +}; diff --git a/.npmignore b/.npmignore index bcade60..bfecfd5 100644 --- a/.npmignore +++ b/.npmignore @@ -1,8 +1,9 @@ +.build.yarnrc.yml +.drone.yml +.prettierrc +.yarnrc.yml +babel.config.* +jest.config.* src tsconfig.json tslint.json -.prettierrc -.yarnrc.yml -.drone.yml -babel.config.* -jest.config.* \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..f0c3079 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + parser: 'typescript', + printWidth: 120, + trailingComma: 'all', + tabWidth: 4, + singleQuote: true, +}; diff --git a/src/api/authenticate.ts b/src/api/authenticate.ts index 62bb408..bec6a76 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 1ec20f7..676d63d 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 08e4693..2854885 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 742f4b0..bfabbb0 100644 --- a/src/api/resetPasswordPost.ts +++ b/src/api/resetPasswordPost.ts @@ -1,41 +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(); + 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) }; } - Log.add(parent._id, Action.RESET); - return { record: parent.record, token: getLoginToken(parent) }; - } - - return false; + return false; }; diff --git a/src/constants/action.ts b/src/constants/action.ts index c679fdf..a10460e 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 8c50655..9e2094c 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 1a6f00c..ae52656 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 9dc0c1d..929d33c 100644 --- a/src/constants/env.ts +++ b/src/constants/env.ts @@ -1,21 +1,20 @@ -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 8027e65..8761979 100644 --- a/src/constants/errors.ts +++ b/src/constants/errors.ts @@ -1,13 +1,12 @@ 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 2b56a03..ea91c22 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 023f4d5..b9dbd84 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 efd2b17..a88f336 100644 --- a/src/dao/create.ts +++ b/src/dao/create.ts @@ -1,51 +1,45 @@ -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, - }).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, +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 strategy", { err }); + throw new DatabaseError('failed to create user', { 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)), - }; + 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, + }); + }); } - await doc.deleteOne((err) => { - throw new DatabaseError("failed to remove invalid auth record", { - err, - doc, - }); - }); - } - return null; + return null; }; diff --git a/src/dao/deleteById.ts b/src/dao/deleteById.ts index c8ea15b..a8fd485 100644 --- a/src/dao/deleteById.ts +++ b/src/dao/deleteById.ts @@ -1,20 +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 eafc3f4..9abbdf6 100644 --- a/src/dao/readAll.ts +++ b/src/dao/readAll.ts @@ -1,11 +1,9 @@ -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 5fd02e6..d8aee08 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 126392b..70ff0cb 100644 --- a/src/dao/readOneByRecord.ts +++ b/src/dao/readOneByRecord.ts @@ -1,6 +1,5 @@ -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 6241106..d6e4945 100644 --- a/src/dao/readOneByUsername.ts +++ b/src/dao/readOneByUsername.ts @@ -1,4 +1,3 @@ -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 3aa08fe..8ca1ab7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,27 +1,17 @@ -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 5d9cd9e..5658dcb 100644 --- a/src/model/auth.ts +++ b/src/model/auth.ts @@ -1,9 +1,6 @@ -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 8bcd8ae..4aacf04 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 8456ff9..6a5f022 100644 --- a/src/model/strategy.ts +++ b/src/model/strategy.ts @@ -1,13 +1,6 @@ -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 f283c4f..796bf33 100644 --- a/src/model/token.ts +++ b/src/model/token.ts @@ -1,13 +1,6 @@ -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 6afdfc8..bcab819 100644 --- a/src/schema/auth.ts +++ b/src/schema/auth.ts @@ -1,98 +1,80 @@ -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, + { + 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, }, - 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 4dcc24c..f799631 100644 --- a/src/schema/log.ts +++ b/src/schema/log.ts @@ -1,51 +1,45 @@ -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 614a3e6..f811554 100644 --- a/src/schema/strategy.ts +++ b/src/schema/strategy.ts @@ -1,92 +1,81 @@ -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, + { + 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: {}, }, - externalId: { type: String, index: true }, - forceReset: { type: Boolean }, - key: { type: String, required: true, trim: true }, - parent: { - type: Types.ObjectId, - ref: COLL_AUTH, - required: true, + { + minimize: true, + timestamps: 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")) { + if (this.method !== STRATEGIES.LOCAL || !this.isModified('key')) { + return next(); + } + + this.key = encrypt(this.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 1c632fd..24fe4e3 100644 --- a/src/schema/token.ts +++ b/src/schema/token.ts @@ -1,81 +1,71 @@ -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 e392dd3..c9d86d8 100644 --- a/src/utils/getDefaultExpiresFor.ts +++ b/src/utils/getDefaultExpiresFor.ts @@ -1,19 +1,15 @@ -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 e6eb311..4f98d35 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 e05290d..b6a9a51 100644 --- a/src/utils/jwt.ts +++ b/src/utils/jwt.ts @@ -1,39 +1,35 @@ -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) + 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, ); - 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 754b4b3..306ce13 100644 --- a/src/utils/links.ts +++ b/src/utils/links.ts @@ -1,11 +1,5 @@ -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 c6da382..f966836 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 bd369e8..922f138 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 e3000fb..e9edda5 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), + });