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,
|
|
|
|
|
Res,
|
2026-02-03 11:48:49 +09:00
|
|
|
UnauthorizedException,
|
2025-09-26 16:30:00 +09:00
|
|
|
} from "@nestjs/common";
|
2025-09-26 15:51:07 +09:00
|
|
|
import type { Request, Response } from "express";
|
2025-12-11 11:25:23 +09:00
|
|
|
import { RateLimitGuard, RateLimit } from "@bff/core/rate-limiting/index.js";
|
2026-01-19 11:25:30 +09:00
|
|
|
import { AuthOrchestrator } from "@bff/modules/auth/application/auth-orchestrator.service.js";
|
2026-03-03 18:34:04 +09:00
|
|
|
import { AuthHealthService } from "@bff/modules/auth/application/auth-health.service.js";
|
2025-12-10 16:08:34 +09:00
|
|
|
import { LocalAuthGuard } from "./guards/local-auth.guard.js";
|
2025-11-05 15:47:06 +09:00
|
|
|
import {
|
|
|
|
|
FailedLoginThrottleGuard,
|
|
|
|
|
type RequestWithRateLimit,
|
2025-12-10 16:08:34 +09:00
|
|
|
} from "./guards/failed-login-throttle.guard.js";
|
|
|
|
|
import { LoginResultInterceptor } from "./interceptors/login-result.interceptor.js";
|
2026-01-14 13:54:01 +09:00
|
|
|
import { Public, OptionalAuth } from "../../decorators/public.decorator.js";
|
2025-12-26 13:04:15 +09:00
|
|
|
import { createZodDto, ZodResponse } from "nestjs-zod";
|
2026-03-04 10:17:04 +09:00
|
|
|
import type { RequestWithUser, RequestWithCookies } from "@bff/modules/auth/auth.types.js";
|
2025-12-11 12:03:31 +09:00
|
|
|
import { JoseJwtService } from "../../infra/token/jose-jwt.service.js";
|
2026-02-03 11:48:49 +09:00
|
|
|
import { LoginOtpWorkflowService } from "../../infra/workflows/login-otp-workflow.service.js";
|
2026-03-03 18:34:04 +09:00
|
|
|
import { PasswordWorkflowService } from "../../infra/workflows/password-workflow.service.js";
|
2025-12-12 15:00:11 +09:00
|
|
|
import type { UserAuth } from "@customer-portal/domain/customer";
|
|
|
|
|
import { extractAccessTokenFromRequest } from "../../utils/token-from-request.util.js";
|
2026-02-03 11:48:49 +09:00
|
|
|
import { getRequestFingerprint } from "@bff/core/http/request-context.util.js";
|
2026-01-19 10:40:50 +09:00
|
|
|
import {
|
|
|
|
|
setAuthCookies,
|
|
|
|
|
clearAuthCookies,
|
|
|
|
|
buildSessionInfo,
|
|
|
|
|
ACCESS_COOKIE_PATH,
|
|
|
|
|
REFRESH_COOKIE_PATH,
|
|
|
|
|
TOKEN_TYPE,
|
|
|
|
|
} from "./utils/auth-cookie.util.js";
|
2026-02-03 19:21:48 +09:00
|
|
|
import {
|
|
|
|
|
setTrustedDeviceCookie,
|
|
|
|
|
clearTrustedDeviceCookie,
|
|
|
|
|
getTrustedDeviceToken,
|
|
|
|
|
} from "./utils/trusted-device-cookie.util.js";
|
2026-02-05 15:38:59 +09:00
|
|
|
import { getDevAuthConfig } from "@bff/core/config/auth-dev.config.js";
|
2026-02-03 19:21:48 +09:00
|
|
|
import { TrustedDeviceService } from "../../infra/trusted-device/trusted-device.service.js";
|
2025-09-18 12:34:26 +09:00
|
|
|
|
|
|
|
|
// Import Zod schemas from domain
|
|
|
|
|
import {
|
2025-09-19 16:34:10 +09:00
|
|
|
passwordResetRequestSchema,
|
|
|
|
|
passwordResetSchema,
|
|
|
|
|
setPasswordRequestSchema,
|
|
|
|
|
changePasswordRequestSchema,
|
|
|
|
|
accountStatusRequestSchema,
|
|
|
|
|
ssoLinkRequestSchema,
|
|
|
|
|
checkPasswordNeededRequestSchema,
|
2025-09-20 11:35:40 +09:00
|
|
|
refreshTokenRequestSchema,
|
2025-11-04 13:28:36 +09:00
|
|
|
checkPasswordNeededResponseSchema,
|
2026-02-03 11:48:49 +09:00
|
|
|
loginVerifyOtpRequestSchema,
|
2025-10-03 16:37:52 +09:00
|
|
|
} from "@customer-portal/domain/auth";
|
2025-09-26 15:51:07 +09:00
|
|
|
|
2026-01-19 10:40:50 +09:00
|
|
|
// Re-export for backward compatibility with tests
|
|
|
|
|
export { ACCESS_COOKIE_PATH, REFRESH_COOKIE_PATH, TOKEN_TYPE };
|
|
|
|
|
|
2025-12-26 13:04:15 +09:00
|
|
|
class AccountStatusRequestDto extends createZodDto(accountStatusRequestSchema) {}
|
|
|
|
|
class RefreshTokenRequestDto extends createZodDto(refreshTokenRequestSchema) {}
|
|
|
|
|
class SetPasswordRequestDto extends createZodDto(setPasswordRequestSchema) {}
|
|
|
|
|
class CheckPasswordNeededRequestDto extends createZodDto(checkPasswordNeededRequestSchema) {}
|
|
|
|
|
class PasswordResetRequestDto extends createZodDto(passwordResetRequestSchema) {}
|
|
|
|
|
class ResetPasswordRequestDto extends createZodDto(passwordResetSchema) {}
|
|
|
|
|
class ChangePasswordRequestDto extends createZodDto(changePasswordRequestSchema) {}
|
|
|
|
|
class SsoLinkRequestDto extends createZodDto(ssoLinkRequestSchema) {}
|
|
|
|
|
class CheckPasswordNeededResponseDto extends createZodDto(checkPasswordNeededResponseSchema) {}
|
2026-02-03 11:48:49 +09:00
|
|
|
class LoginVerifyOtpRequestDto extends createZodDto(loginVerifyOtpRequestSchema) {}
|
2025-12-26 13:04:15 +09:00
|
|
|
|
2025-09-18 12:34:26 +09:00
|
|
|
@Controller("auth")
|
2026-03-06 17:52:57 +09:00
|
|
|
@UseGuards(RateLimitGuard)
|
|
|
|
|
@RateLimit({ limit: 10, ttl: 300 }) // 10 requests per 5 minutes per IP+UA (controller default)
|
2025-09-19 17:37:46 +09:00
|
|
|
export class AuthController {
|
2025-11-17 11:49:58 +09:00
|
|
|
constructor(
|
2026-01-19 11:25:30 +09:00
|
|
|
private authOrchestrator: AuthOrchestrator,
|
2026-02-03 11:48:49 +09:00
|
|
|
private readonly jwtService: JoseJwtService,
|
2026-02-03 19:21:48 +09:00
|
|
|
private readonly loginOtpWorkflow: LoginOtpWorkflowService,
|
2026-03-03 18:34:04 +09:00
|
|
|
private readonly trustedDeviceService: TrustedDeviceService,
|
|
|
|
|
private readonly passwordWorkflow: PasswordWorkflowService,
|
|
|
|
|
private readonly healthService: AuthHealthService
|
2025-11-17 11:49:58 +09:00
|
|
|
) {}
|
2025-09-18 12:34:26 +09:00
|
|
|
|
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()
|
|
|
|
|
@Get("health-check")
|
|
|
|
|
async healthCheck() {
|
2026-03-03 18:34:04 +09:00
|
|
|
return this.healthService.check();
|
2025-09-18 12:34:26 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Public()
|
|
|
|
|
@Post("account-status")
|
2025-12-26 13:04:15 +09:00
|
|
|
async accountStatus(@Body() body: AccountStatusRequestDto) {
|
2026-01-19 11:25:30 +09:00
|
|
|
return this.authOrchestrator.getAccountStatus(body.email);
|
2025-09-18 12:34:26 +09:00
|
|
|
}
|
|
|
|
|
|
2026-02-03 11:48:49 +09:00
|
|
|
/**
|
|
|
|
|
* POST /auth/login - Initiate login with credentials
|
|
|
|
|
*
|
|
|
|
|
* After valid credential check:
|
2026-02-03 19:21:48 +09:00
|
|
|
* 1. Check if device is trusted (has valid trusted device cookie for this user)
|
|
|
|
|
* 2. If trusted, skip OTP and complete login directly
|
|
|
|
|
* 3. Otherwise, generate OTP and send to user's email
|
|
|
|
|
* 4. Return session token for OTP verification
|
|
|
|
|
* 5. User must call /auth/login/verify-otp to complete login
|
2026-02-03 11:48:49 +09:00
|
|
|
*/
|
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(
|
2026-02-03 19:21:48 +09:00
|
|
|
@Req() req: RequestWithUser & RequestWithRateLimit & RequestWithCookies,
|
2025-09-26 15:51:07 +09:00
|
|
|
@Res({ passthrough: true }) res: Response
|
|
|
|
|
) {
|
2026-02-03 18:28:38 +09:00
|
|
|
this.applyAuthRateLimitHeaders(req, res);
|
|
|
|
|
|
|
|
|
|
// In dev mode with SKIP_OTP=true, skip OTP and complete login directly
|
2026-02-05 15:38:59 +09:00
|
|
|
if (getDevAuthConfig().skipOtp) {
|
2026-02-03 18:28:38 +09:00
|
|
|
const loginResult = await this.authOrchestrator.completeLogin(
|
|
|
|
|
{ id: req.user.id, email: req.user.email, role: req.user.role ?? "USER" },
|
|
|
|
|
req
|
|
|
|
|
);
|
|
|
|
|
setAuthCookies(res, loginResult.tokens);
|
|
|
|
|
return {
|
|
|
|
|
user: loginResult.user,
|
|
|
|
|
session: buildSessionInfo(loginResult.tokens),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 19:21:48 +09:00
|
|
|
// Check if this is a trusted device for the authenticated user
|
|
|
|
|
const trustedDeviceToken = getTrustedDeviceToken(req);
|
|
|
|
|
const trustedDeviceResult = await this.trustedDeviceService.validateTrustedDevice(
|
|
|
|
|
trustedDeviceToken,
|
|
|
|
|
req.user.id
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// If device is trusted for this user, skip OTP and complete login
|
|
|
|
|
if (trustedDeviceResult.valid) {
|
|
|
|
|
const loginResult = await this.authOrchestrator.completeLogin(
|
|
|
|
|
{ id: req.user.id, email: req.user.email, role: req.user.role ?? "USER" },
|
|
|
|
|
req
|
|
|
|
|
);
|
|
|
|
|
setAuthCookies(res, loginResult.tokens);
|
|
|
|
|
return {
|
|
|
|
|
user: loginResult.user,
|
|
|
|
|
session: buildSessionInfo(loginResult.tokens),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 11:48:49 +09:00
|
|
|
// Credentials validated by LocalAuthGuard - now initiate OTP
|
|
|
|
|
const fingerprint = getRequestFingerprint(req);
|
|
|
|
|
const otpResult = await this.loginOtpWorkflow.initiateOtp(
|
|
|
|
|
{
|
|
|
|
|
id: req.user.id,
|
|
|
|
|
email: req.user.email,
|
|
|
|
|
role: req.user.role ?? "USER",
|
|
|
|
|
},
|
|
|
|
|
fingerprint
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Return OTP required response - no tokens issued yet
|
|
|
|
|
return {
|
|
|
|
|
requiresOtp: true,
|
|
|
|
|
sessionToken: otpResult.sessionToken,
|
|
|
|
|
maskedEmail: otpResult.maskedEmail,
|
|
|
|
|
expiresAt: otpResult.expiresAt,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /auth/login/verify-otp - Complete login with OTP verification
|
|
|
|
|
*
|
|
|
|
|
* Verifies the OTP code and issues auth tokens on success.
|
2026-02-03 19:21:48 +09:00
|
|
|
* If rememberDevice is true, sets a trusted device cookie to skip OTP on future logins.
|
2026-02-03 11:48:49 +09:00
|
|
|
*/
|
|
|
|
|
@Public()
|
|
|
|
|
@Post("login/verify-otp")
|
|
|
|
|
@HttpCode(200)
|
|
|
|
|
async verifyLoginOtp(
|
|
|
|
|
@Body() body: LoginVerifyOtpRequestDto,
|
|
|
|
|
@Req() req: Request,
|
|
|
|
|
@Res({ passthrough: true }) res: Response
|
|
|
|
|
) {
|
|
|
|
|
const fingerprint = getRequestFingerprint(req);
|
|
|
|
|
const result = await this.loginOtpWorkflow.verifyOtp(body.sessionToken, body.code, fingerprint);
|
|
|
|
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
throw new UnauthorizedException(result.error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// OTP verified - complete login with token generation
|
|
|
|
|
const loginResult = await this.authOrchestrator.completeLogin(
|
|
|
|
|
{ id: result.userId, email: result.email, role: result.role },
|
|
|
|
|
req
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
setAuthCookies(res, loginResult.tokens);
|
|
|
|
|
|
2026-02-03 19:21:48 +09:00
|
|
|
// If user wants to remember this device, create and set trusted device cookie
|
|
|
|
|
if (body.rememberDevice) {
|
|
|
|
|
const rawUserAgent = req.headers["user-agent"];
|
|
|
|
|
const userAgent = typeof rawUserAgent === "string" ? rawUserAgent : undefined;
|
|
|
|
|
const trustedDeviceToken = await this.trustedDeviceService.createTrustedDevice(
|
|
|
|
|
result.userId,
|
|
|
|
|
userAgent
|
|
|
|
|
);
|
|
|
|
|
setTrustedDeviceCookie(res, trustedDeviceToken, this.trustedDeviceService.getTtlMs());
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 11:48:49 +09:00
|
|
|
return {
|
|
|
|
|
user: loginResult.user,
|
|
|
|
|
session: buildSessionInfo(loginResult.tokens),
|
|
|
|
|
};
|
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
|
|
|
|
|
) {
|
2025-12-12 15:00:11 +09:00
|
|
|
const token = extractAccessTokenFromRequest(req);
|
2025-11-05 15:47:06 +09:00
|
|
|
let userId = req.user?.id;
|
|
|
|
|
|
|
|
|
|
if (!userId && token) {
|
2025-12-11 12:03:31 +09:00
|
|
|
const payload = await this.jwtService.verifyAllowExpired<{ sub?: string }>(token);
|
|
|
|
|
if (payload?.sub) {
|
|
|
|
|
userId = payload.sub;
|
2025-11-05 15:47:06 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 19:21:48 +09:00
|
|
|
// Revoke trusted device for this user if they have one
|
|
|
|
|
const trustedDeviceToken = getTrustedDeviceToken(req);
|
|
|
|
|
if (trustedDeviceToken && userId) {
|
|
|
|
|
// Validate to get device ID, then revoke
|
|
|
|
|
const validation = await this.trustedDeviceService.validateTrustedDevice(
|
|
|
|
|
trustedDeviceToken,
|
|
|
|
|
userId
|
|
|
|
|
);
|
|
|
|
|
if (validation.deviceId) {
|
|
|
|
|
await this.trustedDeviceService.revokeDevice(validation.deviceId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-19 11:25:30 +09:00
|
|
|
await this.authOrchestrator.logout(userId, token, req as Request);
|
2025-11-05 15:47:06 +09:00
|
|
|
|
|
|
|
|
// Always clear cookies, even if session expired
|
2026-01-19 10:40:50 +09:00
|
|
|
clearAuthCookies(res);
|
2026-02-03 19:21:48 +09:00
|
|
|
clearTrustedDeviceCookie(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-09-26 15:51:07 +09:00
|
|
|
async refreshToken(
|
2025-12-26 13:04:15 +09:00
|
|
|
@Body() body: RefreshTokenRequestDto,
|
2025-09-26 15:51:07 +09:00
|
|
|
@Req() req: RequestWithCookies,
|
|
|
|
|
@Res({ passthrough: true }) res: Response
|
|
|
|
|
) {
|
2026-01-15 11:28:25 +09:00
|
|
|
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;
|
2026-01-19 11:25:30 +09:00
|
|
|
const result = await this.authOrchestrator.refreshTokens(refreshToken, {
|
2026-01-15 11:28:25 +09:00
|
|
|
deviceId: body.deviceId ?? undefined,
|
|
|
|
|
userAgent: userAgent ?? undefined,
|
2025-09-20 11:35:40 +09:00
|
|
|
});
|
2026-01-19 10:40:50 +09:00
|
|
|
setAuthCookies(res, result.tokens);
|
|
|
|
|
return { user: result.user, session: buildSessionInfo(result.tokens) };
|
2025-09-20 11:35:40 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-18 12:34:26 +09:00
|
|
|
@Public()
|
|
|
|
|
@Post("set-password")
|
2025-09-26 15:51:07 +09:00
|
|
|
async setPassword(
|
2025-12-26 13:04:15 +09:00
|
|
|
@Body() setPasswordData: SetPasswordRequestDto,
|
2025-09-26 15:51:07 +09:00
|
|
|
@Req() _req: Request,
|
|
|
|
|
@Res({ passthrough: true }) res: Response
|
|
|
|
|
) {
|
2026-03-03 18:34:04 +09:00
|
|
|
const result = await this.passwordWorkflow.setPassword(
|
|
|
|
|
setPasswordData.email,
|
|
|
|
|
setPasswordData.password
|
|
|
|
|
);
|
2026-01-19 10:40:50 +09:00
|
|
|
setAuthCookies(res, result.tokens);
|
|
|
|
|
return { user: result.user, session: buildSessionInfo(result.tokens) };
|
2025-09-18 12:34:26 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Public()
|
|
|
|
|
@Post("check-password-needed")
|
|
|
|
|
@HttpCode(200)
|
2025-12-26 13:04:15 +09:00
|
|
|
@ZodResponse({
|
|
|
|
|
status: 200,
|
|
|
|
|
description: "Check if password is needed",
|
|
|
|
|
type: CheckPasswordNeededResponseDto,
|
|
|
|
|
})
|
|
|
|
|
async checkPasswordNeeded(@Body() data: CheckPasswordNeededRequestDto) {
|
2026-03-03 18:34:04 +09:00
|
|
|
return this.passwordWorkflow.checkPasswordNeeded(data.email);
|
2025-09-18 12:34:26 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Public()
|
|
|
|
|
@Post("request-password-reset")
|
2025-12-26 13:04:15 +09:00
|
|
|
async requestPasswordReset(@Body() body: PasswordResetRequestDto, @Req() req: Request) {
|
2026-03-03 18:34:04 +09:00
|
|
|
await this.passwordWorkflow.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-10-22 10:58:16 +09:00
|
|
|
async resetPassword(
|
2025-12-26 13:04:15 +09:00
|
|
|
@Body() body: ResetPasswordRequestDto,
|
2025-10-22 10:58:16 +09:00
|
|
|
@Res({ passthrough: true }) res: Response
|
|
|
|
|
) {
|
2026-03-03 18:34:04 +09:00
|
|
|
await this.passwordWorkflow.resetPassword(body.token, body.password);
|
2025-10-03 17:33:39 +09:00
|
|
|
|
|
|
|
|
// Clear auth cookies after password reset to force re-login
|
2026-01-19 10:40:50 +09:00
|
|
|
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")
|
|
|
|
|
async changePassword(
|
|
|
|
|
@Req() req: Request & { user: { id: string } },
|
2025-12-26 13:04:15 +09:00
|
|
|
@Body() body: ChangePasswordRequestDto,
|
2025-09-26 15:51:07 +09:00
|
|
|
@Res({ passthrough: true }) res: Response
|
2025-09-18 12:34:26 +09:00
|
|
|
) {
|
2026-03-03 18:34:04 +09:00
|
|
|
const result = await this.passwordWorkflow.changePassword(req.user.id, body, req);
|
2026-01-19 10:40:50 +09:00
|
|
|
setAuthCookies(res, result.tokens);
|
|
|
|
|
return { user: result.user, session: buildSessionInfo(result.tokens) };
|
2025-09-18 12:34:26 +09:00
|
|
|
}
|
|
|
|
|
|
2026-01-14 13:54:01 +09:00
|
|
|
/**
|
|
|
|
|
* GET /auth/me - Check authentication status
|
|
|
|
|
*
|
|
|
|
|
* Uses @OptionalAuth: returns isAuthenticated: false if not logged in,
|
|
|
|
|
* 401 only if session cookie is present but expired/invalid
|
|
|
|
|
*/
|
|
|
|
|
@OptionalAuth()
|
2025-09-18 12:34:26 +09:00
|
|
|
@Get("me")
|
2026-01-14 13:54:01 +09:00
|
|
|
getAuthStatus(@Req() req: Request & { user?: UserAuth }) {
|
|
|
|
|
if (!req.user) {
|
|
|
|
|
return { isAuthenticated: false };
|
|
|
|
|
}
|
2025-12-12 15:00:11 +09:00
|
|
|
return { isAuthenticated: true, user: req.user };
|
2025-09-18 12:34:26 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Post("sso-link")
|
|
|
|
|
async createSsoLink(
|
2025-09-25 11:44:10 +09:00
|
|
|
@Req() req: Request & { user: { id: string } },
|
2025-12-26 13:04:15 +09:00
|
|
|
@Body() body: SsoLinkRequestDto
|
2025-09-18 12:34:26 +09:00
|
|
|
) {
|
|
|
|
|
const destination = body?.destination;
|
2026-01-19 11:25:30 +09:00
|
|
|
return this.authOrchestrator.createSsoLink(req.user.id, destination);
|
2025-09-18 12:34:26 +09:00
|
|
|
}
|
|
|
|
|
}
|