Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02796e4e1f
|
||
|
|
951b38db67
|
@@ -24,7 +24,8 @@
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"noUnknownAtRules": "off"
|
||||
"noUnknownAtRules": "off",
|
||||
"noExplicitAny": "off"
|
||||
},
|
||||
"style": {
|
||||
"useImportType": "off"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@memegoat/backend",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
@@ -13,7 +13,7 @@
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"lint": "biome check",
|
||||
"lint:write": "biome check --write",
|
||||
"lint:write": "biome check --write --unsafe",
|
||||
"format": "biome format --write",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
|
||||
@@ -24,6 +24,7 @@ import { ConfigService } from "@nestjs/config";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { AuthController } from "./auth.controller";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { BootstrapService } from "./bootstrap.service";
|
||||
|
||||
jest.mock("iron-session", () => ({
|
||||
getIronSession: jest.fn().mockResolvedValue({
|
||||
@@ -44,6 +45,10 @@ describe("AuthController", () => {
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
|
||||
const mockBootstrapService = {
|
||||
consumeToken: jest.fn(),
|
||||
};
|
||||
|
||||
const mockConfigService = {
|
||||
get: jest
|
||||
.fn()
|
||||
@@ -55,6 +60,7 @@ describe("AuthController", () => {
|
||||
controllers: [AuthController],
|
||||
providers: [
|
||||
{ provide: AuthService, useValue: mockAuthService },
|
||||
{ provide: BootstrapService, useValue: mockBootstrapService },
|
||||
{ provide: ConfigService, useValue: mockConfigService },
|
||||
],
|
||||
}).compile();
|
||||
@@ -75,7 +81,6 @@ describe("AuthController", () => {
|
||||
password: "password",
|
||||
username: "test",
|
||||
};
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Necessary to avoid defining full DTO in test
|
||||
await controller.register(dto as any);
|
||||
expect(authService.register).toHaveBeenCalledWith(dto);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { Body, Controller, Get, Headers, Post, Query, Req, Res } from "@nestjs/common";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Headers,
|
||||
Post,
|
||||
Query,
|
||||
Req,
|
||||
Res,
|
||||
} from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { Throttle } from "@nestjs/throttler";
|
||||
import type { Request, Response } from "express";
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { UnauthorizedException } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { UsersService } from "../users/users.service";
|
||||
import { BootstrapService } from "./bootstrap.service";
|
||||
import { RbacService } from "./rbac.service";
|
||||
import { UsersService } from "../users/users.service";
|
||||
|
||||
describe("BootstrapService", () => {
|
||||
let service: BootstrapService;
|
||||
let rbacService: RbacService;
|
||||
let usersService: UsersService;
|
||||
let _usersService: UsersService;
|
||||
|
||||
const mockRbacService = {
|
||||
countAdmins: jest.fn(),
|
||||
@@ -36,7 +36,7 @@ describe("BootstrapService", () => {
|
||||
|
||||
service = module.get<BootstrapService>(BootstrapService);
|
||||
rbacService = module.get<RbacService>(RbacService);
|
||||
usersService = module.get<UsersService>(UsersService);
|
||||
_usersService = module.get<UsersService>(UsersService);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
@@ -46,7 +46,10 @@ describe("BootstrapService", () => {
|
||||
describe("onApplicationBootstrap", () => {
|
||||
it("should generate a token if no admin exists", async () => {
|
||||
mockRbacService.countAdmins.mockResolvedValue(0);
|
||||
const generateTokenSpy = jest.spyOn(service as any, "generateBootstrapToken");
|
||||
const generateTokenSpy = jest.spyOn(
|
||||
service as any,
|
||||
"generateBootstrapToken",
|
||||
);
|
||||
|
||||
await service.onApplicationBootstrap();
|
||||
|
||||
@@ -56,7 +59,10 @@ describe("BootstrapService", () => {
|
||||
|
||||
it("should not generate a token if admin exists", async () => {
|
||||
mockRbacService.countAdmins.mockResolvedValue(1);
|
||||
const generateTokenSpy = jest.spyOn(service as any, "generateBootstrapToken");
|
||||
const generateTokenSpy = jest.spyOn(
|
||||
service as any,
|
||||
"generateBootstrapToken",
|
||||
);
|
||||
|
||||
await service.onApplicationBootstrap();
|
||||
|
||||
@@ -70,9 +76,9 @@ describe("BootstrapService", () => {
|
||||
mockRbacService.countAdmins.mockResolvedValue(0);
|
||||
await service.onApplicationBootstrap();
|
||||
|
||||
await expect(
|
||||
service.consumeToken("wrong-token", "user1"),
|
||||
).rejects.toThrow(UnauthorizedException);
|
||||
await expect(service.consumeToken("wrong-token", "user1")).rejects.toThrow(
|
||||
UnauthorizedException,
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw UnauthorizedException if user not found", async () => {
|
||||
@@ -97,7 +103,10 @@ describe("BootstrapService", () => {
|
||||
|
||||
const result = await service.consumeToken(token, "user1");
|
||||
|
||||
expect(rbacService.assignRoleToUser).toHaveBeenCalledWith("user-uuid", "admin");
|
||||
expect(rbacService.assignRoleToUser).toHaveBeenCalledWith(
|
||||
"user-uuid",
|
||||
"admin",
|
||||
);
|
||||
expect((service as any).bootstrapToken).toBeNull();
|
||||
expect(result.message).toContain("user1 is now an administrator");
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as crypto from "node:crypto";
|
||||
import {
|
||||
Injectable,
|
||||
Logger,
|
||||
@@ -5,7 +6,6 @@ import {
|
||||
UnauthorizedException,
|
||||
} from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import * as crypto from "node:crypto";
|
||||
import { UsersService } from "../users/users.service";
|
||||
import { RbacService } from "./rbac.service";
|
||||
|
||||
@@ -34,9 +34,15 @@ export class BootstrapService implements OnApplicationBootstrap {
|
||||
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(
|
||||
"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.");
|
||||
}
|
||||
|
||||
@@ -53,7 +59,9 @@ export class BootstrapService implements OnApplicationBootstrap {
|
||||
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.`);
|
||||
this.logger.log(
|
||||
`User ${username} has been promoted to administrator via bootstrap token.`,
|
||||
);
|
||||
return { message: `User ${username} is now an administrator` };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,11 @@ export class RbacService implements OnApplicationBootstrap {
|
||||
if (count === 0) {
|
||||
this.logger.log("No roles found, seeding default roles...");
|
||||
const defaultRoles = [
|
||||
{ name: "Administrator", slug: "admin", description: "Full system access" },
|
||||
{
|
||||
name: "Administrator",
|
||||
slug: "admin",
|
||||
description: "Full system access",
|
||||
},
|
||||
{
|
||||
name: "Moderator",
|
||||
slug: "moderator",
|
||||
|
||||
@@ -8,7 +8,6 @@ jest.mock("minio");
|
||||
describe("S3Service", () => {
|
||||
let service: S3Service;
|
||||
let configService: ConfigService;
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Fine for testing purposes
|
||||
let minioClient: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "@memegoat/frontend",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "biome check",
|
||||
"lint:write": "biome check --write",
|
||||
"format": "biome format --write"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@memegoat/source",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"version:get": "cmake -P version.cmake GET",
|
||||
@@ -13,9 +13,13 @@
|
||||
"build:back": "pnpm run -F @memegoat/backend build",
|
||||
"build:docs": "pnpm run -F @memegoat/documentation build",
|
||||
"lint": "pnpm run lint:back && pnpm run lint:front && pnpm run lint:docs",
|
||||
"lint:fix": "pnpm run lint:back:fix && pnpm run lint:front:fix && pnpm run lint:docs:fix",
|
||||
"lint:back": "pnpm run -F @memegoat/backend lint",
|
||||
"lint:back:fix": "pnpm run -F @memegoat/backend lint:write",
|
||||
"lint:front": "pnpm run -F @memegoat/frontend lint",
|
||||
"lint:front:fix": "pnpm run -F @memegoat/frontend lint:write",
|
||||
"lint:docs": "pnpm run -F @memegoat/documentation lint",
|
||||
"lint:docs:fix": "pnpm run -F @memegoat/documentation lint:write",
|
||||
"test": "pnpm run test:back && pnpm run test:front",
|
||||
"test:back": "pnpm run -F @memegoat/backend test",
|
||||
"test:front": "pnpm run -F @memegoat/frontend test",
|
||||
|
||||
Reference in New Issue
Block a user