feat: changed backend-pg to backend

This commit is contained in:
Akhil Mohan
2024-01-27 19:14:33 +05:30
parent d13eafcef7
commit 66d258f02b
792 changed files with 40298 additions and 40607 deletions
@@ -0,0 +1,124 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
import { TDeleteTokenForUserDALDTO } from "./auth-token-types";
export type TTokenDALConfig = {};
export type TTokenDALFactory = ReturnType<typeof tokenDALFactory>;
export const tokenDALFactory = (db: TDbClient) => {
const authOrm = ormify(db, TableName.AuthTokens);
const findOneTokenSession = async (
filter: Partial<TAuthTokenSessions>
): Promise<TAuthTokenSessions | undefined> => {
try {
const doc = await db(TableName.AuthTokenSession).where(filter).first();
return doc;
} catch (error) {
throw new DatabaseError({ error, name: "FindOneTokenSession" });
}
};
const deleteTokenForUser = async ({
userId,
type,
orgId
}: TDeleteTokenForUserDALDTO): Promise<TAuthTokens[] | undefined> => {
try {
const doc = await db(TableName.AuthTokens)
.where({ userId, type, orgId })
.delete()
.returning("*");
return doc;
} catch (error) {
throw new DatabaseError({ error, name: "DeleteTokenForUser" });
}
};
const decrementTriesField = async ({
userId,
type
}: TDeleteTokenForUserDALDTO): Promise<void> => {
try {
await db(TableName.AuthTokens).where({ userId, type }).decrement("triesLeft", 1);
} catch (error) {
throw new DatabaseError({ error, name: "DecrementTriesField" });
}
};
const findTokenSessions = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => {
try {
const sessions = await (tx || db)(TableName.AuthTokenSession).where(filter);
return sessions;
} catch (error) {
throw new DatabaseError({ name: "Find all token session", error });
}
};
const insertTokenSession = async (
userId: string,
ip: string,
userAgent: string
): Promise<TAuthTokenSessions | undefined> => {
try {
const [session] = await db(TableName.AuthTokenSession)
.insert({
userId,
ip,
userAgent,
accessVersion: 1,
refreshVersion: 1,
lastUsed: new Date()
})
.returning("*");
return session;
} catch (error) {
throw new DatabaseError({ error, name: "InsertTokenSession" });
}
};
const incrementTokenSessionVersion = async (
userId: string,
sessionId: string
): Promise<TAuthTokenSessions | undefined> => {
try {
const [session] = await db(TableName.AuthTokenSession)
.where({ userId, id: sessionId })
.increment("accessVersion", 1)
.increment("refreshVersion", 1)
.returning("*");
return session;
} catch (error) {
throw new DatabaseError({ error, name: "IncrementTokenSessionVersion" });
}
};
const deleteTokenSession = async (filter: Partial<TAuthTokenSessions>, tx?: Knex) => {
try {
const sessions = await (tx || db)(TableName.AuthTokenSession)
.where(filter)
.del()
.returning("*");
return sessions;
} catch (error) {
throw new DatabaseError({ name: "Delete token session", error });
}
};
return {
...authOrm,
findTokenSessions,
deleteTokenForUser,
decrementTriesField,
findOneTokenSession,
insertTokenSession,
incrementTokenSessionVersion,
deleteTokenSession
};
};
@@ -0,0 +1,167 @@
import crypto from "node:crypto";
import bcrypt from "bcrypt";
import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors";
import { AuthModeJwtTokenPayload } from "../auth/auth-type";
import { TUserDALFactory } from "../user/user-dal";
import { TTokenDALFactory } from "./auth-token-dal";
import {
TCreateTokenForUserDTO,
TIssueAuthTokenDTO,
TokenType,
TValidateTokenForUserDTO
} from "./auth-token-types";
type TAuthTokenServiceFactoryDep = {
tokenDAL: TTokenDALFactory;
userDAL: Pick<TUserDALFactory, "findById">;
};
export type TAuthTokenServiceFactory = ReturnType<typeof tokenServiceFactory>;
export const getTokenConfig = (tokenType: TokenType) => {
// generate random token based on specified token use-case
// type [type]
switch (tokenType) {
case TokenType.TOKEN_EMAIL_CONFIRMATION: {
// generate random 6-digit code
const token = String(crypto.randomInt(10 ** 5, 10 ** 6 - 1));
const expiresAt = new Date(new Date().getTime() + 86400000);
return { token, expiresAt };
}
case TokenType.TOKEN_EMAIL_MFA: {
// generate random 6-digit code
const token = String(crypto.randomInt(10 ** 5, 10 ** 6 - 1));
const triesLeft = 5;
const expiresAt = new Date(new Date().getTime() + 300000);
return { token, triesLeft, expiresAt };
}
case TokenType.TOKEN_EMAIL_ORG_INVITATION: {
// generate random hex
const token = crypto.randomBytes(16).toString("hex");
const expiresAt = new Date(new Date().getTime() + 259200000);
return { token, expiresAt };
}
case TokenType.TOKEN_EMAIL_PASSWORD_RESET: {
// generate random hex
const token = crypto.randomBytes(16).toString("hex");
const expiresAt = new Date(new Date().getTime() + 86400000);
return { token, expiresAt };
}
default: {
const token = crypto.randomBytes(16).toString("hex");
const expiresAt = new Date();
return { token, expiresAt };
}
}
};
export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFactoryDep) => {
const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => {
const { token, ...tkCfg } = getTokenConfig(type);
const appCfg = getConfig();
const tokenHash = await bcrypt.hash(token, appCfg.SALT_ROUNDS);
await tokenDAL.transaction(async (tx) => {
await tokenDAL.delete({ userId, type, orgId: orgId || null }, tx);
const newToken = await tokenDAL.create(
{
tokenHash,
expiresAt: tkCfg.expiresAt,
type,
userId,
orgId,
triesLeft: tkCfg?.triesLeft
},
tx
);
return newToken;
});
return token;
};
const validateTokenForUser = async ({
type,
userId,
code,
orgId
}: TValidateTokenForUserDTO): Promise<TAuthTokens | undefined> => {
const token = await tokenDAL.findOne({ type, userId, orgId: orgId || null });
// validate token
if (!token) throw new Error("Failed to find token");
if (token?.expiresAt && new Date(token.expiresAt) < new Date()) {
await tokenDAL.delete({ type, userId, orgId });
throw new Error("Token expired. Please try again");
}
const isValidToken = await bcrypt.compare(code, token.tokenHash);
if (!isValidToken) {
if (token?.triesLeft) {
if (token.triesLeft === 1) {
await tokenDAL.deleteTokenForUser({ type, userId, orgId: orgId || null });
} else {
await tokenDAL.decrementTriesField({ type, userId, orgId: orgId || null });
}
}
throw new Error("Invalid token");
}
const deletedToken = await tokenDAL.delete({ type, userId, orgId: orgId || null });
return deletedToken?.[0];
};
const getUserTokenSession = async ({
userId,
ip,
userAgent
}: TIssueAuthTokenDTO): Promise<TAuthTokenSessions | undefined> => {
let session = await tokenDAL.findOneTokenSession({ userId, ip, userAgent });
if (!session) {
session = await tokenDAL.insertTokenSession(userId, ip, userAgent);
}
return session;
};
const clearTokenSessionById = async (
userId: string,
sessionId: string
): Promise<TAuthTokenSessions | undefined> =>
tokenDAL.incrementTokenSessionVersion(userId, sessionId);
const getUserTokenSessionById = async (id: string, userId: string) =>
tokenDAL.findOneTokenSession({ id, userId });
const getTokenSessionByUser = async (userId: string) => tokenDAL.findTokenSessions({ userId });
const revokeAllMySessions = async (userId: string) => tokenDAL.deleteTokenSession({ userId });
// to parse jwt identity in inject identity plugin
const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload) => {
const session = await tokenDAL.findOneTokenSession({
id: token.tokenVersionId,
userId: token.userId
});
if (!session) throw new UnauthorizedError({ name: "Session not found" });
if (token.accessVersion !== session.accessVersion)
throw new UnauthorizedError({ name: "Stale session" });
const user = await userDAL.findById(session.userId);
if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" });
return { user, tokenVersionId: token.tokenVersionId };
};
return {
createTokenForUser,
validateTokenForUser,
getUserTokenSession,
clearTokenSessionById,
getTokenSessionByUser,
revokeAllMySessions,
fnValidateJwtIdentity,
getUserTokenSessionById
};
};
@@ -0,0 +1,49 @@
export enum TokenType {
TOKEN_EMAIL_CONFIRMATION = "emailConfirmation",
TOKEN_EMAIL_MFA = "emailMfa",
TOKEN_EMAIL_ORG_INVITATION = "organizationInvitation",
TOKEN_EMAIL_PASSWORD_RESET = "passwordReset"
}
export type TCreateTokenForUserDTO = {
type: TokenType;
userId: string;
orgId?: string;
};
export type TCreateOrgInviteTokenDTO = {
userId: string;
orgId: string;
};
export type TValidateTokenForUserDTO = {
type: TokenType;
code: string;
userId: string;
orgId?: string;
};
export type TUpsertTokenForUserDALDTO = {
type: TokenType;
expiresAt: Date;
userId: string;
tokenHash: string;
triesLeft?: number;
};
export type TGetTokenForUserDALDTO = {
userId: string;
type: TokenType;
};
export type TDeleteTokenForUserDALDTO = {
userId: string;
type: TokenType;
orgId: string | null;
};
export type TIssueAuthTokenDTO = {
userId: string;
ip: string;
userAgent: string;
};