2025-09-26 16:30:00 +09:00
|
|
|
|
import {
|
|
|
|
|
|
Controller,
|
|
|
|
|
|
Post,
|
|
|
|
|
|
Body,
|
|
|
|
|
|
UseGuards,
|
2025-09-27 17:51:54 +09:00
|
|
|
|
UseInterceptors,
|
2025-09-26 16:30:00 +09:00
|
|
|
|
Get,
|
|
|
|
|
|
Req,
|
|
|
|
|
|
HttpCode,
|
|
|
|
|
|
UsePipes,
|
|
|
|
|
|
Res,
|
|
|
|
|
|
} from "@nestjs/common";
|
2025-09-26 15:51:07 +09:00
|
|
|
|
import type { Request, Response } from "express";
|
2025-09-18 12:34:26 +09:00
|
|
|
|
import { Throttle } from "@nestjs/throttler";
|
2025-11-05 15:47:06 +09:00
|
|
|
|
import { JwtService } from "@nestjs/jwt";
|
2025-10-02 16:33:25 +09:00
|
|
|
|
import { AuthFacade } from "@bff/modules/auth/application/auth.facade";
|
2025-09-18 12:34:26 +09:00
|
|
|
|
import { LocalAuthGuard } from "./guards/local-auth.guard";
|
|
|
|
|
|
import { AuthThrottleGuard } from "./guards/auth-throttle.guard";
|
2025-11-05 15:47:06 +09:00
|
|
|
|
import {
|
|
|
|
|
|
FailedLoginThrottleGuard,
|
|
|
|
|
|
type RequestWithRateLimit,
|
|
|
|
|
|
} from "./guards/failed-login-throttle.guard";
|
2025-09-27 17:51:54 +09:00
|
|
|
|
import { LoginResultInterceptor } from "./interceptors/login-result.interceptor";
|
2025-10-02 18:35:26 +09:00
|
|
|
|
import { Public } from "../../decorators/public.decorator";
|
2025-12-02 11:06:54 +09:00
|
|
|
|
import { ZodValidationPipe } from "nestjs-zod";
|
2025-11-05 15:47:06 +09:00
|
|
|
|
import type { RequestWithUser } from "@bff/modules/auth/auth.types";
|
2025-11-06 13:26:30 +09:00
|
|
|
|
import { SalesforceReadThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-read-throttle.guard";
|
2025-11-06 16:32:29 +09:00
|
|
|
|
import { SalesforceWriteThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-write-throttle.guard";
|
2025-09-18 12:34:26 +09:00
|
|
|
|
|
|
|
|
|
|
// Import Zod schemas from domain
|
|
|
|
|
|
import {
|
2025-09-19 16:34:10 +09:00
|
|
|
|
signupRequestSchema,
|
|
|
|
|
|
passwordResetRequestSchema,
|
|
|
|
|
|
passwordResetSchema,
|
|
|
|
|
|
setPasswordRequestSchema,
|
|
|
|
|
|
linkWhmcsRequestSchema,
|
|
|
|
|
|
changePasswordRequestSchema,
|
|
|
|
|
|
validateSignupRequestSchema,
|
|
|
|
|
|
accountStatusRequestSchema,
|
|
|
|
|
|
ssoLinkRequestSchema,
|
|
|
|
|
|
checkPasswordNeededRequestSchema,
|
2025-09-20 11:35:40 +09:00
|
|
|
|
refreshTokenRequestSchema,
|
2025-11-04 13:28:36 +09:00
|
|
|
|
checkPasswordNeededResponseSchema,
|
|
|
|
|
|
linkWhmcsResponseSchema,
|
2025-10-03 16:37:52 +09:00
|
|
|
|
type SignupRequest,
|
|
|
|
|
|
type PasswordResetRequest,
|
2025-10-08 10:33:33 +09:00
|
|
|
|
type ResetPasswordRequest,
|
2025-10-03 16:37:52 +09:00
|
|
|
|
type SetPasswordRequest,
|
|
|
|
|
|
type LinkWhmcsRequest,
|
|
|
|
|
|
type ChangePasswordRequest,
|
|
|
|
|
|
type ValidateSignupRequest,
|
|
|
|
|
|
type AccountStatusRequest,
|
|
|
|
|
|
type SsoLinkRequest,
|
|
|
|
|
|
type CheckPasswordNeededRequest,
|
|
|
|
|
|
type RefreshTokenRequest,
|
|
|
|
|
|
type AuthTokens,
|
|
|
|
|
|
} from "@customer-portal/domain/auth";
|
2025-09-26 15:51:07 +09:00
|
|
|
|
|
2025-10-22 10:23:56 +09:00
|
|
|
|
type CookieValue = string | undefined;
|
|
|
|
|
|
type RequestWithCookies = Omit<Request, "cookies"> & {
|
|
|
|
|
|
cookies?: Record<string, CookieValue>;
|
2025-10-03 11:29:59 +09:00
|
|
|
|
};
|
2025-09-26 15:51:07 +09:00
|
|
|
|
|
2025-10-02 18:35:26 +09:00
|
|
|
|
const resolveAuthorizationHeader = (req: RequestWithCookies): string | undefined => {
|
|
|
|
|
|
const rawHeader = req.headers?.authorization;
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof rawHeader === "string") {
|
|
|
|
|
|
return rawHeader;
|
2025-09-26 15:51:07 +09:00
|
|
|
|
}
|
2025-10-02 18:35:26 +09:00
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(rawHeader)) {
|
|
|
|
|
|
const headerValues: string[] = rawHeader;
|
|
|
|
|
|
for (const candidate of headerValues) {
|
|
|
|
|
|
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
|
|
|
|
return candidate;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const extractBearerToken = (req: RequestWithCookies): string | undefined => {
|
|
|
|
|
|
const authHeader = resolveAuthorizationHeader(req);
|
|
|
|
|
|
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
|
|
|
|
return authHeader.slice(7);
|
2025-09-26 15:51:07 +09:00
|
|
|
|
}
|
|
|
|
|
|
return undefined;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const extractTokenFromRequest = (req: RequestWithCookies): string | undefined => {
|
2025-10-02 18:35:26 +09:00
|
|
|
|
const headerToken = extractBearerToken(req);
|
2025-09-26 15:51:07 +09:00
|
|
|
|
if (headerToken) {
|
|
|
|
|
|
return headerToken;
|
|
|
|
|
|
}
|
|
|
|
|
|
const cookieToken = req.cookies?.access_token;
|
|
|
|
|
|
return typeof cookieToken === "string" && cookieToken.length > 0 ? cookieToken : undefined;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const calculateCookieMaxAge = (isoTimestamp: string): number => {
|
|
|
|
|
|
const expiresAt = Date.parse(isoTimestamp);
|
|
|
|
|
|
if (Number.isNaN(expiresAt)) {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return Math.max(0, expiresAt - Date.now());
|
|
|
|
|
|
};
|
2025-09-18 12:34:26 +09:00
|
|
|
|
|
|
|
|
|
|
@Controller("auth")
|
2025-09-19 17:37:46 +09:00
|
|
|
|
export class AuthController {
|
2025-11-17 11:49:58 +09:00
|
|
|
|
constructor(
|
|
|
|
|
|
private authFacade: AuthFacade,
|
|
|
|
|
|
private readonly jwtService: JwtService
|
|
|
|
|
|
) {}
|
2025-09-18 12:34:26 +09:00
|
|
|
|
|
2025-09-26 15:51:07 +09:00
|
|
|
|
private setAuthCookies(res: Response, tokens: AuthTokens): void {
|
|
|
|
|
|
const accessMaxAge = calculateCookieMaxAge(tokens.expiresAt);
|
|
|
|
|
|
const refreshMaxAge = calculateCookieMaxAge(tokens.refreshExpiresAt);
|
|
|
|
|
|
|
|
|
|
|
|
res.setSecureCookie("access_token", tokens.accessToken, {
|
|
|
|
|
|
maxAge: accessMaxAge,
|
|
|
|
|
|
path: "/",
|
|
|
|
|
|
});
|
|
|
|
|
|
res.setSecureCookie("refresh_token", tokens.refreshToken, {
|
|
|
|
|
|
maxAge: refreshMaxAge,
|
|
|
|
|
|
path: "/",
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private clearAuthCookies(res: Response): void {
|
|
|
|
|
|
res.setSecureCookie("access_token", "", { maxAge: 0, path: "/" });
|
|
|
|
|
|
res.setSecureCookie("refresh_token", "", { maxAge: 0, path: "/" });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-05 15:47:06 +09:00
|
|
|
|
private applyAuthRateLimitHeaders(req: RequestWithRateLimit, res: Response): void {
|
|
|
|
|
|
FailedLoginThrottleGuard.applyRateLimitHeaders(req, res);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-18 12:34:26 +09:00
|
|
|
|
@Public()
|
|
|
|
|
|
@Post("validate-signup")
|
2025-11-06 13:26:30 +09:00
|
|
|
|
@UseGuards(AuthThrottleGuard, SalesforceReadThrottleGuard)
|
2025-10-29 15:54:45 +09:00
|
|
|
|
@Throttle({ default: { limit: 20, ttl: 600 } }) // 20 validations per 10 minutes per IP
|
2025-09-24 18:00:49 +09:00
|
|
|
|
@UsePipes(new ZodValidationPipe(validateSignupRequestSchema))
|
2025-10-03 16:37:52 +09:00
|
|
|
|
async validateSignup(@Body() validateData: ValidateSignupRequest, @Req() req: Request) {
|
2025-10-02 16:33:25 +09:00
|
|
|
|
return this.authFacade.validateSignup(validateData, req);
|
2025-09-18 12:34:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Public()
|
|
|
|
|
|
@Get("health-check")
|
|
|
|
|
|
async healthCheck() {
|
2025-10-02 16:33:25 +09:00
|
|
|
|
return this.authFacade.healthCheck();
|
2025-09-18 12:34:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Public()
|
|
|
|
|
|
@Post("signup-preflight")
|
2025-11-06 13:26:30 +09:00
|
|
|
|
@UseGuards(AuthThrottleGuard, SalesforceReadThrottleGuard)
|
2025-10-29 15:54:45 +09:00
|
|
|
|
@Throttle({ default: { limit: 20, ttl: 600 } }) // 20 validations per 10 minutes per IP
|
2025-09-24 18:00:49 +09:00
|
|
|
|
@UsePipes(new ZodValidationPipe(signupRequestSchema))
|
2025-09-18 12:34:26 +09:00
|
|
|
|
@HttpCode(200)
|
2025-10-03 16:37:52 +09:00
|
|
|
|
async signupPreflight(@Body() signupData: SignupRequest) {
|
2025-10-02 16:33:25 +09:00
|
|
|
|
return this.authFacade.signupPreflight(signupData);
|
2025-09-18 12:34:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Public()
|
|
|
|
|
|
@Post("account-status")
|
2025-09-24 18:00:49 +09:00
|
|
|
|
@UsePipes(new ZodValidationPipe(accountStatusRequestSchema))
|
2025-10-03 16:37:52 +09:00
|
|
|
|
async accountStatus(@Body() body: AccountStatusRequest) {
|
2025-10-02 16:33:25 +09:00
|
|
|
|
return this.authFacade.getAccountStatus(body.email);
|
2025-09-18 12:34:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Public()
|
|
|
|
|
|
@Post("signup")
|
2025-11-06 16:32:29 +09:00
|
|
|
|
@UseGuards(AuthThrottleGuard, SalesforceWriteThrottleGuard)
|
2025-10-29 15:54:45 +09:00
|
|
|
|
@Throttle({ default: { limit: 5, ttl: 900 } }) // 5 signups per 15 minutes per IP (reasonable for account creation)
|
2025-09-24 18:00:49 +09:00
|
|
|
|
@UsePipes(new ZodValidationPipe(signupRequestSchema))
|
2025-09-26 15:51:07 +09:00
|
|
|
|
async signup(
|
2025-10-03 16:37:52 +09:00
|
|
|
|
@Body() signupData: SignupRequest,
|
2025-09-26 15:51:07 +09:00
|
|
|
|
@Req() req: Request,
|
|
|
|
|
|
@Res({ passthrough: true }) res: Response
|
|
|
|
|
|
) {
|
2025-10-02 16:33:25 +09:00
|
|
|
|
const result = await this.authFacade.signup(signupData, req);
|
2025-09-26 15:51:07 +09:00
|
|
|
|
this.setAuthCookies(res, result.tokens);
|
|
|
|
|
|
return result;
|
2025-09-18 12:34:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Public()
|
2025-09-27 17:51:54 +09:00
|
|
|
|
@UseGuards(LocalAuthGuard, FailedLoginThrottleGuard)
|
|
|
|
|
|
@UseInterceptors(LoginResultInterceptor)
|
2025-09-18 12:34:26 +09:00
|
|
|
|
@Post("login")
|
2025-09-26 15:51:07 +09:00
|
|
|
|
async login(
|
2025-11-05 15:47:06 +09:00
|
|
|
|
@Req() req: RequestWithUser & RequestWithRateLimit,
|
2025-09-26 15:51:07 +09:00
|
|
|
|
@Res({ passthrough: true }) res: Response
|
|
|
|
|
|
) {
|
2025-10-02 16:33:25 +09:00
|
|
|
|
const result = await this.authFacade.login(req.user, req);
|
2025-09-26 15:51:07 +09:00
|
|
|
|
this.setAuthCookies(res, result.tokens);
|
2025-11-05 15:47:06 +09:00
|
|
|
|
this.applyAuthRateLimitHeaders(req, res);
|
2025-09-26 15:51:07 +09:00
|
|
|
|
return result;
|
2025-09-18 12:34:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-05 15:47:06 +09:00
|
|
|
|
@Public()
|
2025-09-18 12:34:26 +09:00
|
|
|
|
@Post("logout")
|
2025-09-26 15:51:07 +09:00
|
|
|
|
async logout(
|
2025-11-05 15:47:06 +09:00
|
|
|
|
@Req() req: RequestWithCookies & { user?: { id: string } },
|
2025-09-26 15:51:07 +09:00
|
|
|
|
@Res({ passthrough: true }) res: Response
|
|
|
|
|
|
) {
|
|
|
|
|
|
const token = extractTokenFromRequest(req);
|
2025-11-05 15:47:06 +09:00
|
|
|
|
let userId = req.user?.id;
|
|
|
|
|
|
|
|
|
|
|
|
if (!userId && token) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const payload = await this.jwtService.verifyAsync<{ sub?: string }>(token, {
|
|
|
|
|
|
ignoreExpiration: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (payload?.sub) {
|
|
|
|
|
|
userId = payload.sub;
|
|
|
|
|
|
}
|
2025-11-17 11:49:58 +09:00
|
|
|
|
} catch {
|
2025-11-05 15:47:06 +09:00
|
|
|
|
// Ignore verification errors – we still want to clear client cookies.
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await this.authFacade.logout(userId, token, req as Request);
|
|
|
|
|
|
|
|
|
|
|
|
// Always clear cookies, even if session expired
|
2025-09-26 15:51:07 +09:00
|
|
|
|
this.clearAuthCookies(res);
|
2025-09-18 12:34:26 +09:00
|
|
|
|
return { message: "Logout successful" };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-20 11:35:40 +09:00
|
|
|
|
@Public()
|
|
|
|
|
|
@Post("refresh")
|
2025-10-29 15:54:45 +09:00
|
|
|
|
@Throttle({ default: { limit: 10, ttl: 300 } }) // 10 attempts per 5 minutes per IP
|
2025-09-24 18:00:49 +09:00
|
|
|
|
@UsePipes(new ZodValidationPipe(refreshTokenRequestSchema))
|
2025-09-26 15:51:07 +09:00
|
|
|
|
async refreshToken(
|
2025-10-03 16:37:52 +09:00
|
|
|
|
@Body() body: RefreshTokenRequest,
|
2025-09-26 15:51:07 +09:00
|
|
|
|
@Req() req: RequestWithCookies,
|
|
|
|
|
|
@Res({ passthrough: true }) res: Response
|
|
|
|
|
|
) {
|
|
|
|
|
|
const refreshToken = body.refreshToken ?? req.cookies?.refresh_token;
|
2025-10-03 11:29:59 +09:00
|
|
|
|
const rawUserAgent = req.headers["user-agent"];
|
|
|
|
|
|
const userAgent = typeof rawUserAgent === "string" ? rawUserAgent : undefined;
|
2025-10-02 16:33:25 +09:00
|
|
|
|
const result = await this.authFacade.refreshTokens(refreshToken, {
|
2025-09-20 11:35:40 +09:00
|
|
|
|
deviceId: body.deviceId,
|
2025-10-03 11:29:59 +09:00
|
|
|
|
userAgent,
|
2025-09-20 11:35:40 +09:00
|
|
|
|
});
|
2025-09-26 15:51:07 +09:00
|
|
|
|
this.setAuthCookies(res, result.tokens);
|
|
|
|
|
|
return result;
|
2025-09-20 11:35:40 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-18 12:34:26 +09:00
|
|
|
|
@Public()
|
|
|
|
|
|
@Post("link-whmcs")
|
2025-11-06 16:32:29 +09:00
|
|
|
|
@UseGuards(AuthThrottleGuard, SalesforceWriteThrottleGuard)
|
2025-10-29 15:54:45 +09:00
|
|
|
|
@Throttle({ default: { limit: 5, ttl: 600 } }) // 5 attempts per 10 minutes per IP (industry standard)
|
2025-09-24 18:00:49 +09:00
|
|
|
|
@UsePipes(new ZodValidationPipe(linkWhmcsRequestSchema))
|
2025-10-03 16:37:52 +09:00
|
|
|
|
async linkWhmcs(@Body() linkData: LinkWhmcsRequest, @Req() _req: Request) {
|
2025-11-04 13:28:36 +09:00
|
|
|
|
const result = await this.authFacade.linkWhmcsUser(linkData);
|
|
|
|
|
|
return linkWhmcsResponseSchema.parse(result);
|
2025-09-18 12:34:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Public()
|
|
|
|
|
|
@Post("set-password")
|
|
|
|
|
|
@UseGuards(AuthThrottleGuard)
|
2025-10-29 15:54:45 +09:00
|
|
|
|
@Throttle({ default: { limit: 5, ttl: 600 } }) // 5 attempts per 10 minutes per IP+UA (industry standard)
|
2025-09-24 18:00:49 +09:00
|
|
|
|
@UsePipes(new ZodValidationPipe(setPasswordRequestSchema))
|
2025-09-26 15:51:07 +09:00
|
|
|
|
async setPassword(
|
2025-10-03 16:37:52 +09:00
|
|
|
|
@Body() setPasswordData: SetPasswordRequest,
|
2025-09-26 15:51:07 +09:00
|
|
|
|
@Req() _req: Request,
|
|
|
|
|
|
@Res({ passthrough: true }) res: Response
|
|
|
|
|
|
) {
|
2025-10-02 16:33:25 +09:00
|
|
|
|
const result = await this.authFacade.setPassword(setPasswordData);
|
2025-09-26 15:51:07 +09:00
|
|
|
|
this.setAuthCookies(res, result.tokens);
|
|
|
|
|
|
return result;
|
2025-09-18 12:34:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Public()
|
|
|
|
|
|
@Post("check-password-needed")
|
2025-09-24 18:00:49 +09:00
|
|
|
|
@UsePipes(new ZodValidationPipe(checkPasswordNeededRequestSchema))
|
2025-09-18 12:34:26 +09:00
|
|
|
|
@HttpCode(200)
|
2025-10-03 16:37:52 +09:00
|
|
|
|
async checkPasswordNeeded(@Body() data: CheckPasswordNeededRequest) {
|
2025-11-04 13:28:36 +09:00
|
|
|
|
const response = await this.authFacade.checkPasswordNeeded(data.email);
|
|
|
|
|
|
return checkPasswordNeededResponseSchema.parse(response);
|
2025-09-18 12:34:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Public()
|
|
|
|
|
|
@Post("request-password-reset")
|
2025-10-29 15:54:45 +09:00
|
|
|
|
@Throttle({ default: { limit: 5, ttl: 900 } }) // 5 attempts per 15 minutes (standard for password operations)
|
2025-09-24 18:00:49 +09:00
|
|
|
|
@UsePipes(new ZodValidationPipe(passwordResetRequestSchema))
|
2025-10-03 16:37:52 +09:00
|
|
|
|
async requestPasswordReset(@Body() body: PasswordResetRequest, @Req() req: Request) {
|
2025-10-02 16:33:25 +09:00
|
|
|
|
await this.authFacade.requestPasswordReset(body.email, req);
|
2025-09-18 12:34:26 +09:00
|
|
|
|
return { message: "If an account exists, a reset email has been sent" };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Public()
|
|
|
|
|
|
@Post("reset-password")
|
2025-10-03 17:33:39 +09:00
|
|
|
|
@HttpCode(200)
|
2025-09-24 18:00:49 +09:00
|
|
|
|
@UsePipes(new ZodValidationPipe(passwordResetSchema))
|
2025-10-22 10:58:16 +09:00
|
|
|
|
async resetPassword(
|
|
|
|
|
|
@Body() body: ResetPasswordRequest,
|
|
|
|
|
|
@Res({ passthrough: true }) res: Response
|
|
|
|
|
|
) {
|
2025-10-03 17:33:39 +09:00
|
|
|
|
await this.authFacade.resetPassword(body.token, body.password);
|
|
|
|
|
|
|
|
|
|
|
|
// Clear auth cookies after password reset to force re-login
|
2025-11-17 10:31:33 +09:00
|
|
|
|
this.clearAuthCookies(res);
|
2025-10-03 17:33:39 +09:00
|
|
|
|
return { message: "Password reset successful" };
|
2025-09-18 12:34:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Post("change-password")
|
2025-10-29 15:54:45 +09:00
|
|
|
|
@Throttle({ default: { limit: 5, ttl: 300 } })
|
2025-09-24 18:00:49 +09:00
|
|
|
|
@UsePipes(new ZodValidationPipe(changePasswordRequestSchema))
|
2025-09-18 12:34:26 +09:00
|
|
|
|
async changePassword(
|
|
|
|
|
|
@Req() req: Request & { user: { id: string } },
|
2025-10-03 16:37:52 +09:00
|
|
|
|
@Body() body: ChangePasswordRequest,
|
2025-09-26 15:51:07 +09:00
|
|
|
|
@Res({ passthrough: true }) res: Response
|
2025-09-18 12:34:26 +09:00
|
|
|
|
) {
|
2025-10-08 10:33:33 +09:00
|
|
|
|
const result = await this.authFacade.changePassword(req.user.id, body, req);
|
2025-09-26 15:51:07 +09:00
|
|
|
|
this.setAuthCookies(res, result.tokens);
|
|
|
|
|
|
return result;
|
2025-09-18 12:34:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Get("me")
|
2025-09-25 13:21:11 +09:00
|
|
|
|
getAuthStatus(@Req() req: Request & { user: { id: string; email: string; role: string } }) {
|
2025-09-18 12:34:26 +09:00
|
|
|
|
// Return basic auth info only - full profile should use /api/me
|
|
|
|
|
|
return {
|
|
|
|
|
|
isAuthenticated: true,
|
|
|
|
|
|
user: {
|
|
|
|
|
|
id: req.user.id,
|
|
|
|
|
|
email: req.user.email,
|
|
|
|
|
|
role: req.user.role,
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Post("sso-link")
|
2025-09-24 18:00:49 +09:00
|
|
|
|
@UsePipes(new ZodValidationPipe(ssoLinkRequestSchema))
|
2025-09-18 12:34:26 +09:00
|
|
|
|
async createSsoLink(
|
2025-09-25 11:44:10 +09:00
|
|
|
|
@Req() req: Request & { user: { id: string } },
|
2025-10-03 16:37:52 +09:00
|
|
|
|
@Body() body: SsoLinkRequest
|
2025-09-18 12:34:26 +09:00
|
|
|
|
) {
|
|
|
|
|
|
const destination = body?.destination;
|
2025-10-02 16:33:25 +09:00
|
|
|
|
return this.authFacade.createSsoLink(req.user.id, destination);
|
2025-09-18 12:34:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|