a LOTTTA more migration hoopla shii

This commit is contained in:
user
2026-02-28 16:20:47 +02:00
parent 4d107c8cab
commit 2c8cd1fd15
18 changed files with 208 additions and 81 deletions

View File

@@ -1,5 +1,5 @@
{
"name": "@pkg/redis",
"name": "@pkg/keystore",
"module": "index.ts",
"type": "module",
"private": true,

View File

@@ -1,7 +1,7 @@
import { errAsync, okAsync, ResultAsync } from "neverthrow";
import { FlowExecCtx } from "@core/flow.execution.context";
import { UserRepository } from "@domains/user/repository";
import { getRedisInstance, Redis } from "@pkg/redis";
import { getRedisInstance, Redis } from "@pkg/keystore";
import { TwofaRepository } from "./repository";
import { auth } from "../auth/config.base";
import type { TwoFaSession } from "./data";
@@ -17,6 +17,7 @@ export class TwofaController {
private twofaRepo: TwofaRepository,
private userRepo: UserRepository,
private store: Redis,
private secret: string,
) {}
checkTotp(secret: string, code: string) {
@@ -42,7 +43,7 @@ export class TwofaController {
.andThen((enabled) =>
enabled
? errAsync(twofaErrors.alreadyEnabled(fctx))
: this.twofaRepo.setup(fctx, user.id),
: this.twofaRepo.setup(fctx, user.id, this.secret),
)
.map((secret) => {
const appName = settings.appName;
@@ -345,5 +346,6 @@ export function getTwofaController() {
new TwofaRepository(db, _redis),
new UserRepository(db),
_redis,
settings.twoFaSecret,
);
}

View File

@@ -3,13 +3,15 @@ import { FlowExecCtx } from "@core/flow.execution.context";
import { hashString, verifyHash } from "@/core/hash.utils";
import { twoFactor, twofaSessions } from "@pkg/db/schema";
import { TwoFactor, type TwoFaSession } from "./data";
import { crypto } from "@otplib/plugin-crypto-noble";
import { base32 } from "@otplib/plugin-base32-scure";
import { and, Database, eq, gt, lt } from "@pkg/db";
import { generateSecret, verify } from "otplib";
import { generate, verify } from "@otplib/totp";
import { settings } from "@core/settings";
import type { Err } from "@pkg/result";
import { twofaErrors } from "./errors";
import { Redis } from "@pkg/keystore";
import { logger } from "@pkg/logger";
import { Redis } from "@pkg/redis";
import { nanoid } from "nanoid";
type TwoFaSetup = {
@@ -30,7 +32,7 @@ export class TwofaRepository {
) {}
checkTotp(secret: string, code: string) {
const checked = verify({ secret, token: code });
const checked = verify({ secret, token: code, crypto, base32 });
logger.debug("TOTP check result", { checked });
return checked;
}
@@ -98,14 +100,22 @@ export class TwofaRepository {
});
}
setup(fctx: FlowExecCtx, userId: string): ResultAsync<string, Err> {
setup(
fctx: FlowExecCtx,
userId: string,
secret: string,
): ResultAsync<string, Err> {
logger.info("Starting 2FA setup", { ...fctx, userId });
return ResultAsync.fromSafePromise(
(async () => {
const secret = generateSecret();
const payload = {
const token = await generate({
secret,
crypto,
base32,
});
const payload = {
secret: token,
lastUsedCode: "",
tries: 0,
} as TwoFaSetup;

View File

@@ -10,7 +10,7 @@ import { AuthController, getAuthController } from "./controller";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { FlowExecCtx } from "@/core/flow.execution.context";
import { UserRoleMap } from "@domains/user/data";
import { getRedisInstance } from "@pkg/redis";
import { getRedisInstance } from "@pkg/keystore";
import { APIError } from "better-auth/api";
import { settings } from "@core/settings";
import { betterAuth } from "better-auth";

View File

@@ -1,13 +1,16 @@
{
"name": "@pkg/logic",
"scripts": {
"auth:schemagen": "bun x @better-auth/cli generate --config ./domains/auth/config.base.ts --output ../../packages/db/schema/better.auth.schema.ts"
"auth:schemagen": "pnpm dlx @better-auth/cli generate --config ./domains/auth/config.base.ts --output ../../packages/db/schema/better.auth.schema.ts"
},
"dependencies": {
"@hono/standard-validator": "^0.2.1",
"@otplib/plugin-base32-scure": "^13.3.0",
"@otplib/plugin-crypto-noble": "^13.3.0",
"@otplib/totp": "^13.3.0",
"@pkg/db": "workspace:*",
"@pkg/logger": "workspace:*",
"@pkg/redis": "workspace:*",
"@pkg/keystore": "workspace:*",
"@pkg/result": "workspace:*",
"@pkg/settings": "workspace:*",
"@types/pdfkit": "^0.14.0",

View File

@@ -22,6 +22,7 @@ export const settingsSchema = v.object({
betterAuthUrl: v.string(),
betterAuthSecret: v.string(),
twoFaSecret: v.string(),
twofaSessionExpiryMinutes: v.optional(v.number()),
twofaRequiredHours: v.optional(v.number()),
@@ -107,6 +108,7 @@ function loadSettings(): Settings {
betterAuthUrl: getEnv("BETTER_AUTH_URL"),
betterAuthSecret: getEnv("BETTER_AUTH_SECRET"),
twoFaSecret: getEnv("TWOFA_SECRET"),
twofaSessionExpiryMinutes: getEnvNumber(
"TWOFA_SESSION_EXPIRY_MINUTES",
10,