Restructuring the folders #1
@@ -1,17 +1,17 @@
|
|||||||
import { Auth, Log } from '..';
|
import { Auth, Log } from "..";
|
||||||
import { Action } from '../constants/action';
|
import { Action } from "../constants/action";
|
||||||
import { getLoginToken } from '../utils/getLoginToken';
|
import { getLoginToken } from "../utils/getLoginToken";
|
||||||
|
|
||||||
export const authenticate = async (username: string, password: string) => {
|
export const authenticate = async (username: string, password: string) => {
|
||||||
const doc = await Auth.findByUsername(username).catch();
|
const doc = await Auth.findByUsername(username).catch();
|
||||||
if (!!doc && (await doc.authenticate(password))) {
|
if (!!doc && (await doc.authenticate(password))) {
|
||||||
Log.add(doc.id, Action.AUTHENTICATE);
|
Log.add(doc.id, Action.AUTHENTICATE);
|
||||||
return { ...doc, token: getLoginToken(doc) };
|
return { ...doc, token: getLoginToken(doc) };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc) {
|
if (doc) {
|
||||||
Log.add(doc.id, Action.AUTHENTICATE_FAILURE);
|
Log.add(doc.id, Action.AUTHENTICATE_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { StringSchemaDefinition } from 'mongoose';
|
import { StringSchemaDefinition } from "mongoose";
|
||||||
import { Auth, Strategy } from '..';
|
import { Auth, Strategy } from "..";
|
||||||
|
|
||||||
export const deleteStrategy = async (id: StringSchemaDefinition) => {
|
export const deleteStrategy = async (id: StringSchemaDefinition) => {
|
||||||
const strategy = await Strategy.findById(id);
|
const strategy = await Strategy.findById(id);
|
||||||
|
|
||||||
if (strategy) {
|
if (strategy) {
|
||||||
const parentId = strategy.parent;
|
const parentId = strategy.parent;
|
||||||
await strategy.deleteOne();
|
await strategy.deleteOne();
|
||||||
await Auth.findOneAndUpdate({ id: parentId, strategies: { $pull: id } });
|
await Auth.findOneAndUpdate({ id: parentId, strategies: { $pull: id } });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { readOneByUsername } from '../dao/readOneByUsername';
|
import { readOneByUsername } from "../dao/readOneByUsername";
|
||||||
import { Log, Token } from '..';
|
import { Log, Token } from "..";
|
||||||
import { TokenType } from '../constants/tokens';
|
import { TokenType } from "../constants/tokens";
|
||||||
import { Action } from '../constants/action';
|
import { Action } from "../constants/action";
|
||||||
|
|
||||||
export const resetPasswordGet = async (username: string) => {
|
export const resetPasswordGet = async (username: string) => {
|
||||||
const doc = await readOneByUsername(username);
|
const doc = await readOneByUsername(username);
|
||||||
|
|
||||||
if (doc) {
|
if (doc) {
|
||||||
Log.add(doc._id, Action.RESET_REQUEST);
|
Log.add(doc._id, Action.RESET_REQUEST);
|
||||||
return {
|
return {
|
||||||
record: doc.record,
|
record: doc.record,
|
||||||
token: Token.getToken(TokenType.RESET, doc._id),
|
token: Token.getToken(TokenType.RESET, doc._id),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,38 +1,41 @@
|
|||||||
import { Types } from 'mongoose';
|
import { Types } from "mongoose";
|
||||||
|
|
||||||
import { Log, Strategy, Token } from '..';
|
import { Log, Strategy, Token } from "..";
|
||||||
import { STRATEGIES } from '../constants/strategies';
|
import { STRATEGIES } from "../constants/strategies";
|
||||||
import { AuthDocument } from '../schema/auth';
|
import { AuthDocument } from "../schema/auth";
|
||||||
import { getLoginToken } from '../utils/getLoginToken';
|
import { getLoginToken } from "../utils/getLoginToken";
|
||||||
import { StrategyDocument } from '../schema/strategy';
|
import { StrategyDocument } from "../schema/strategy";
|
||||||
import { Action } from '../constants/action';
|
import { Action } from "../constants/action";
|
||||||
|
|
||||||
export const resetPasswordPost = async (token: string, password: string) => {
|
export const resetPasswordPost = async (token: string, password: string) => {
|
||||||
const parentId = await Token.validateResetToken(token);
|
const parentId = await Token.validateResetToken(token);
|
||||||
|
|
||||||
if (parentId) {
|
if (parentId) {
|
||||||
let parent: AuthDocument;
|
let parent: AuthDocument;
|
||||||
let strategy: StrategyDocument | null = await Strategy.findOne({ parent: parentId, method: STRATEGIES.LOCAL });
|
let strategy: StrategyDocument | null = await Strategy.findOne({
|
||||||
|
parent: parentId,
|
||||||
|
method: STRATEGIES.LOCAL,
|
||||||
|
});
|
||||||
|
|
||||||
if (strategy) {
|
if (strategy) {
|
||||||
parent = await strategy.getAuthRecord();
|
parent = await strategy.getAuthRecord();
|
||||||
strategy.key = password;
|
strategy.key = password;
|
||||||
await strategy.save();
|
await strategy.save();
|
||||||
} else {
|
} else {
|
||||||
strategy = await Strategy.create({
|
strategy = await Strategy.create({
|
||||||
key: password,
|
key: password,
|
||||||
method: STRATEGIES.LOCAL,
|
method: STRATEGIES.LOCAL,
|
||||||
parent: <Types.ObjectId>parentId,
|
parent: <Types.ObjectId>parentId,
|
||||||
});
|
});
|
||||||
|
|
||||||
parent = await strategy.getAuthRecord();
|
parent = await strategy.getAuthRecord();
|
||||||
parent.strategies.push(strategy._id);
|
parent.strategies.push(strategy._id);
|
||||||
await parent.save();
|
await parent.save();
|
||||||
}
|
|
||||||
|
|
||||||
Log.add(parent._id, Action.RESET);
|
|
||||||
return { record: parent.record, token: getLoginToken(parent) };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
Log.add(parent._id, Action.RESET);
|
||||||
|
return { record: parent.record, token: getLoginToken(parent) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export enum Action {
|
export enum Action {
|
||||||
AUTHENTICATE = 'AUTHENTICATE',
|
AUTHENTICATE = "AUTHENTICATE",
|
||||||
AUTHENTICATE_FAILURE = 'AUTHENTICATE_FAILURE',
|
AUTHENTICATE_FAILURE = "AUTHENTICATE_FAILURE",
|
||||||
CREATE = 'CREATE',
|
CREATE = "CREATE",
|
||||||
DELETE = 'DELETE',
|
DELETE = "DELETE",
|
||||||
RESET = 'RESET',
|
RESET = "RESET",
|
||||||
RESET_REQUEST = 'RESET_REQUEST',
|
RESET_REQUEST = "RESET_REQUEST",
|
||||||
UPDATE = 'UPDATE',
|
UPDATE = "UPDATE",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
export enum Status {
|
export enum Status {
|
||||||
ACTIVE,
|
ACTIVE,
|
||||||
BLOCK_HARD,
|
BLOCK_HARD,
|
||||||
BLOCK_SOFT,
|
BLOCK_SOFT,
|
||||||
DELETED,
|
DELETED,
|
||||||
INACTIVE,
|
INACTIVE,
|
||||||
UNVERIFIED,
|
UNVERIFIED,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export const DB_USERNAME = process.env.DB_USERNAME;
|
|||||||
export const DB_PASSWORD = process.env.DB_PASSWORD;
|
export const DB_PASSWORD = process.env.DB_PASSWORD;
|
||||||
export const DB_NAME = process.env.DB_NAME;
|
export const DB_NAME = process.env.DB_NAME;
|
||||||
|
|
||||||
export const COLL_AUTH = 'Auth';
|
export const COLL_AUTH = "Auth";
|
||||||
export const COLL_LOG = 'Log';
|
export const COLL_LOG = "Log";
|
||||||
export const COLL_STRATEGY = 'Strategy';
|
export const COLL_STRATEGY = "Strategy";
|
||||||
export const COLL_TOKEN = 'Token';
|
export const COLL_TOKEN = "Token";
|
||||||
|
|||||||
@@ -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 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_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 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 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 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 DEFAULT_TOKEN_DAYS = process.env.DEFAULT_TOKEN_DAYS || 365;
|
||||||
|
|
||||||
export const ROUTE_PREFIX = process.env.ROUTE_PREFIX || '/auth';
|
export const ROUTE_PREFIX = process.env.ROUTE_PREFIX || "/auth";
|
||||||
export const LOGIN_ROUTE = process.env.LOGIN_ROUTE || '/login';
|
export const LOGIN_ROUTE = process.env.LOGIN_ROUTE || "/login";
|
||||||
export const RESET_ROUTE = process.env.RESET_ROUTE || '/reset';
|
export const RESET_ROUTE = process.env.RESET_ROUTE || "/reset";
|
||||||
export const VERIFICATION_ROUTE = process.env.VERIFICATION_ROUTE || '/verification';
|
export const VERIFICATION_ROUTE =
|
||||||
|
process.env.VERIFICATION_ROUTE || "/verification";
|
||||||
|
|
||||||
export const REQUIRE_VERIFICATION = process.env.REQUIRE_VERIFICATION || true;
|
export const REQUIRE_VERIFICATION = process.env.REQUIRE_VERIFICATION || true;
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
export enum ErrorCodes {
|
export enum ErrorCodes {
|
||||||
RESET_REQUEST_DATA = 'RESET_REQUEST_DATA',
|
RESET_REQUEST_DATA = "RESET_REQUEST_DATA",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ErrorMessages = {
|
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) => ({
|
export const getErrorBody = (code: ErrorCodes) => ({
|
||||||
code,
|
code,
|
||||||
message: ErrorMessages[code],
|
message: ErrorMessages[code],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export enum STRATEGIES {
|
export enum STRATEGIES {
|
||||||
LOCAL,
|
LOCAL,
|
||||||
APPLE,
|
APPLE,
|
||||||
FACEBOOK,
|
FACEBOOK,
|
||||||
FIDO2,
|
FIDO2,
|
||||||
GOOGLE,
|
GOOGLE,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export enum TokenType {
|
export enum TokenType {
|
||||||
RESET = 'RESET',
|
RESET = "RESET",
|
||||||
VERIFICATION = 'VERIFICATION',
|
VERIFICATION = "VERIFICATION",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, Log, Strategy, Token } from "..";
|
||||||
import { Auth as AuthProps } from '../schema/auth';
|
import { Auth as AuthProps } from "../schema/auth";
|
||||||
import { STRATEGIES } from '../constants/strategies';
|
import { STRATEGIES } from "../constants/strategies";
|
||||||
import { REQUIRE_VERIFICATION } from '../constants/env';
|
import { REQUIRE_VERIFICATION } from "../constants/env";
|
||||||
import { TokenType } from '../constants/tokens';
|
import { TokenType } from "../constants/tokens";
|
||||||
import { Status } from '../constants/auth';
|
import { Status } from "../constants/auth";
|
||||||
import { Action } from '../constants/action';
|
import { Action } from "../constants/action";
|
||||||
|
|
||||||
export const create = async ({ record, username, password }: AuthProps & { password: string }) => {
|
export const create = async ({
|
||||||
const status = REQUIRE_VERIFICATION ? Status.UNVERIFIED : Status.ACTIVE;
|
record,
|
||||||
const doc = await Auth.create({
|
username,
|
||||||
record,
|
password,
|
||||||
status,
|
}: AuthProps & { password: string }) => {
|
||||||
username,
|
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) => {
|
}).catch((err) => {
|
||||||
throw new DatabaseError('failed to create user', { err });
|
throw new DatabaseError("failed to create strategy", { err });
|
||||||
});
|
});
|
||||||
if (doc) {
|
if (strategy) {
|
||||||
const strategy = await Strategy.create({ method: STRATEGIES.LOCAL, key: password, parent: doc._id }).catch(
|
doc.strategies.push(strategy._id);
|
||||||
(err) => {
|
await doc.save();
|
||||||
throw new DatabaseError('failed to create strategy', { err });
|
Log.add(doc._id, Action.CREATE);
|
||||||
},
|
return {
|
||||||
);
|
doc,
|
||||||
if (strategy) {
|
token:
|
||||||
doc.strategies.push(strategy._id);
|
REQUIRE_VERIFICATION &&
|
||||||
await doc.save();
|
(await Token.getToken(TokenType.VERIFICATION, doc._id)),
|
||||||
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 });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return null;
|
await doc.deleteOne((err) => {
|
||||||
|
throw new DatabaseError("failed to remove invalid auth record", {
|
||||||
|
err,
|
||||||
|
doc,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import { StringSchemaDefinition } from 'mongoose';
|
import { StringSchemaDefinition } from "mongoose";
|
||||||
|
|
||||||
import { Auth, Log, Strategy, Token } from '..';
|
import { Auth, Log, Strategy, Token } from "..";
|
||||||
import { Status } from '../constants/auth';
|
import { Status } from "../constants/auth";
|
||||||
import { Action } from '../constants/action';
|
import { Action } from "../constants/action";
|
||||||
|
|
||||||
export const deleteById = async (id: StringSchemaDefinition) => {
|
export const deleteById = async (id: StringSchemaDefinition) => {
|
||||||
if (await Auth.findByIdAndUpdate(id, { status: Status.DELETED, strategies: [] }).catch()) {
|
if (
|
||||||
await Strategy.deleteMany({ parent: id });
|
await Auth.findByIdAndUpdate(id, {
|
||||||
await Token.deleteMany({ auth: id });
|
status: Status.DELETED,
|
||||||
Log.add(id, Action.DELETE);
|
strategies: [],
|
||||||
return true;
|
}).catch()
|
||||||
}
|
) {
|
||||||
return false;
|
await Strategy.deleteMany({ parent: id });
|
||||||
|
await Token.deleteMany({ auth: id });
|
||||||
|
Log.add(id, Action.DELETE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { FilterQuery } from 'mongoose';
|
import { FilterQuery } from "mongoose";
|
||||||
|
|
||||||
import { Auth } from '../model/auth';
|
import { Auth } from "../model/auth";
|
||||||
import { Status } from '../constants/auth';
|
import { Status } from "../constants/auth";
|
||||||
import { AuthDocument } from '../schema/auth';
|
import { AuthDocument } from "../schema/auth";
|
||||||
|
|
||||||
export const readAll = async (query: FilterQuery<AuthDocument> = {}) => Auth.find(query);
|
export const readAll = async (query: FilterQuery<AuthDocument> = {}) =>
|
||||||
|
Auth.find(query);
|
||||||
|
|
||||||
export const readAllActive = async () => readAll({ status: { $ne: Status.DELETED } });
|
export const readAllActive = async () =>
|
||||||
|
readAll({ status: { $ne: Status.DELETED } });
|
||||||
|
|||||||
@@ -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);
|
export const readOneById = async (id: Types.ObjectId) => Auth.findById(id);
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
34
src/index.ts
34
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 {
|
||||||
import { Auth } from './model/auth';
|
DB_HOST,
|
||||||
import { Log } from './model/log';
|
DB_NAME,
|
||||||
import { Strategy } from './model/strategy';
|
DB_PASSWORD,
|
||||||
import { Token } from './model/token';
|
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
|
const connection = mongoose
|
||||||
.connect(`mongodb://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}`)
|
.connect(
|
||||||
.then((c) => console.debug('Database connection established', { connection: c }))
|
`mongodb://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}`
|
||||||
.catch((error) => {
|
)
|
||||||
console.error('Mongo connection failure', error);
|
.then((c) =>
|
||||||
process.exit(1);
|
console.debug("Database connection established", { connection: c })
|
||||||
});
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Mongo connection failure", error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
export { connection, Auth, Log, Strategy, Token };
|
export { connection, Auth, Log, Strategy, Token };
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from "mongoose";
|
||||||
|
|
||||||
import { AuthDocument, AuthModel, AuthSchema } from '../schema/auth';
|
import { AuthDocument, AuthModel, AuthSchema } from "../schema/auth";
|
||||||
import { COLL_AUTH } from '../constants/db';
|
import { COLL_AUTH } from "../constants/db";
|
||||||
|
|
||||||
export const Auth = mongoose.model<AuthDocument, AuthModel>(COLL_AUTH, AuthSchema);
|
export const Auth = mongoose.model<AuthDocument, AuthModel>(
|
||||||
|
COLL_AUTH,
|
||||||
|
AuthSchema
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from "mongoose";
|
||||||
|
|
||||||
import { LogModel, Log as LogDocument, LogSchema } from '../schema/log';
|
import { LogModel, Log as LogDocument, LogSchema } from "../schema/log";
|
||||||
import { COLL_LOG } from '../constants/db';
|
import { COLL_LOG } from "../constants/db";
|
||||||
|
|
||||||
export const Log = mongoose.model<LogDocument, LogModel>(COLL_LOG, LogSchema);
|
export const Log = mongoose.model<LogDocument, LogModel>(COLL_LOG, LogSchema);
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from "mongoose";
|
||||||
|
|
||||||
import { StrategyDocument, StrategyModel, StrategySchema } from '../schema/strategy';
|
import {
|
||||||
import { COLL_STRATEGY } from '../constants/db';
|
StrategyDocument,
|
||||||
|
StrategyModel,
|
||||||
|
StrategySchema,
|
||||||
|
} from "../schema/strategy";
|
||||||
|
import { COLL_STRATEGY } from "../constants/db";
|
||||||
|
|
||||||
export const Strategy = mongoose.model<StrategyDocument, StrategyModel>(COLL_STRATEGY, StrategySchema);
|
export const Strategy = mongoose.model<StrategyDocument, StrategyModel>(
|
||||||
|
COLL_STRATEGY,
|
||||||
|
StrategySchema
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from "mongoose";
|
||||||
|
|
||||||
import { TokenModel, Token as TokenDocument, TokenSchema } from '../schema/token';
|
import {
|
||||||
import { COLL_TOKEN } from '../constants/db';
|
TokenModel,
|
||||||
|
Token as TokenDocument,
|
||||||
|
TokenSchema,
|
||||||
|
} from "../schema/token";
|
||||||
|
import { COLL_TOKEN } from "../constants/db";
|
||||||
|
|
||||||
export const Token = mongoose.model<TokenDocument, TokenModel>(COLL_TOKEN, TokenSchema);
|
export const Token = mongoose.model<TokenDocument, TokenModel>(
|
||||||
|
COLL_TOKEN,
|
||||||
|
TokenSchema
|
||||||
|
);
|
||||||
|
|||||||
@@ -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 { Status } from "../constants/auth";
|
||||||
import { COLL_STRATEGY } from '../constants/db';
|
import { COLL_STRATEGY } from "../constants/db";
|
||||||
import { STRATEGIES } from '../constants/strategies';
|
import { STRATEGIES } from "../constants/strategies";
|
||||||
import { StrategyDocument } from './strategy';
|
import { StrategyDocument } from "./strategy";
|
||||||
import { verify } from '../utils/password';
|
import { verify } from "../utils/password";
|
||||||
|
|
||||||
export interface Auth {
|
export interface Auth {
|
||||||
is2FA?: boolean;
|
is2FA?: boolean;
|
||||||
record: StringSchemaDefinition;
|
record: StringSchemaDefinition;
|
||||||
username: string;
|
username: string;
|
||||||
status: Status;
|
status: Status;
|
||||||
strategies: Types.ObjectId[] | StrategyDocument[];
|
strategies: Types.ObjectId[] | StrategyDocument[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthBaseDocument extends Auth, Document {
|
interface AuthBaseDocument extends Auth, Document {
|
||||||
authenticate(password: string): Promise<boolean>;
|
authenticate(password: string): Promise<boolean>;
|
||||||
getStrategy(method?: STRATEGIES): Promise<StrategyDocument | null>;
|
getStrategy(method?: STRATEGIES): Promise<StrategyDocument | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthDocument extends AuthBaseDocument {
|
export interface AuthDocument extends AuthBaseDocument {
|
||||||
strategies: Types.ObjectId[];
|
strategies: Types.ObjectId[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthPopulatedDocument extends AuthBaseDocument {
|
export interface AuthPopulatedDocument extends AuthBaseDocument {
|
||||||
strategies: StrategyDocument[];
|
strategies: StrategyDocument[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthModel extends Model<AuthDocument> {
|
export interface AuthModel extends Model<AuthDocument> {
|
||||||
findByUsername(username: string): Promise<AuthDocument>;
|
findByUsername(username: string): Promise<AuthDocument>;
|
||||||
getLocalStrategyForUsername(username: string): Promise<StrategyDocument>;
|
getLocalStrategyForUsername(username: string): Promise<StrategyDocument>;
|
||||||
isUsernameAvailable(username: string): Promise<boolean>;
|
isUsernameAvailable(username: string): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthSchema = new Schema<AuthDocument, AuthModel>(
|
export const AuthSchema = new Schema<AuthDocument, AuthModel>(
|
||||||
{
|
{
|
||||||
is2FA: { type: Boolean, default: false },
|
is2FA: { type: Boolean, default: false },
|
||||||
record: { type: Types.ObjectId, unique: true },
|
record: { type: Types.ObjectId, unique: true },
|
||||||
status: { type: Number, enum: Status, default: Status.UNVERIFIED, index: true },
|
status: {
|
||||||
strategies: [{ type: Types.ObjectId, ref: COLL_STRATEGY, default: [] }],
|
type: Number,
|
||||||
username: { type: String, required: true, unique: true },
|
enum: Status,
|
||||||
},
|
default: Status.UNVERIFIED,
|
||||||
{
|
index: 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) {
|
AuthSchema.methods.authenticate = async function (
|
||||||
const strategy = await this.getStrategy();
|
this: AuthBaseDocument,
|
||||||
return !!strategy && verify(password, strategy.key);
|
password: string
|
||||||
|
) {
|
||||||
|
const strategy = await this.getStrategy();
|
||||||
|
return !!strategy && verify(password, strategy.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthSchema.methods.getStrategy = async function (this: AuthBaseDocument, method = STRATEGIES.LOCAL) {
|
AuthSchema.methods.getStrategy = async function (
|
||||||
const doc = await this.populate<{ strategies: StrategyDocument[] }>('strategies');
|
this: AuthBaseDocument,
|
||||||
return doc.strategies.filter((strategy) => strategy.method === method).pop() || null;
|
method = STRATEGIES.LOCAL
|
||||||
|
) {
|
||||||
|
const doc = await this.populate<{ strategies: StrategyDocument[] }>(
|
||||||
|
"strategies"
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
doc.strategies.filter((strategy) => strategy.method === method).pop() ||
|
||||||
|
null
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthSchema.statics = {
|
AuthSchema.statics = {
|
||||||
async findByUsername(username) {
|
async findByUsername(username) {
|
||||||
return this.findOne({ username });
|
return this.findOne({ username });
|
||||||
},
|
},
|
||||||
|
|
||||||
async getLocalStrategyForUsername(username) {
|
async getLocalStrategyForUsername(username) {
|
||||||
const doc = await this.findByUsername(username);
|
const doc = await this.findByUsername(username);
|
||||||
return !!doc && doc.getStrategy();
|
return !!doc && doc.getStrategy();
|
||||||
},
|
},
|
||||||
|
|
||||||
async isUsernameAvailable(username) {
|
async isUsernameAvailable(username) {
|
||||||
return !this.findByUsername(username);
|
return !this.findByUsername(username);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AuthSchema = InferSchemaType<typeof AuthSchema>;
|
export type AuthSchema = InferSchemaType<typeof AuthSchema>;
|
||||||
|
|||||||
@@ -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 {
|
export interface Log {
|
||||||
action: Action;
|
action: Action;
|
||||||
auth: StringSchemaDefinition;
|
auth: StringSchemaDefinition;
|
||||||
payload?: Payload;
|
payload?: Payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LogModel extends Model<Log> {
|
export interface LogModel extends Model<Log> {
|
||||||
add(id: StringSchemaDefinition, action: Action, payload?: Payload): void;
|
add(id: StringSchemaDefinition, action: Action, payload?: Payload): void;
|
||||||
historyForUser(id: StringSchemaDefinition, action?: Action): Array<Log>;
|
historyForUser(id: StringSchemaDefinition, action?: Action): Array<Log>;
|
||||||
loginsForUser(id: StringSchemaDefinition): Array<Log>;
|
loginsForUser(id: StringSchemaDefinition): Array<Log>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LogSchema = new Schema<Log, LogModel>(
|
export const LogSchema = new Schema<Log, LogModel>(
|
||||||
{
|
{
|
||||||
action: { type: String, enum: Action, required: true },
|
action: { type: String, enum: Action, required: true },
|
||||||
auth: { type: Types.ObjectId, index: true, required: true },
|
auth: { type: Types.ObjectId, index: true, required: true },
|
||||||
payload: { type: Object },
|
payload: { type: Object },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
minimize: true,
|
minimize: true,
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
LogSchema.statics = {
|
LogSchema.statics = {
|
||||||
add(id, action, payload) {
|
add(id, action, payload) {
|
||||||
this.create({ action, auth: id, payload }).catch();
|
this.create({ action, auth: id, payload }).catch();
|
||||||
},
|
},
|
||||||
|
|
||||||
async historyForUser(id, action) {
|
async historyForUser(id, action) {
|
||||||
return this.find({ auth: id, action });
|
return this.find({ auth: id, action });
|
||||||
},
|
},
|
||||||
|
|
||||||
async loginsForUser(id) {
|
async loginsForUser(id) {
|
||||||
return this.find({ auth: id, action: Action.AUTHENTICATE });
|
return this.find({ auth: id, action: Action.AUTHENTICATE });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LogSchema = InferSchemaType<typeof LogSchema>;
|
export type LogSchema = InferSchemaType<typeof LogSchema>;
|
||||||
|
|||||||
@@ -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 { STRATEGIES } from "../constants/strategies";
|
||||||
import { encrypt } from '../utils/password';
|
import { encrypt } from "../utils/password";
|
||||||
import { COLL_AUTH } from '../constants/db';
|
import { COLL_AUTH } from "../constants/db";
|
||||||
import { AuthDocument } from './auth';
|
import { AuthDocument } from "./auth";
|
||||||
import { Strategy } from '..';
|
import { Strategy } from "..";
|
||||||
|
|
||||||
export interface Strategy {
|
export interface Strategy {
|
||||||
method: STRATEGIES;
|
method: STRATEGIES;
|
||||||
parent: StringSchemaDefinition | AuthDocument;
|
parent: StringSchemaDefinition | AuthDocument;
|
||||||
externalId?: string;
|
externalId?: string;
|
||||||
key: string;
|
key: string;
|
||||||
profile?: { [key: string]: string | boolean | number };
|
profile?: { [key: string]: string | boolean | number };
|
||||||
forceReset?: boolean;
|
forceReset?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StrategyBaseDocument extends Strategy, Document {
|
interface StrategyBaseDocument extends Strategy, Document {
|
||||||
getAuthRecord(): Promise<AuthDocument>;
|
getAuthRecord(): Promise<AuthDocument>;
|
||||||
getPopulatedStrategy(): Promise<StrategyPopulatedDocument>;
|
getPopulatedStrategy(): Promise<StrategyPopulatedDocument>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StrategyDocument extends StrategyBaseDocument {
|
export interface StrategyDocument extends StrategyBaseDocument {
|
||||||
parent: StringSchemaDefinition;
|
parent: StringSchemaDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StrategyPopulatedDocument extends StrategyBaseDocument {
|
export interface StrategyPopulatedDocument extends StrategyBaseDocument {
|
||||||
parent: AuthDocument;
|
parent: AuthDocument;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StrategyModel = Model<StrategyDocument>;
|
export type StrategyModel = Model<StrategyDocument>;
|
||||||
|
|
||||||
export const StrategySchema = new Schema<StrategyDocument, StrategyModel>(
|
export const StrategySchema = new Schema<StrategyDocument, StrategyModel>(
|
||||||
{
|
{
|
||||||
method: {
|
method: {
|
||||||
type: Number,
|
type: Number,
|
||||||
enum: STRATEGIES,
|
enum: STRATEGIES,
|
||||||
index: true,
|
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 },
|
||||||
minimize: true,
|
forceReset: { type: Boolean },
|
||||||
timestamps: true,
|
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) {
|
StrategySchema.methods.getPopulatedStrategy = async function (
|
||||||
return this.populate<StrategyPopulatedDocument>('parent');
|
this: StrategyDocument
|
||||||
|
) {
|
||||||
|
return this.populate<StrategyPopulatedDocument>("parent");
|
||||||
};
|
};
|
||||||
|
|
||||||
StrategySchema.methods.getAuthRecord = async function (this: StrategyDocument) {
|
StrategySchema.methods.getAuthRecord = async function (this: StrategyDocument) {
|
||||||
return (await this.getPopulatedStrategy()).parent;
|
return (await this.getPopulatedStrategy()).parent;
|
||||||
};
|
};
|
||||||
|
|
||||||
StrategySchema.pre('save', async function save(next) {
|
StrategySchema.pre("save", async function save(next) {
|
||||||
if (typeof this.method === 'undefined') {
|
if (typeof this.method === "undefined") {
|
||||||
return next(new Error(`Strategy requires a method.`));
|
return next(new Error(`Strategy requires a method.`));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await Strategy.findOne({ method: this.method, parent: this.parent })) {
|
if (await Strategy.findOne({ method: this.method, parent: this.parent })) {
|
||||||
return next(new Error(`${this.method} strategy already exists for this user.`));
|
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();
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.key = encrypt(this.key);
|
||||||
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
export type StrategySchema = InferSchemaType<typeof StrategySchema>;
|
export type StrategySchema = InferSchemaType<typeof StrategySchema>;
|
||||||
|
|||||||
@@ -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 { TokenType } from "../constants/tokens";
|
||||||
import { getDefaultExpiresFor } from '../utils/getDefaultExpiresFor';
|
import { getDefaultExpiresFor } from "../utils/getDefaultExpiresFor";
|
||||||
import { sign, verify } from '../utils/jwt';
|
import { sign, verify } from "../utils/jwt";
|
||||||
|
|
||||||
export interface Token {
|
export interface Token {
|
||||||
auth: StringSchemaDefinition;
|
auth: StringSchemaDefinition;
|
||||||
expires?: number;
|
expires?: number;
|
||||||
type: TokenType;
|
type: TokenType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenModel extends Model<Token> {
|
export interface TokenModel extends Model<Token> {
|
||||||
cleanupExpiredTokens(): { success: boolean; deletedCount: number };
|
cleanupExpiredTokens(): { success: boolean; deletedCount: number };
|
||||||
getToken(type: TokenType, auth: Types.ObjectId, expires?: number): string;
|
getToken(type: TokenType, auth: Types.ObjectId, expires?: number): string;
|
||||||
validateResetToken(token: string): Types.ObjectId | false;
|
validateResetToken(token: string): Types.ObjectId | false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TokenSchema = new Schema<Token, TokenModel>(
|
export const TokenSchema = new Schema<Token, TokenModel>(
|
||||||
{
|
{
|
||||||
auth: { type: Types.ObjectId, index: true },
|
auth: { type: Types.ObjectId, index: true },
|
||||||
expires: { type: Number, required: true },
|
expires: { type: Number, required: true },
|
||||||
type: { type: String, enum: TokenType, required: true },
|
type: { type: String, enum: TokenType, required: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
minimize: true,
|
minimize: true,
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
TokenSchema.statics = {
|
TokenSchema.statics = {
|
||||||
async cleanupExpiredTokens() {
|
async cleanupExpiredTokens() {
|
||||||
const { acknowledged, deletedCount } = await this.deleteMany({ expires: { $lte: Date.now() } });
|
const { acknowledged, deletedCount } = await this.deleteMany({
|
||||||
return { success: acknowledged, deletedCount };
|
expires: { $lte: Date.now() },
|
||||||
},
|
});
|
||||||
|
return { success: acknowledged, deletedCount };
|
||||||
|
},
|
||||||
|
|
||||||
async getToken(type: TokenType, auth: StringSchemaDefinition, expires?: number) {
|
async getToken(
|
||||||
const existing = await this.findOne({ type, auth });
|
type: TokenType,
|
||||||
if (existing) {
|
auth: StringSchemaDefinition,
|
||||||
await existing.deleteOne();
|
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) });
|
const doc = await this.create({
|
||||||
return sign({
|
type,
|
||||||
sub: `${doc._id}`,
|
auth,
|
||||||
exp: doc.expires,
|
expires: expires || getDefaultExpiresFor(type),
|
||||||
});
|
});
|
||||||
},
|
return sign({
|
||||||
|
sub: `${doc._id}`,
|
||||||
|
exp: doc.expires,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
async validateResetToken(token: string) {
|
async validateResetToken(token: string) {
|
||||||
const { sub } = verify(token);
|
const { sub } = verify(token);
|
||||||
|
|
||||||
if (sub) {
|
if (sub) {
|
||||||
const record = await this.findById(sub);
|
const record = await this.findById(sub);
|
||||||
if (record) {
|
if (record) {
|
||||||
await record.deleteOne();
|
await record.deleteOne();
|
||||||
return !!record?.expires && record.expires >= Date.now() && record.auth;
|
return !!record?.expires && record.expires >= Date.now() && record.auth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TokenSchema = InferSchemaType<typeof TokenSchema>;
|
export type TokenSchema = InferSchemaType<typeof TokenSchema>;
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import { LOGIN_VALID_TIMEOUT, RESET_VALID_TIMEOUT, VERIFY_VALID_TIMEOUT } from '../constants/env';
|
import {
|
||||||
import { TokenType } from '../constants/tokens';
|
LOGIN_VALID_TIMEOUT,
|
||||||
import { parseTimeoutToMs } from '../utils/parseTimeoutToMs';
|
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) => {
|
export const getDefaultExpiresFor = (type: TokenType | void) => {
|
||||||
if (type === TokenType.RESET) {
|
if (type === TokenType.RESET) {
|
||||||
return Date.now() + parseTimeoutToMs(RESET_VALID_TIMEOUT);
|
return Date.now() + parseTimeoutToMs(RESET_VALID_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === TokenType.VERIFICATION) {
|
if (type === TokenType.VERIFICATION) {
|
||||||
return Date.now() + parseTimeoutToMs(VERIFY_VALID_TIMEOUT);
|
return Date.now() + parseTimeoutToMs(VERIFY_VALID_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT);
|
return Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { sign } from '../utils/jwt';
|
import { sign } from "../utils/jwt";
|
||||||
import { LOGIN_VALID_TIMEOUT } from '../constants/env';
|
import { LOGIN_VALID_TIMEOUT } from "../constants/env";
|
||||||
import { parseTimeoutToMs } from '../utils/parseTimeoutToMs';
|
import { parseTimeoutToMs } from "../utils/parseTimeoutToMs";
|
||||||
import { AuthDocument } from '../schema/auth';
|
import { AuthDocument } from "../schema/auth";
|
||||||
|
|
||||||
export const getLoginToken = ({ record: sub, status }: AuthDocument) =>
|
export const getLoginToken = ({ record: sub, status }: AuthDocument) =>
|
||||||
sign({
|
sign({
|
||||||
sub: <string>sub,
|
sub: <string>sub,
|
||||||
status,
|
status,
|
||||||
exp: Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT),
|
exp: Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,35 +1,39 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from "jsonwebtoken";
|
||||||
import { JWT_AUDIENCE, JWT_ISSUER, JWT_SECRET } from '../constants/env';
|
import { JWT_AUDIENCE, JWT_ISSUER, JWT_SECRET } from "../constants/env";
|
||||||
export interface TokenProps {
|
export interface TokenProps {
|
||||||
aud?: string;
|
aud?: string;
|
||||||
exp?: number | Date;
|
exp?: number | Date;
|
||||||
iss?: string;
|
iss?: string;
|
||||||
sub: string | null;
|
sub: string | null;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SignProps = string | TokenProps | void;
|
export type SignProps = string | TokenProps | void;
|
||||||
|
|
||||||
export const sign = (props: SignProps) => {
|
export const sign = (props: SignProps) => {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const { sub = null, ...rest }: TokenProps =
|
const { sub = null, ...rest }: TokenProps =
|
||||||
typeof props === 'string' || typeof props === 'undefined' ? { sub: props || null } : props;
|
typeof props === "string" || typeof props === "undefined"
|
||||||
let { exp } = rest;
|
? { sub: props || null }
|
||||||
if (!exp) {
|
: props;
|
||||||
exp = new Date(today);
|
let { exp } = rest;
|
||||||
exp.setDate(today.getDate() + parseInt(process.env.JWT_DAYS_VALID as string));
|
if (!exp) {
|
||||||
exp = exp.getTime() / 1000;
|
exp = new Date(today);
|
||||||
}
|
exp.setDate(
|
||||||
return jwt.sign(
|
today.getDate() + parseInt(process.env.JWT_DAYS_VALID as string)
|
||||||
{
|
|
||||||
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);
|
export const verify = (token: string) => jwt.verify(token, JWT_SECRET);
|
||||||
|
|||||||
@@ -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}`;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
export const parseTimeoutToMs = (timeout: string) => {
|
export const parseTimeoutToMs = (timeout: string) => {
|
||||||
const match = timeout.match(/(?<number>\d+)(?<unit>d|h|m)/gi)?.groups || {};
|
const match = timeout.match(/(?<number>\d+)(?<unit>d|h|m)/gi)?.groups || {};
|
||||||
const { number, unit } = match;
|
const { number, unit } = match;
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case 'd':
|
case "d":
|
||||||
return 1000 * 60 * 60 * 24 * parseInt(number);
|
return 1000 * 60 * 60 * 24 * parseInt(number);
|
||||||
case 'h':
|
case "h":
|
||||||
return 1000 * 60 * 60 * parseInt(number);
|
return 1000 * 60 * 60 * parseInt(number);
|
||||||
case 'm':
|
case "m":
|
||||||
default:
|
default:
|
||||||
return 1000 * 60 * parseInt(number) || 1;
|
return 1000 * 60 * parseInt(number) || 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { pbkdf2Sync, randomBytes } from 'crypto';
|
import { pbkdf2Sync, randomBytes } from "crypto";
|
||||||
|
|
||||||
export const encrypt = (password: string) => {
|
export const encrypt = (password: string) => {
|
||||||
const salt = randomBytes(16).toString('hex');
|
const salt = randomBytes(16).toString("hex");
|
||||||
const hash = pbkdf2Sync(password, salt, 10000, 512, 'sha512').toString('hex');
|
const hash = pbkdf2Sync(password, salt, 10000, 512, "sha512").toString("hex");
|
||||||
return `${salt}:${hash}`;
|
return `${salt}:${hash}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const verify = (test: string, secret: string) => {
|
export const verify = (test: string, secret: string) => {
|
||||||
const [salt, hash] = secret.split(':');
|
const [salt, hash] = secret.split(":");
|
||||||
return pbkdf2Sync(test, salt, 10000, 512, 'sha512').toString('hex') === hash;
|
return pbkdf2Sync(test, salt, 10000, 512, "sha512").toString("hex") === hash;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { sign } from './jwt';
|
import { sign } from "./jwt";
|
||||||
import { LOGIN_VALID_TIMEOUT } from '../constants/env';
|
import { LOGIN_VALID_TIMEOUT } from "../constants/env";
|
||||||
import { Status } from '../constants/auth';
|
import { Status } from "../constants/auth";
|
||||||
import { parseTimeoutToMs } from './parseTimeoutToMs';
|
import { parseTimeoutToMs } from "./parseTimeoutToMs";
|
||||||
|
|
||||||
export const generateLoginToken = (sub: string, status: Status) =>
|
export const generateLoginToken = (sub: string, status: Status) =>
|
||||||
sign({
|
sign({
|
||||||
sub,
|
sub,
|
||||||
status,
|
status,
|
||||||
exp: Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT),
|
exp: Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user