mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 02:51:51 +00:00
feat: changed backend-pg to backend
This commit is contained in:
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user