import * as crypto from "node:crypto"; import { Injectable, Logger, OnApplicationBootstrap, UnauthorizedException, } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { UsersService } from "../users/users.service"; import { RbacService } from "./rbac.service"; @Injectable() export class BootstrapService implements OnApplicationBootstrap { private readonly logger = new Logger(BootstrapService.name); private bootstrapToken: string | null = null; constructor( private readonly rbacService: RbacService, private readonly usersService: UsersService, private readonly configService: ConfigService, ) {} async onApplicationBootstrap() { const adminCount = await this.rbacService.countAdmins(); if (adminCount === 0) { this.generateBootstrapToken(); } } private generateBootstrapToken() { this.bootstrapToken = crypto.randomBytes(32).toString("hex"); const domain = this.configService.get("DOMAIN_NAME") || "localhost"; const protocol = domain.includes("localhost") ? "http" : "https"; const url = `${protocol}://${domain}/auth/bootstrap-admin`; this.logger.warn("SECURITY ALERT: No administrator found in database."); this.logger.warn( "To create the first administrator, use the following endpoint:", ); this.logger.warn( `Endpoint: GET ${url}?token=${this.bootstrapToken}&username=votre_nom_utilisateur`, ); this.logger.warn( 'Exemple: curl -X GET "http://localhost/auth/bootstrap-admin?token=...&username=..."', ); this.logger.warn("This token is one-time use only."); } async consumeToken(token: string, username: string) { if (!this.bootstrapToken || token !== this.bootstrapToken) { throw new UnauthorizedException("Invalid or expired bootstrap token"); } const user = await this.usersService.findPublicProfile(username); if (!user) { throw new UnauthorizedException(`User ${username} not found`); } await this.rbacService.assignRoleToUser(user.uuid, "admin"); this.bootstrapToken = null; // One-time use this.logger.log( `User ${username} has been promoted to administrator via bootstrap token.`, ); return { message: `User ${username} is now an administrator` }; } }