- Added `lint:fix` scripts for backend, frontend, and documentation. - Enabled `biome check --write` for unsafe fixes in backend scripts. - Fixed imports, formatting, and logging for improved code clarity. - Adjusted service unit tests for better readability and maintainability.
68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
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` };
|
|
}
|
|
}
|