Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions drips/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import {
Controller,
Post,
Get,
Body,
HttpCode,
HttpStatus,
Ip,
Headers,
Query,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { AuthService } from './auth.service';
import { UserService } from './services/user.service';
import { RegisterDto } from './dto/register.dto';
import { LoginDto } from './dto/login.dto';
import { AuthResponseDto } from './dto/auth-response.dto';
import { RefreshTokenDto } from './dto/refresh-token.dto';
import { RequestVerificationDto } from './dto/request-verification.dto';
import { VerifyEmailDto } from './dto/verify-email.dto';
import { RequestPasswordResetDto } from './dto/request-password-reset.dto';
import { ResetPasswordDto } from './dto/reset-password.dto';
import { CreateUserDto } from './dto/create-user.dto';
import { ListUsersQueryDto } from './dto/list-users-query.dto';
import { UserResponseDto } from './dto/user-response.dto';
import { ListUsersResponseDto } from './dto/list-users-response.dto';

@ApiTags('auth')
@Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly userService: UserService,
) {}

@Post('register')
@ApiOperation({ summary: 'Register a new user' })
@ApiResponse({
status: 201,
description: 'User successfully registered',
type: AuthResponseDto,
})
@ApiResponse({
status: 409,
description: 'User with this email already exists',
})
@ApiResponse({
status: 400,
description: 'Validation error',
})
async register(@Body() registerDto: RegisterDto): Promise<AuthResponseDto> {
return this.authService.register(registerDto);
}

@Post('login')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Login with email and password' })
@ApiResponse({
status: 200,
description: 'User successfully logged in',
type: AuthResponseDto,
})
@ApiResponse({
status: 401,
description: 'Invalid email or password',
})
@ApiResponse({
status: 400,
description: 'Validation error',
})
async login(
@Body() loginDto: LoginDto,
@Ip() ip: string,
@Headers('user-agent') userAgent?: string,
): Promise<AuthResponseDto> {
return this.authService.login(loginDto, ip, userAgent);
}

@Post('refresh')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Refresh access token' })
@ApiResponse({ status: 200, description: 'Tokens refreshed' })
@ApiResponse({ status: 401, description: 'Invalid or expired refresh token' })
async refresh(
@Body() dto: RefreshTokenDto,
@Ip() ip: string,
@Headers('user-agent') userAgent?: string,
) {
return this.authService.rotateRefreshToken(dto.refreshToken, ip, userAgent);
}

@Post('logout')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Logout and revoke refresh token' })
@ApiResponse({ status: 200, description: 'Logged out successfully' })
async logout(@Body() dto: RefreshTokenDto) {
await this.authService.revokeRefreshToken(dto.refreshToken);
return { message: 'Logged out successfully' };
}

@Post('request-verification')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Request email verification' })
@ApiResponse({
status: 200,
description: 'Verification email sent if user exists',
})
async requestVerification(@Body() dto: RequestVerificationDto) {
await this.authService.requestVerification(dto);
return { message: 'Verification email sent' };
}

@Post('verify-email')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Verify email with token' })
@ApiResponse({ status: 200, description: 'Email verified successfully' })
@ApiResponse({ status: 400, description: 'Invalid or expired token' })
async verifyEmail(@Body() dto: VerifyEmailDto) {
await this.authService.verifyEmail(dto);
return { message: 'Email verified successfully' };
}

@Post('request-password-reset')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Request password reset' })
@ApiResponse({ status: 200, description: 'Reset email sent if user exists' })
async requestPasswordReset(@Body() dto: RequestPasswordResetDto) {
await this.authService.requestPasswordReset(dto);
return { message: 'Password reset email sent' };
}

@Post('reset-password')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Reset password with token' })
@ApiResponse({ status: 200, description: 'Password reset successfully' })
@ApiResponse({ status: 400, description: 'Invalid or expired token' })
async resetPassword(@Body() dto: ResetPasswordDto) {
await this.authService.resetPassword(dto);
return { message: 'Password reset successfully' };
}

// ========== TEMPORARY ADMIN-ONLY ENDPOINTS ==========

@Post('admin/users')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({
summary: 'Create a new user (temporary admin endpoint for testing)',
})
@ApiResponse({
status: 201,
description: 'User created successfully',
type: UserResponseDto,
})
@ApiResponse({
status: 400,
description: 'Validation error or user already exists',
})
async createUser(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
const user = await this.userService.createUser(
createUserDto.email,
createUserDto.password,
createUserDto.roles,
createUserDto.status,
createUserDto.firstName,
createUserDto.lastName,
);
return new UserResponseDto(user);
}

@Get('admin/users')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'List all users with pagination (temporary admin endpoint for testing)',
})
@ApiResponse({
status: 200,
description: 'Users retrieved successfully',
type: ListUsersResponseDto,
})
@ApiResponse({
status: 400,
description: 'Invalid pagination parameters',
})
async listUsers(
@Query() queryDto: ListUsersQueryDto,
): Promise<ListUsersResponseDto> {
const { users, total, page, limit } = await this.userService.listUsers(
queryDto.page,
queryDto.limit,
);

const userDtos = users.map((user) => new UserResponseDto(user));
return new ListUsersResponseDto(userDtos, total, page, limit);
}
}
57 changes: 57 additions & 0 deletions drips/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './strategies/jwt.strategy';
import { JwtRefreshStrategy } from './strategies/jwt-refresh.strategy';
import { User } from '../users/entities/user.entity';
import { RefreshToken } from './entities/refresh-token.entity';
import { EmailVerificationToken } from './entities/email-verification-token.entity';
import { PasswordResetToken } from './entities/password-reset-token.entity';
import { MockMailer } from '../../../libs/common/src/mailer/mock-mailer';
import { UserService } from './services/user.service';
import { UserRepository } from './repositories/user.repository';

/* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */

@Module({
imports: [
ConfigModule,
TypeOrmModule.forFeature([
RefreshToken,
User,
EmailVerificationToken,
PasswordResetToken,
]),
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({}), // Config is per-token in the service
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET'),
signOptions: {
expiresIn: parseInt(
configService.get<string>('JWT_TTL') || '3600',
10,
),
},
}),
}),
ConfigModule,
],
controllers: [AuthController],
providers: [
AuthService,
JwtStrategy,
JwtRefreshStrategy,
MockMailer,
UserService,
UserRepository,
],
exports: [AuthService, JwtStrategy, PassportModule, UserService],
})
export class AuthModule {}
Loading
Loading