149 lines
4.7 KiB
TypeScript
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));
|
|
}
|