init user system with keycloak
This commit is contained in:
267
src/users/users.controller.ts
Normal file
267
src/users/users.controller.ts
Normal file
@@ -0,0 +1,267 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Put,
|
||||
Delete,
|
||||
Param,
|
||||
Body,
|
||||
HttpCode,
|
||||
UseGuards,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiParam,
|
||||
ApiBearerAuth,
|
||||
ApiUnauthorizedResponse,
|
||||
ApiForbiddenResponse,
|
||||
} from '@nestjs/swagger';
|
||||
import { UsersService } from './users.service';
|
||||
import { User } from './users.entity';
|
||||
import { UpdateUserDto } from './dto/update-user.dto';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import {
|
||||
CurrentUser,
|
||||
type AuthenticatedUser,
|
||||
} from '../auth/decorators/current-user.decorator';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
|
||||
/**
|
||||
* Users Controller
|
||||
*
|
||||
* Handles user-related HTTP endpoints.
|
||||
* All endpoints require authentication via Keycloak JWT token.
|
||||
*
|
||||
* Note: User creation is handled automatically during authentication flow.
|
||||
* Users cannot be created directly via API - they must authenticate via Keycloak.
|
||||
*/
|
||||
@ApiTags('users')
|
||||
@Controller('users')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
export class UsersController {
|
||||
private readonly logger = new Logger(UsersController.name);
|
||||
|
||||
constructor(
|
||||
private readonly usersService: UsersService,
|
||||
private readonly authService: AuthService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get current authenticated user's profile.
|
||||
* This endpoint syncs the user from Keycloak token on each request.
|
||||
*/
|
||||
@Get('me')
|
||||
@ApiOperation({
|
||||
summary: 'Get current user profile',
|
||||
description:
|
||||
'Returns the authenticated user profile. Automatically syncs data from Keycloak token.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Current user profile',
|
||||
type: User,
|
||||
})
|
||||
@ApiUnauthorizedResponse({
|
||||
description: 'Invalid or missing JWT token',
|
||||
})
|
||||
async getCurrentUser(
|
||||
@CurrentUser() authUser: AuthenticatedUser,
|
||||
): Promise<User> {
|
||||
this.logger.debug(`Get current user: ${authUser.keycloakSub}`);
|
||||
|
||||
// Sync user from token (creates if doesn't exist, updates if exists)
|
||||
const user = await this.authService.syncUserFromToken(authUser);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current authenticated user's profile.
|
||||
*/
|
||||
@Put('me')
|
||||
@ApiOperation({
|
||||
summary: 'Update current user profile',
|
||||
description:
|
||||
'Updates the authenticated user profile. Users can only update their own profile.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'User profile updated successfully',
|
||||
type: User,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Invalid request data',
|
||||
})
|
||||
@ApiUnauthorizedResponse({
|
||||
description: 'Invalid or missing JWT token',
|
||||
})
|
||||
async updateCurrentUser(
|
||||
@CurrentUser() authUser: AuthenticatedUser,
|
||||
@Body() updateUserDto: UpdateUserDto,
|
||||
): Promise<User> {
|
||||
this.logger.log(`Update current user: ${authUser.keycloakSub}`);
|
||||
|
||||
// First ensure user exists in our system
|
||||
const user = await this.authService.syncUserFromToken(authUser);
|
||||
|
||||
// Update the user's profile
|
||||
return Promise.resolve(
|
||||
this.usersService.update(user.id, updateUserDto, authUser.keycloakSub),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user by their ID.
|
||||
* Currently allows any authenticated user to view other users.
|
||||
* Consider adding additional authorization if needed.
|
||||
*/
|
||||
@Get(':id')
|
||||
@ApiOperation({
|
||||
summary: 'Get a user by ID',
|
||||
description: 'Retrieves a user profile by their internal ID.',
|
||||
})
|
||||
@ApiParam({
|
||||
name: 'id',
|
||||
description: 'User internal UUID',
|
||||
example: '550e8400-e29b-41d4-a716-446655440000',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'User found',
|
||||
type: User,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 404,
|
||||
description: 'User not found',
|
||||
})
|
||||
@ApiUnauthorizedResponse({
|
||||
description: 'Invalid or missing JWT token',
|
||||
})
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@CurrentUser() authUser: AuthenticatedUser,
|
||||
): Promise<User> {
|
||||
this.logger.debug(
|
||||
`Get user by ID: ${id} (requested by ${authUser.keycloakSub})`,
|
||||
);
|
||||
return Promise.resolve(this.usersService.findOne(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a user by their ID.
|
||||
* Users can only update their own profile (enforced by service layer).
|
||||
*/
|
||||
@Put(':id')
|
||||
@ApiOperation({
|
||||
summary: 'Update a user by ID',
|
||||
description:
|
||||
'Updates a user profile. Users can only update their own profile.',
|
||||
})
|
||||
@ApiParam({
|
||||
name: 'id',
|
||||
description: 'User internal UUID',
|
||||
example: '550e8400-e29b-41d4-a716-446655440000',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'User updated successfully',
|
||||
type: User,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Invalid request data',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 404,
|
||||
description: 'User not found',
|
||||
})
|
||||
@ApiForbiddenResponse({
|
||||
description: 'Cannot update another user profile',
|
||||
})
|
||||
@ApiUnauthorizedResponse({
|
||||
description: 'Invalid or missing JWT token',
|
||||
})
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() updateUserDto: UpdateUserDto,
|
||||
@CurrentUser() authUser: AuthenticatedUser,
|
||||
): Promise<User> {
|
||||
this.logger.log(`Update user ${id} (requested by ${authUser.keycloakSub})`);
|
||||
return Promise.resolve(
|
||||
this.usersService.update(id, updateUserDto, authUser.keycloakSub),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete current authenticated user's account.
|
||||
* Note: This only deletes the local user record.
|
||||
* The user still exists in Keycloak and can re-authenticate.
|
||||
*/
|
||||
@Delete('me')
|
||||
@ApiOperation({
|
||||
summary: 'Delete current user account',
|
||||
description:
|
||||
'Deletes the authenticated user account. Only removes local data; user still exists in Keycloak.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 204,
|
||||
description: 'User account deleted successfully',
|
||||
})
|
||||
@ApiUnauthorizedResponse({
|
||||
description: 'Invalid or missing JWT token',
|
||||
})
|
||||
@HttpCode(204)
|
||||
async deleteCurrentUser(
|
||||
@CurrentUser() authUser: AuthenticatedUser,
|
||||
): Promise<void> {
|
||||
this.logger.log(`Delete current user: ${authUser.keycloakSub}`);
|
||||
|
||||
// First ensure user exists in our system
|
||||
const user = await this.authService.syncUserFromToken(authUser);
|
||||
|
||||
// Delete the user's account
|
||||
this.usersService.delete(user.id, authUser.keycloakSub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user by their ID.
|
||||
* Users can only delete their own account (enforced by service layer).
|
||||
*/
|
||||
@Delete(':id')
|
||||
@ApiOperation({
|
||||
summary: 'Delete a user by ID',
|
||||
description:
|
||||
'Deletes a user account. Users can only delete their own account. Only removes local data; user still exists in Keycloak.',
|
||||
})
|
||||
@ApiParam({
|
||||
name: 'id',
|
||||
description: 'User internal UUID',
|
||||
example: '550e8400-e29b-41d4-a716-446655440000',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 204,
|
||||
description: 'User deleted successfully',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 404,
|
||||
description: 'User not found',
|
||||
})
|
||||
@ApiForbiddenResponse({
|
||||
description: 'Cannot delete another user account',
|
||||
})
|
||||
@ApiUnauthorizedResponse({
|
||||
description: 'Invalid or missing JWT token',
|
||||
})
|
||||
@HttpCode(204)
|
||||
delete(
|
||||
@Param('id') id: string,
|
||||
@CurrentUser() authUser: AuthenticatedUser,
|
||||
): void {
|
||||
this.logger.log(`Delete user ${id} (requested by ${authUser.keycloakSub})`);
|
||||
this.usersService.delete(id, authUser.keycloakSub);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user