Files
trackgo-be/src/modules/auth/auth.controller.ts
T
shancheas 07550b3167 Add privilege management system with related migrations and guards
- Introduced a new `PrivilegesModule` to manage user privileges and access control.
- Added `RequirePrivilege` decorator to enforce privilege checks on controller handlers.
- Implemented `PrivilegesGuard` to handle authorization based on user privileges.
- Created database migrations for `privileges`, `privilege_keys`, and `privilege_details` tables.
- Updated user model to include `is_superadmin` field for enhanced access control.
- Added unit tests for the new privileges functionality and guards to ensure correct behavior.
2026-08-24 11:31:13 +07:00

125 lines
4.1 KiB
TypeScript

import { Body, Controller, Get, HttpCode, Post } from '@nestjs/common';
import {
ApiBadRequestResponse,
ApiBearerAuth,
ApiConflictResponse,
ApiCreatedResponse,
ApiNoContentResponse,
ApiOkResponse,
ApiOperation,
ApiTags,
ApiTooManyRequestsResponse,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';
import { Throttle } from '@nestjs/throttler';
import type { AuthUser } from '../../common/auth/auth-user';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import { Public } from '../../common/decorators/public.decorator';
import { BEARER_AUTH_NAME } from '../../common/swagger/setup-swagger';
import { PrivilegesService } from '../privileges/privileges.service';
import { UsersService } from '../users/users.service';
import { AuthService } from './auth.service';
import {
LoginDto,
MeResponseDto,
RefreshTokenDto,
RegisterDto,
TokenPairDto,
} from './dto/auth.dto';
@ApiTags('auth')
@Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly usersService: UsersService,
private readonly privilegesService: PrivilegesService,
) {}
@Public()
@Throttle({ default: { limit: 5, ttl: 60_000 } })
@Post('register')
@ApiOperation({ summary: 'Register a new user' })
@ApiCreatedResponse({ type: TokenPairDto })
@ApiBadRequestResponse({ description: 'Validation failed' })
@ApiConflictResponse({ description: 'Username already registered' })
@ApiTooManyRequestsResponse({ description: 'Rate limit exceeded' })
register(@Body() dto: RegisterDto): Promise<TokenPairDto> {
return this.authService.register(dto.username, dto.password);
}
@Public()
@Throttle({ default: { limit: 5, ttl: 60_000 } })
@Post('login')
@HttpCode(200)
@ApiOperation({ summary: 'Log in with username and password' })
@ApiOkResponse({ type: TokenPairDto })
@ApiBadRequestResponse({ description: 'Validation failed' })
@ApiUnauthorizedResponse({ description: 'Invalid credentials' })
@ApiTooManyRequestsResponse({ description: 'Rate limit exceeded' })
login(@Body() dto: LoginDto): Promise<TokenPairDto> {
return this.authService.login(dto.username, dto.password);
}
@Public()
@Throttle({ default: { limit: 10, ttl: 60_000 } })
@Post('refresh')
@HttpCode(200)
@ApiOperation({
summary: 'Rotate refresh token and issue a new access token',
})
@ApiOkResponse({ type: TokenPairDto })
@ApiBadRequestResponse({ description: 'Validation failed' })
@ApiUnauthorizedResponse({ description: 'Invalid refresh token' })
@ApiTooManyRequestsResponse({ description: 'Rate limit exceeded' })
refresh(@Body() dto: RefreshTokenDto): Promise<TokenPairDto> {
return this.authService.refresh(dto.refreshToken);
}
@Public()
@Throttle({ default: { limit: 10, ttl: 60_000 } })
@Post('revoke')
@HttpCode(204)
@ApiOperation({ summary: 'Revoke a refresh token session' })
@ApiNoContentResponse({ description: 'Session revoked (or already invalid)' })
@ApiBadRequestResponse({ description: 'Validation failed' })
@ApiTooManyRequestsResponse({ description: 'Rate limit exceeded' })
async revoke(@Body() dto: RefreshTokenDto): Promise<void> {
await this.authService.revoke(dto.refreshToken);
}
@Get('me')
@ApiBearerAuth(BEARER_AUTH_NAME)
@ApiOperation({ summary: 'Get the current authenticated user' })
@ApiOkResponse({ type: MeResponseDto })
@ApiUnauthorizedResponse({ description: 'Missing or invalid access token' })
async me(@CurrentUser() user: AuthUser): Promise<MeResponseDto> {
const full = await this.usersService.findById(user.id);
const isSuperadmin = full?.isSuperadmin ?? user.isSuperadmin;
if (!full?.privilegeId) {
return {
id: user.id,
username: user.username,
isSuperadmin,
privilege: null,
permissions: {},
};
}
const privilege = await this.privilegesService.findPrivilegeSummary(
full.privilegeId,
);
const permissions = privilege
? await this.privilegesService.getPermissionsMap(privilege.id)
: {};
return {
id: user.id,
username: user.username,
isSuperadmin,
privilege,
permissions,
};
}
}