237 lines
6.0 KiB
TypeScript
237 lines
6.0 KiB
TypeScript
// backend/src/modules/auth/auth.controller.ts
|
|
|
|
import {
|
|
Controller,
|
|
Post,
|
|
Body,
|
|
UseGuards,
|
|
Req,
|
|
Res,
|
|
HttpCode,
|
|
HttpStatus,
|
|
Get
|
|
} from '@nestjs/common';
|
|
import type { Response } from 'express';
|
|
import {
|
|
ApiTags,
|
|
ApiOperation,
|
|
ApiResponse,
|
|
ApiBearerAuth,
|
|
ApiBody,
|
|
ApiUnauthorizedResponse,
|
|
ApiBadRequestResponse
|
|
} from '@nestjs/swagger';
|
|
|
|
import { AuthService } from './auth.service';
|
|
import { LoginDto } from './dto/login.dto';
|
|
import { RegisterDto } from './dto/register.dto';
|
|
import { CreateOrganizationDto } from './dto/create-organization.dto';
|
|
import { JwtAuthGuard } from './guards/jwt-auth.guard';
|
|
import { LocalAuthGuard } from './guards/local-auth.guard';
|
|
|
|
@ApiTags('auth')
|
|
@Controller('auth')
|
|
export class AuthController {
|
|
constructor(private readonly authService: AuthService) {}
|
|
|
|
// =========================
|
|
// LOGIN
|
|
// =========================
|
|
@Post('login')
|
|
@HttpCode(HttpStatus.OK)
|
|
@UseGuards(LocalAuthGuard)
|
|
@ApiOperation({ summary: 'Login with email and password' })
|
|
@ApiBody({ type: LoginDto })
|
|
@ApiResponse({ status: 200, description: 'Login successful' })
|
|
@ApiUnauthorizedResponse({ description: 'Invalid credentials' })
|
|
@ApiBadRequestResponse({ description: 'Invalid input data' })
|
|
async login(
|
|
@Body() loginDto: LoginDto,
|
|
@Req() req,
|
|
@Res({ passthrough: true }) res: Response
|
|
) {
|
|
console.log('Login endpoint hit');
|
|
|
|
const result = await this.authService.login(loginDto, req.user);
|
|
|
|
// ✅ SET COOKIES HERE
|
|
this.setAuthCookies(res, result.data.accessToken, result.data.refreshToken);
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
user: result.data.user,
|
|
organizations: result.data.organizations,
|
|
},
|
|
};
|
|
}
|
|
|
|
// =========================
|
|
// REGISTER
|
|
// =========================
|
|
@Post('register')
|
|
@ApiOperation({ summary: 'Register a new user' })
|
|
@ApiBody({ type: RegisterDto })
|
|
@ApiResponse({ status: 201, description: 'User registered successfully' })
|
|
@ApiBadRequestResponse({ description: 'Invalid input data' })
|
|
async register(
|
|
@Body() registerDto: RegisterDto,
|
|
@Res({ passthrough: true }) res: Response
|
|
) {
|
|
console.log('Register endpoint hit');
|
|
|
|
const result = await this.authService.register(registerDto);
|
|
|
|
// ✅ SET COOKIES HERE
|
|
this.setAuthCookies(res, result.data.accessToken, result.data.refreshToken);
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
user: result.data.user,
|
|
organizations: result.data.organizations,
|
|
},
|
|
};
|
|
}
|
|
|
|
// =========================
|
|
// SELECT ORGANIZATION
|
|
// =========================
|
|
@Post('select-organization')
|
|
@UseGuards(JwtAuthGuard)
|
|
@ApiBearerAuth('JWT-auth')
|
|
async selectOrganization(
|
|
@Req() req,
|
|
@Body('organizationId') organizationId: string,
|
|
@Res({ passthrough: true }) res: Response
|
|
) {
|
|
const result = await this.authService.selectOrganization(
|
|
req.user.id,
|
|
organizationId
|
|
);
|
|
|
|
// 🔥 Replace access token with org-scoped token
|
|
this.setAccessToken(res, result.data.accessToken);
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
organization: result.data.organization,
|
|
},
|
|
};
|
|
}
|
|
|
|
@Post('organizations')
|
|
@UseGuards(JwtAuthGuard)
|
|
@ApiBearerAuth('JWT-auth')
|
|
@ApiOperation({ summary: 'Create organization for current user' })
|
|
async createOrganization(@Req() req, @Body() dto: CreateOrganizationDto) {
|
|
return this.authService.createOrganization(req.user.id, dto);
|
|
}
|
|
|
|
// =========================
|
|
// PROFILE
|
|
// =========================
|
|
@Get('profile')
|
|
@UseGuards(JwtAuthGuard)
|
|
@ApiBearerAuth('JWT-auth')
|
|
@ApiOperation({ summary: 'Get user profile' })
|
|
@ApiResponse({ status: 200, description: 'Profile retrieved successfully' })
|
|
@ApiUnauthorizedResponse({ description: 'Invalid or missing JWT token' })
|
|
async getProfile(@Req() req) {
|
|
console.log('Profile endpoint hit');
|
|
console.log('USER FROM JWT:', req.user);
|
|
|
|
return this.authService.getProfile(req.user.id);
|
|
}
|
|
|
|
@Get('subscription-alert')
|
|
@UseGuards(JwtAuthGuard)
|
|
@ApiBearerAuth('JWT-auth')
|
|
@ApiOperation({
|
|
summary:
|
|
'Owner-only: seat / trial status for warning indicator (current org from JWT)',
|
|
})
|
|
async getSubscriptionAlert(@Req() req) {
|
|
return this.authService.getOwnerSubscriptionAlert(
|
|
req.user.id,
|
|
req.user.organizationId,
|
|
);
|
|
}
|
|
|
|
// =========================
|
|
// LOGOUT
|
|
// =========================
|
|
@Post('logout')
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({ summary: 'Logout current user' })
|
|
@ApiResponse({ status: 200, description: 'Logout successful' })
|
|
async logout(@Req() req, @Res({ passthrough: true }) res: Response) {
|
|
const accessToken = req?.cookies?.accessToken;
|
|
|
|
if (accessToken) {
|
|
await this.authService.logout(accessToken);
|
|
}
|
|
|
|
this.clearAuthCookies(res);
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Logged out successfully',
|
|
};
|
|
}
|
|
|
|
// =========================
|
|
// TEST
|
|
// =========================
|
|
@Get('test')
|
|
@ApiOperation({ summary: 'Test endpoint' })
|
|
test() {
|
|
return { message: 'Auth controller is working!' };
|
|
}
|
|
|
|
// =========================
|
|
// 🔥 COOKIE HELPERS
|
|
// =========================
|
|
private setAuthCookies(
|
|
res: Response,
|
|
accessToken: string,
|
|
refreshToken: string
|
|
) {
|
|
this.setAccessToken(res, accessToken);
|
|
this.setRefreshToken(res, refreshToken);
|
|
}
|
|
|
|
private setAccessToken(res: Response, token: string) {
|
|
res.cookie('accessToken', token, {
|
|
httpOnly: true,
|
|
secure: false, // ⚠️ true in production (HTTPS)
|
|
sameSite: 'lax',
|
|
path: '/',
|
|
});
|
|
}
|
|
|
|
private setRefreshToken(res: Response, token: string) {
|
|
res.cookie('refreshToken', token, {
|
|
httpOnly: true,
|
|
secure: false,
|
|
sameSite: 'lax',
|
|
path: '/',
|
|
});
|
|
}
|
|
|
|
private clearAuthCookies(res: Response) {
|
|
res.clearCookie('accessToken', {
|
|
httpOnly: true,
|
|
secure: false,
|
|
sameSite: 'lax',
|
|
path: '/',
|
|
});
|
|
res.clearCookie('refreshToken', {
|
|
httpOnly: true,
|
|
secure: false,
|
|
sameSite: 'lax',
|
|
path: '/',
|
|
});
|
|
}
|
|
} |