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
4 changes: 3 additions & 1 deletion backend/src/assets/notes-docs.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { Controller, Get, Post, Param, Body, Req } from '@nestjs/common';
import { Controller, Get, Post, Param, Body, Req, UseGuards } from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiBearerAuth,
ApiResponse,
} from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('assets')
@ApiBearerAuth('JWT-auth')
@Controller('assets/:id')
@UseGuards(JwtAuthGuard)
export class NotesDocsController {
private notes = new Map<string, any[]>();
private docs = new Map<string, any[]>();
Expand Down
7 changes: 5 additions & 2 deletions backend/src/audits/audits.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { Body, Controller, Get, Param, Patch, Post, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { AuditsService } from './audits.service';
import { CreateAuditSessionDto } from './dto/create-audit-session.dto';
import { RecordAuditItemDto } from './dto/record-audit-item.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('audits')
@ApiBearerAuth('JWT-auth')
@Controller('audits')
@UseGuards(JwtAuthGuard)
export class AuditsController {
constructor(private readonly auditsService: AuditsService) {}

Expand Down
12 changes: 12 additions & 0 deletions backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ export class AuthController {
return this.authService.forgotPassword(email);
}

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

@Post('refresh')
@HttpCode(HttpStatus.OK)
@ApiOperation({
Expand Down
2 changes: 1 addition & 1 deletion backend/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { AuditLogsModule } from '../audit-logs/audit-logs.module';
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET', 'secretKey'),
secret: configService.get<string>('JWT_SECRET'),
signOptions: {
expiresIn: (configService.get<string>('JWT_EXPIRATION') ??
'7d') as any,
Expand Down
56 changes: 46 additions & 10 deletions backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,15 @@ export class AuthService {
private generateAccessToken(user: AuthUser) {
const payload = { sub: user.id, email: user.email, role: user.role };
return this.jwtService.sign(payload, {
secret: this.configService.get<string>('JWT_SECRET', 'secretKey'),
secret: this.configService.get<string>('JWT_SECRET'),
expiresIn: this.configService.get<string>('JWT_EXPIRATION', '15m') as any,
});
}

private generateRefreshToken(user: AuthUser) {
const payload = { sub: user.id, email: user.email, type: 'refresh' };
return this.jwtService.sign(payload, {
secret: this.configService.get<string>(
'JWT_REFRESH_SECRET',
'refreshSecretKey',
),
secret: this.configService.get<string>('JWT_REFRESH_SECRET'),
expiresIn: '7d' as any,
});
}
Expand Down Expand Up @@ -136,10 +133,7 @@ export class AuthService {
async refreshToken(refreshToken: string) {
try {
const payload = this.jwtService.verify(refreshToken, {
secret: this.configService.get<string>(
'JWT_REFRESH_SECRET',
'refreshSecretKey',
),
secret: this.configService.get<string>('JWT_REFRESH_SECRET'),
});

const user = await this.usersService.findById(payload.sub);
Expand Down Expand Up @@ -178,7 +172,49 @@ export class AuthService {
if (!user) {
throw new NotFoundException('User not found');
}
// In a full implementation this would queue a reset email.

const resetToken = crypto.randomBytes(32).toString('hex');
const hashedToken = this.hashToken(resetToken);
const expires = new Date(Date.now() + 60 * 60 * 1000); // 1 hour

await this.usersService.setPasswordResetToken(
user.id,
hashedToken,
expires,
);

// TODO: send the resetToken via email instead of returning it.
// Never return the raw token in production — anyone who knows a user's
// email could otherwise call this endpoint and take over the account.
if (process.env.NODE_ENV !== 'production') {
return {
message: 'Password reset instructions sent',
resetToken,
};
}

return { message: 'Password reset instructions sent' };
}

async resetPassword(token: string, newPassword: string) {
if (!token || !newPassword) {
throw new BadRequestException('Token and new password are required');
}

const hashedToken = this.hashToken(token);
const user = await this.usersService.findByResetToken(hashedToken);

if (!user) {
throw new BadRequestException('Invalid or expired reset token');
}

if (!user.passwordResetExpires || user.passwordResetExpires < new Date()) {
throw new BadRequestException('Invalid or expired reset token');
}

const passwordHash = await bcrypt.hash(newPassword, 10);
await this.usersService.updatePassword(user.id, passwordHash);

return { message: 'Password reset successfully' };
}
}
2 changes: 1 addition & 1 deletion backend/src/auth/strategies/jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get<string>('JWT_SECRET', 'secretKey'),
secretOrKey: configService.get<string>('JWT_SECRET'),
});
}

Expand Down
13 changes: 12 additions & 1 deletion backend/src/branches/branches.controller.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { Controller, Get, Post, Patch, Delete, Body, Param } from '@nestjs/common';
import {
Controller,
Get,
Post,
Patch,
Delete,
Body,
Param,
UseGuards,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiBearerAuth,
ApiResponse,
} from '@nestjs/swagger';
import { BranchesService } from './branches.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('branches')
@ApiBearerAuth('JWT-auth')
@Controller('branches')
@UseGuards(JwtAuthGuard)
export class BranchesController {
constructor(private readonly branchesService: BranchesService) {}

Expand Down
3 changes: 3 additions & 0 deletions backend/src/categories/categories.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Delete,
Body,
Param,
UseGuards,
} from '@nestjs/common';
import {
ApiTags,
Expand All @@ -14,10 +15,12 @@ import {
ApiResponse,
} from '@nestjs/swagger';
import { CategoriesService } from './categories.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('categories')
@ApiBearerAuth('JWT-auth')
@Controller('categories')
@UseGuards(JwtAuthGuard)
export class CategoriesController {
constructor(private readonly categoriesService: CategoriesService) {}

Expand Down
3 changes: 3 additions & 0 deletions backend/src/departments/departments.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Delete,
Body,
Param,
UseGuards,
} from '@nestjs/common';
import {
ApiTags,
Expand All @@ -14,10 +15,12 @@ import {
ApiResponse,
} from '@nestjs/swagger';
import { DepartmentsService } from './departments.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('departments')
@ApiBearerAuth('JWT-auth')
@Controller('departments')
@UseGuards(JwtAuthGuard)
export class DepartmentsController {
constructor(private readonly deptService: DepartmentsService) {}

Expand Down
4 changes: 3 additions & 1 deletion backend/src/inventory/inventory.controller.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { Controller, Get, Post, Param, Body, Req } from '@nestjs/common';
import { Controller, Get, Post, Param, Body, Req, UseGuards } from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiBearerAuth,
ApiResponse,
} from '@nestjs/swagger';
import { InventoryService } from './inventory.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('inventory')
@ApiBearerAuth('JWT-auth')
@Controller('inventory')
@UseGuards(JwtAuthGuard)
export class InventoryController {
constructor(private readonly inventoryService: InventoryService) {}

Expand Down
3 changes: 3 additions & 0 deletions backend/src/licenses/licenses.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Delete,
Param,
Body,
UseGuards,
} from '@nestjs/common';
import {
ApiTags,
Expand All @@ -17,10 +18,12 @@ import { LicensesService } from './licenses.service';
import { CreateLicenseDto } from './dto/create-license.dto';
import { UpdateLicenseDto } from './dto/update-license.dto';
import { AssignSeatDto } from './dto/assign-seat.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('licenses')
@ApiBearerAuth('JWT-auth')
@Controller('licenses')
@UseGuards(JwtAuthGuard)
export class LicensesController {
constructor(private readonly licensesService: LicensesService) {}

Expand Down
3 changes: 3 additions & 0 deletions backend/src/locations/locations.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Delete,
Body,
Param,
UseGuards,
} from '@nestjs/common';
import {
ApiTags,
Expand All @@ -16,10 +17,12 @@ import {
import { LocationsService } from './locations.service';
import { CreateLocationDto } from './dto/create-location.dto';
import { UpdateLocationDto } from './dto/update-location.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('locations')
@ApiBearerAuth('JWT-auth')
@Controller('locations')
@UseGuards(JwtAuthGuard)
export class LocationsController {
constructor(private readonly locationsService: LocationsService) {}

Expand Down
22 changes: 22 additions & 0 deletions backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,29 @@ import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import helmet from 'helmet';
import { AppModule } from './app.module';

function validateEnv() {
const required = ['JWT_SECRET', 'JWT_REFRESH_SECRET'];
const missing = required.filter((key) => !process.env[key]);
if (missing.length > 0) {
throw new Error(
`Missing required environment variables: ${missing.join(', ')}. ` +
'Set them before starting the server. There are no built-in defaults for security reasons.',
);
}
const minLen = 32;
for (const key of required) {
const val = process.env[key]!;
if (val.length < minLen) {
throw new Error(
`${key} must be at least ${minLen} characters long for security.`,
);
}
}
}

async function bootstrap() {
validateEnv();

const app = await NestFactory.create(AppModule);

// Set secure HTTP response headers (CSP, HSTS, X-Frame-Options, etc.).
Expand Down
7 changes: 5 additions & 2 deletions backend/src/maintenance/asset-maintenance.controller.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { Body, Controller, Get, Param, Patch, Post, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { MaintenanceService } from './maintenance.service';
import { CreateMaintenanceRecordDto } from './dto/create-maintenance-record.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('assets')
@ApiBearerAuth('JWT-auth')
@Controller('assets/:id/maintenance')
@UseGuards(JwtAuthGuard)
export class AssetMaintenanceController {
constructor(private readonly maintenanceService: MaintenanceService) {}

Expand Down
3 changes: 3 additions & 0 deletions backend/src/maintenance/maintenance.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Body,
Param,
Query,
UseGuards,
} from '@nestjs/common';
import {
ApiTags,
Expand All @@ -16,10 +17,12 @@ import {
import { MaintenanceService } from './maintenance.service';
import { CreateMaintenanceRecordDto } from './dto/create-maintenance-record.dto';
import { UpdateMaintenanceRecordDto } from './dto/update-maintenance-record.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('maintenance')
@ApiBearerAuth('JWT-auth')
@Controller('maintenance')
@UseGuards(JwtAuthGuard)
export class MaintenanceController {
constructor(private readonly maintenanceService: MaintenanceService) {}

Expand Down
13 changes: 12 additions & 1 deletion backend/src/purchase-orders/purchase-orders.controller.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { Controller, Get, Post, Patch, Param, Body, Req } from '@nestjs/common';
import {
Controller,
Get,
Post,
Patch,
Param,
Body,
Req,
UseGuards,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiBearerAuth,
ApiResponse,
} from '@nestjs/swagger';
import { PurchaseOrdersService } from './purchase-orders.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('purchase-orders')
@ApiBearerAuth('JWT-auth')
@Controller('purchase-orders')
@UseGuards(JwtAuthGuard)
export class PurchaseOrdersController {
constructor(private readonly poService: PurchaseOrdersService) {}

Expand Down
Loading
Loading