- C1: Disable Swagger UI in production (env gate) - M1+M2: Add Helmet.js for security headers (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy) and remove X-Powered-By - H2: Add @nestjs/throttler rate limiting (5 req/min on login/register) - M4: Remove orgSchema from JWT payload and client-side storage; tenant middleware now resolves schema from orgId via cached DB lookup - L1: Fix Chatwoot user identification (read from auth store on ready) - Remove schemaName from frontend Organization type and UI displays Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
import {
|
|
Controller,
|
|
Post,
|
|
Patch,
|
|
Body,
|
|
UseGuards,
|
|
Request,
|
|
Get,
|
|
} from '@nestjs/common';
|
|
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
|
import { AuthGuard } from '@nestjs/passport';
|
|
import { Throttle } from '@nestjs/throttler';
|
|
import { AuthService } from './auth.service';
|
|
import { RegisterDto } from './dto/register.dto';
|
|
import { LoginDto } from './dto/login.dto';
|
|
import { SwitchOrgDto } from './dto/switch-org.dto';
|
|
import { JwtAuthGuard } from './guards/jwt-auth.guard';
|
|
import { AllowViewer } from '../../common/decorators/allow-viewer.decorator';
|
|
|
|
@ApiTags('auth')
|
|
@Controller('auth')
|
|
export class AuthController {
|
|
constructor(private authService: AuthService) {}
|
|
|
|
@Post('register')
|
|
@ApiOperation({ summary: 'Register a new user' })
|
|
@Throttle({ default: { limit: 5, ttl: 60000 } })
|
|
async register(@Body() dto: RegisterDto) {
|
|
return this.authService.register(dto);
|
|
}
|
|
|
|
@Post('login')
|
|
@ApiOperation({ summary: 'Login with email and password' })
|
|
@Throttle({ default: { limit: 5, ttl: 60000 } })
|
|
@UseGuards(AuthGuard('local'))
|
|
async login(@Request() req: any, @Body() _dto: LoginDto) {
|
|
const ip = req.headers['x-forwarded-for'] || req.ip;
|
|
const ua = req.headers['user-agent'];
|
|
return this.authService.login(req.user, ip, ua);
|
|
}
|
|
|
|
@Get('profile')
|
|
@ApiOperation({ summary: 'Get current user profile' })
|
|
@ApiBearerAuth()
|
|
@UseGuards(JwtAuthGuard)
|
|
async getProfile(@Request() req: any) {
|
|
return this.authService.getProfile(req.user.sub);
|
|
}
|
|
|
|
@Patch('intro-seen')
|
|
@ApiOperation({ summary: 'Mark the how-to intro as seen for the current user' })
|
|
@ApiBearerAuth()
|
|
@UseGuards(JwtAuthGuard)
|
|
@AllowViewer()
|
|
async markIntroSeen(@Request() req: any) {
|
|
await this.authService.markIntroSeen(req.user.sub);
|
|
return { success: true };
|
|
}
|
|
|
|
@Post('switch-org')
|
|
@ApiOperation({ summary: 'Switch active organization' })
|
|
@ApiBearerAuth()
|
|
@UseGuards(JwtAuthGuard)
|
|
@AllowViewer()
|
|
async switchOrg(@Request() req: any, @Body() dto: SwitchOrgDto) {
|
|
const ip = req.headers['x-forwarded-for'] || req.ip;
|
|
const ua = req.headers['user-agent'];
|
|
return this.authService.switchOrganization(req.user.sub, dto.organizationId, ip, ua);
|
|
}
|
|
}
|