- 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.
125 lines
4.1 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
}
|