Files
illusory-mapp/packages/logic/domains/auth/controller.ts
2026-02-28 14:50:04 +02:00

149 lines
4.7 KiB
TypeScript

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 type { Err } from "@pkg/result";
import { authErrors } from "./errors";
import { logger } from "@pkg/logger";
import { nanoid } from "nanoid";
import { db } from "@pkg/db";
export class AuthController {
private readonly mins = 10;
constructor(private accountRepo: AccountRepository) {}
sendEmailChangeVerificationEmail(
fctx: FlowExecCtx,
newEmail: string,
token: string,
url: string,
): ResultAsync<void, Err> {
logger.info("Sending email change verification link", {
...fctx,
newEmail,
});
logger.debug("Original URL", { ...fctx, url });
const transformedUrl = url
.replace("/api/auth/verify-email", "/account/verify-email")
.replace("/api/", "/");
logger.debug("Transformed URL", { ...fctx, transformedUrl });
// Simulate email sending with 90/10 success/failure
const success = Math.random() > 0.1;
if (!success) {
logger.error("Failed to send email change verification link", {
...fctx,
error: "Simulated email service failure",
});
return ResultAsync.fromPromise(
Promise.reject(
authErrors.emailChangeVerificationFailed(
fctx,
"Simulated email service failure",
),
),
(error) => error as Err,
);
}
logger.info("Email change verification sent successfully", {
...fctx,
newEmail,
});
return ResultAsync.fromSafePromise(Promise.resolve(undefined));
}
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 },
};
});
}
sendMagicLink(
fctx: FlowExecCtx,
email: string,
token: string,
url: string,
): ResultAsync<void, Err> {
logger.info("Sending magic link", { ...fctx, email });
logger.debug("Original URL", { ...fctx, url });
const transformedUrl = url
.replace("/api/auth/magic-link/verify", "/auth/magic-link")
.replace("/api/", "/");
logger.debug("Transformed URL", { ...fctx, transformedUrl });
// Simulate email sending with 90/10 success/failure
const success = Math.random() > 0.1;
if (!success) {
logger.error("Failed to send magic link email", {
...fctx,
error: "Simulated email service failure",
});
return ResultAsync.fromPromise(
Promise.reject(
authErrors.magicLinkEmailFailed(
fctx,
"Simulated email service failure",
),
),
(error) => error as Err,
);
}
logger.info("Magic link email sent successfully", {
...fctx,
email,
});
return ResultAsync.fromSafePromise(Promise.resolve(undefined));
}
}
export function getAuthController(): AuthController {
return new AuthController(new AccountRepository(db));
}