Reduce duplicate code, move shit out to common package

This commit is contained in:
2023-05-30 20:22:55 -04:00
parent 64e1f53f4e
commit 4ce4b62fe5
30 changed files with 106 additions and 192 deletions

View File

@@ -1,12 +1,13 @@
import { Action } from '@mifi/auth-common/lib/enums/action';
import { generateLoginToken } from '@mifi/auth-common/lib/utils/generateLoginToken';
import { Auth, Log } from '..';
import { Action } from '../constants/action';
import { getLoginToken } from '../utils/getLoginToken';
export const authenticate = async (username: string, password: string, includeToken = false) => {
const doc = await Auth.findByUsername(username).catch();
if (!!doc && (await doc.authenticate(password))) {
Log.add(doc.id, Action.AUTHENTICATE);
return { sub: doc._id, record: doc.record, token: includeToken ? getLoginToken(doc) : undefined };
return { sub: doc._id, record: doc.record, token: includeToken ? generateLoginToken(doc) : undefined };
}
if (doc) {

View File

@@ -1,4 +1,5 @@
import { StringSchemaDefinition } from 'mongoose';
import { Auth, Strategy } from '..';
export const deleteStrategy = async (id: StringSchemaDefinition) => {

View File

@@ -1,7 +1,8 @@
import { Action } from '@mifi/auth-common/lib/enums/action';
import { TokenType } from '@mifi/auth-common/lib/enums/tokens';
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);

View File

@@ -1,11 +1,12 @@
import { Types } from 'mongoose';
import { Log, Strategy, Token } from '..';
import { STRATEGIES } from '../constants/strategies';
import { Action } from '@mifi/auth-common/lib/enums/action';
import { STRATEGIES } from '@mifi/auth-common/lib/enums/strategies';
import { generateLoginToken } from '@mifi/auth-common/lib/utils/generateLoginToken';
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 '..';
export const resetPasswordPost = async (token: string, password: string) => {
const parentId = await Token.validateResetToken(token);
@@ -34,7 +35,7 @@ export const resetPasswordPost = async (token: string, password: string) => {
}
Log.add(parent._id, Action.RESET);
return { record: parent.record, token: getLoginToken(parent) };
return { record: parent.record, token: generateLoginToken(parent) };
}
return false;

View File

@@ -1,9 +0,0 @@
export enum Action {
AUTHENTICATE = 'AUTHENTICATE',
AUTHENTICATE_FAILURE = 'AUTHENTICATE_FAILURE',
CREATE = 'CREATE',
DELETE = 'DELETE',
RESET = 'RESET',
RESET_REQUEST = 'RESET_REQUEST',
UPDATE = 'UPDATE',
}

View File

@@ -1,8 +0,0 @@
export enum Status {
ACTIVE,
BLOCK_HARD,
BLOCK_SOFT,
DELETED,
INACTIVE,
UNVERIFIED,
}

View File

@@ -1,20 +0,0 @@
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 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 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 REQUIRE_VERIFICATION = process.env.REQUIRE_VERIFICATION || true;

View File

@@ -1,12 +0,0 @@
export enum ErrorCodes {
RESET_REQUEST_DATA = 'RESET_REQUEST_DATA',
}
export const ErrorMessages = {
[ErrorCodes.RESET_REQUEST_DATA]: 'A valid username and password must be provided',
};
export const getErrorBody = (code: ErrorCodes) => ({
code,
message: ErrorMessages[code],
});

View File

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

View File

@@ -0,0 +1,4 @@
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;

View File

@@ -1,4 +0,0 @@
export enum TokenType {
RESET = 'RESET',
VERIFICATION = 'VERIFICATION',
}

View File

@@ -1,12 +1,12 @@
import { Action } from '@mifi/auth-common/lib/enums/action';
import { Status } from '@mifi/auth-common/lib/enums/status';
import { STRATEGIES } from '@mifi/auth-common/lib/enums/strategies';
import { TokenType } from '@mifi/auth-common/lib/enums/tokens';
import { REQUIRE_VERIFICATION } from '@mifi/auth-common/lib/settings';
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 '../..';
type CreateProps = Pick<AuthProps, 'username'> & {
externalId?: string;

View File

@@ -1,8 +1,9 @@
import { StringSchemaDefinition } from 'mongoose';
import { Action } from '@mifi/auth-common/lib/enums/action';
import { Status } from '@mifi/auth-common/lib/enums/status';
import { Auth, Log, Strategy, Token } from '../..';
import { Status } from '../../constants/auth';
import { Action } from '../../constants/action';
export const deleteById = async (id: StringSchemaDefinition) => {
if (

View File

@@ -1,7 +1,8 @@
import { FilterQuery } from 'mongoose';
import { Status } from '@mifi/auth-common/lib/enums/status';
import { Auth } from '../../model/auth';
import { Status } from '../../constants/auth';
import { AuthDocument } from '../../schema/auth';
export const readAll = async (query: FilterQuery<AuthDocument> = {}) => Auth.find(query);

View File

@@ -1,6 +1,7 @@
import { Types } from 'mongoose';
import { STRATEGIES } from '../../constants/strategies';
import { STRATEGIES } from '@mifi/auth-common/lib/enums/strategies';
import { Strategy } from '../../model/strategy';
export const readOneByParentAndMethod = async (parent: Types.ObjectId, method: STRATEGIES) =>

View File

@@ -1,10 +1,11 @@
import { Document, InferSchemaType, Model, Schema, StringSchemaDefinition, Types } from 'mongoose';
import { Status } from '../constants/auth';
import { Status } from '@mifi/auth-common/lib/enums/status';
import { STRATEGIES } from '@mifi/auth-common/lib/enums/strategies';
import { COLL_STRATEGY } from '../constants/db';
import { STRATEGIES } from '../constants/strategies';
import { StrategyDocument } from './strategy';
import { verify } from '../utils/password';
import { StrategyDocument } from './strategy';
export interface Auth {
handle?: string;

View File

@@ -1,9 +1,8 @@
import { InferSchemaType, Model, Schema, StringSchemaDefinition, Types } from 'mongoose';
import { Action } from '@mifi/auth-common/lib/enums/action';
import { Payload } from '@mifi/services-common/lib/types/Payload';
import { Action } from '../constants/action';
export interface Log {
action: Action;
auth: StringSchemaDefinition;

View File

@@ -1,6 +1,7 @@
import { Document, InferSchemaType, Model, Schema, StringSchemaDefinition, Types } from 'mongoose';
import { STRATEGIES } from '../constants/strategies';
import { STRATEGIES } from '@mifi/auth-common/lib/enums/strategies';
import { encrypt } from '../utils/password';
import { COLL_AUTH } from '../constants/db';
import { AuthDocument } from './auth';

View File

@@ -1,8 +1,9 @@
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 '@mifi/auth-common/lib/enums/tokens';
import { getDefaultExpiresFor } from '@mifi/auth-common/lib/helpers/getDefaultExpiresFor';
import { sign, verify } from '@mifi/auth-common/lib/utils/jwt';
import { SignProps } from '@mifi/auth-common/lib/utils/jwt/sign';
export interface Token {
auth: StringSchemaDefinition;
@@ -50,7 +51,7 @@ TokenSchema.statics = {
return sign({
sub: `${doc._id}`,
exp: doc.expires,
});
} as SignProps);
},
async validateResetToken(token: string) {

View File

@@ -1,15 +0,0 @@
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.VERIFICATION) {
return Date.now() + parseTimeoutToMs(VERIFY_VALID_TIMEOUT);
}
return Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT);
};

View File

@@ -1,11 +0,0 @@
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: <string>sub,
status,
exp: Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT),
});

View File

@@ -1,35 +0,0 @@
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;
}
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,
);
};
export const verify = (token: string) => jwt.verify(token, JWT_SECRET);

View File

@@ -1,5 +0,0 @@
import { RESET_ROUTE, ROUTE_PREFIX, VERIFICATION_ROUTE } from '../constants/env';
export const getPasswordResetPath = (token: string) => `${ROUTE_PREFIX}${RESET_ROUTE}?t=${token}`;
export const getVerificationPath = (token: string) => `${ROUTE_PREFIX}${VERIFICATION_ROUTE}?t=${token}`;

View File

@@ -1,13 +0,0 @@
export const parseTimeoutToMs = (timeout: string) => {
const match = timeout.match(/(?<number>\d+)(?<unit>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;
}
};

View File

@@ -5,8 +5,3 @@ export const encrypt = (password: string) => {
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;
};

View File

@@ -0,0 +1,4 @@
import { encrypt } from './encrypt';
import { verify } from './verify';
export { encrypt, verify };

View File

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

View File

@@ -1,11 +0,0 @@
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),
});