This commit is contained in:
user
2026-03-27 20:06:38 +02:00
commit 8c45efc92e
544 changed files with 33060 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
import {
admin,
customSession,
multiSession,
username,
} from "better-auth/plugins";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { UserRoleMap } from "@domains/user/data";
import { getRedisInstance } from "@pkg/keystore";
import { settings } from "@core/settings";
import { betterAuth } from "better-auth";
import { logger } from "@pkg/logger";
import { db, schema } from "@pkg/db";
const COOKIE_CACHE_MAX_AGE = 60 * 5;
const USERNAME_REGEX = /^[a-zA-Z0-9_]+$/;
export const auth = betterAuth({
trustedOrigins: ["http://localhost:5173", settings.betterAuthUrl],
advanced: { useSecureCookies: settings.nodeEnv === "production" },
appName: settings.appName,
emailAndPassword: {
enabled: true,
disableSignUp: true,
requireEmailVerification: false,
},
plugins: [
customSession(async ({ user, session }) => {
session.id = session.token;
return { user, session };
}),
username({
minUsernameLength: 5,
maxUsernameLength: 20,
usernameValidator: async (username) => {
return USERNAME_REGEX.test(username);
},
}),
admin({
defaultRole: UserRoleMap.admin,
defaultBanReason:
"Stop fanum taxing the server bub, losing aura points fr",
defaultBanExpiresIn: 60 * 60 * 24,
}),
multiSession({ maximumSessions: 5 }),
],
logger: {
log: (level, message, metadata) => {
logger.log(level, message, metadata);
},
level: "debug",
},
database: drizzleAdapter(db, { provider: "pg", schema: { ...schema } }),
secondaryStorage: {
get: async (key) => {
const redis = getRedisInstance();
return await redis.get(key);
},
set: async (key, value, ttl) => {
const redis = getRedisInstance();
if (ttl) {
await redis.setex(key, ttl, value);
} else {
await redis.set(key, value);
}
},
delete: async (key) => {
const redis = getRedisInstance();
const out = await redis.del(key);
if (!out && out !== 0) {
return null;
}
return out.toString() as any;
},
},
session: {
modelName: "session",
expiresIn: 60 * 60 * 24 * 7,
updateAge: 60 * 60 * 24,
cookieCache: {
enabled: true,
maxAge: COOKIE_CACHE_MAX_AGE,
},
},
user: {
modelName: "user",
additionalFields: {
onboardingDone: {
type: "boolean",
defaultValue: false,
required: false,
},
last2FAVerifiedAt: { type: "date", required: false },
parentId: { required: false, type: "string" },
},
},
});
// - - -

View File

@@ -0,0 +1,60 @@
import { AuthContext, MiddlewareContext, MiddlewareOptions } from "better-auth";
import { AccountRepository } from "../user/account.repository";
import { FlowExecCtx } from "@/core/flow.execution.context";
import { ResultAsync } from "neverthrow";
import { authErrors } from "./errors";
import { logger } from "@pkg/logger";
import { nanoid } from "nanoid";
import { db } from "@pkg/db";
export class AuthController {
constructor(private accountRepo: AccountRepository) {}
swapAccountPasswordForTwoFactor(
fctx: FlowExecCtx,
ctx: MiddlewareContext<
MiddlewareOptions,
AuthContext & { returned?: unknown; responseHeaders?: Headers }
>,
) {
logger.info("Swapping account password for 2FA", {
...fctx,
});
if (!ctx.path.includes("two-factor")) {
return ResultAsync.fromSafePromise(Promise.resolve(ctx));
}
if (!ctx.body.password || ctx.body.password.length === 0) {
return ResultAsync.fromSafePromise(Promise.resolve(ctx));
}
logger.info("Rotating password for 2FA setup for user", {
...fctx,
userId: ctx.body.userId,
});
return this.accountRepo
.rotatePassword(fctx, ctx.body.userId, nanoid())
.mapErr((err) => {
logger.error("Failed to rotate password for 2FA", {
...fctx,
error: err,
});
return authErrors.passwordRotationFailed(fctx, err.detail);
})
.map((newPassword) => {
logger.info("Password rotated successfully for 2FA setup", {
...fctx,
});
return {
...ctx,
body: { ...ctx.body, password: newPassword },
};
});
}
}
export function getAuthController(): AuthController {
return new AuthController(new AccountRepository(db));
}

View File

@@ -0,0 +1,32 @@
import { FlowExecCtx } from "@/core/flow.execution.context";
import { getError } from "@pkg/logger";
import { ERROR_CODES, type Err } from "@pkg/result";
export const authErrors = {
passwordRotationFailed: (fctx: FlowExecCtx, detail: string): Err =>
getError({
flowId: fctx.flowId,
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
message: "Failed to begin 2FA setup",
description: "An error occurred while rotating the password for 2FA",
detail,
}),
dbError: (fctx: FlowExecCtx, detail: string): Err =>
getError({
flowId: fctx.flowId,
code: ERROR_CODES.DATABASE_ERROR,
message: "Database operation failed",
description: "Please try again later",
detail,
}),
accountNotFound: (fctx: FlowExecCtx): Err =>
getError({
flowId: fctx.flowId,
code: ERROR_CODES.NOT_FOUND,
message: "Account not found",
description: "Please try again later",
detail: "Account not found for user",
}),
};