Files
imen_pishghaman/apps/api/src/auth/auth.controller.ts
T

102 lines
2.9 KiB
TypeScript
Raw Normal View History

import { Body, Controller, Get, HttpCode, Module, Post, Req, Res } from "@nestjs/common";
import { JwtModule, type JwtSignOptions } from "@nestjs/jwt";
import { Throttle } from "@nestjs/throttler";
import {
type AuthResponse,
type ChangePasswordInput,
changePasswordSchema,
type LoginInput,
loginSchema,
} from "@imen/shared";
import type { Request, Response } from "express";
import { env, isProd } from "../config/env.js";
import { AdminAuth, type AuthUser, CurrentUser } from "../common/auth.js";
import { ZodPipe } from "../common/zod.pipe.js";
import { AuthService, type IssuedTokens } from "./auth.service.js";
const COOKIE = "imen_rt";
const COOKIE_PATH = "/api/admin/auth";
const ua = (req: Request) => req.headers["user-agent"] ?? "";
const readCookie = (req: Request): string | undefined =>
(req.cookies as Record<string, string> | undefined)?.[COOKIE];
@Controller("admin/auth")
export class AuthController {
constructor(private readonly auth: AuthService) {}
@Post("login")
@HttpCode(200)
@Throttle({ default: { limit: 8, ttl: 60_000 } })
async login(
@Body(new ZodPipe(loginSchema)) body: LoginInput,
@Req() req: Request,
@Res({ passthrough: true }) res: Response,
): Promise<AuthResponse> {
return this.respond(res, await this.auth.login(body, ua(req)));
}
@Post("refresh")
@HttpCode(200)
@Throttle({ default: { limit: 30, ttl: 60_000 } })
async refresh(
@Req() req: Request,
@Res({ passthrough: true }) res: Response,
): Promise<AuthResponse> {
try {
return this.respond(res, await this.auth.refresh(readCookie(req), ua(req)));
} catch (e) {
res.clearCookie(COOKIE, { path: COOKIE_PATH });
throw e;
}
}
@Post("logout")
@HttpCode(204)
async logout(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
await this.auth.logout(readCookie(req));
res.clearCookie(COOKIE, { path: COOKIE_PATH });
}
@Get("me")
@AdminAuth()
me(@CurrentUser() user: AuthUser) {
return this.auth.me(user.id);
}
@Post("change-password")
@HttpCode(204)
@AdminAuth()
async changePassword(
@Body(new ZodPipe(changePasswordSchema)) body: ChangePasswordInput,
@CurrentUser() user: AuthUser,
) {
await this.auth.changePassword(user.id, body);
}
private respond(res: Response, t: IssuedTokens): AuthResponse {
res.cookie(COOKIE, t.refreshToken, {
httpOnly: true,
secure: isProd && env.COOKIE_SECURE,
sameSite: "strict",
path: COOKIE_PATH,
expires: t.refreshExpiresAt,
});
return { accessToken: t.accessToken, user: t.user };
}
}
@Module({
imports: [
JwtModule.register({
global: true,
secret: env.JWT_SECRET,
signOptions: { expiresIn: env.JWT_ACCESS_TTL as JwtSignOptions["expiresIn"] },
}),
],
controllers: [AuthController],
providers: [AuthService],
exports: [AuthService],
})
export class AuthModule {}