diff --git a/api/package.json b/api/package.json index f1aac043..77d96691 100644 --- a/api/package.json +++ b/api/package.json @@ -43,6 +43,7 @@ "reflect-metadata": "catalog:", "rxjs": "^7.8.1", "typeorm": "catalog:", + "uid-safe": "^2.1.5", "xlsx": "^0.18.5", "zod": "catalog:" }, @@ -61,6 +62,7 @@ "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", "@types/supertest": "^6.0.0", + "@types/uid-safe": "^2.1.5", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.42.0", diff --git a/api/src/modules/auth/auth.module.ts b/api/src/modules/auth/auth.module.ts index 7d6b559b..f12331a1 100644 --- a/api/src/modules/auth/auth.module.ts +++ b/api/src/modules/auth/auth.module.ts @@ -6,6 +6,7 @@ import { AuthenticationModule } from '@api/modules/auth/authentication.module'; import { RequestPasswordRecoveryCommandHandler } from '@api/modules/auth/commands/request-password-recovery-command.handler'; import { NewUserEventHandler } from '@api/modules/admin/events/handlers/new-user-event.handler'; import { PasswordRecoveryRequestedEventHandler } from '@api/modules/auth/events/handlers/password-recovery-requested.handler'; +import { BackofficeService } from './backoffice.service'; @Module({ imports: [AuthenticationModule, NotificationsModule], @@ -15,6 +16,7 @@ import { PasswordRecoveryRequestedEventHandler } from '@api/modules/auth/events/ RequestPasswordRecoveryCommandHandler, NewUserEventHandler, PasswordRecoveryRequestedEventHandler, + BackofficeService, ], exports: [AuthenticationModule, AuthMailer], }) diff --git a/api/src/modules/auth/authentication.controller.ts b/api/src/modules/auth/authentication.controller.ts index 195de7c9..16619e0c 100644 --- a/api/src/modules/auth/authentication.controller.ts +++ b/api/src/modules/auth/authentication.controller.ts @@ -5,6 +5,7 @@ import { UseInterceptors, ClassSerializerInterceptor, HttpStatus, + Res, } from '@nestjs/common'; import { User } from '@shared/entities/users/user.entity'; import { LocalAuthGuard } from '@api/modules/auth/guards/local-auth.guard'; @@ -21,13 +22,18 @@ import { CommandBus } from '@nestjs/cqrs'; import { RequestPasswordRecoveryCommand } from '@api/modules/auth/commands/request-password-recovery.command'; import { EmailConfirmation } from '@api/modules/auth/strategies/email-update.strategy'; import { ROLES } from '@shared/entities/users/roles.enum'; +import { Response } from 'express'; +import { ApiConfigService } from '../config/app-config.service'; +import { BackofficeService } from './backoffice.service'; @Controller() @UseInterceptors(ClassSerializerInterceptor) export class AuthenticationController { constructor( private authService: AuthenticationService, + private readonly backofficeService: BackofficeService, private readonly commandBus: CommandBus, + private readonly configService: ApiConfigService, ) {} @Public() @@ -48,9 +54,27 @@ export class AuthenticationController { @Public() @UseGuards(LocalAuthGuard) @TsRestHandler(authContract.login) - async login(@GetUser() user: User): Promise { + async login( + @GetUser() user: User, + @Res({ passthrough: true }) res: Response, + ): Promise { return tsRestHandler(authContract.login, async () => { - const userWithAccessToken = await this.authService.logIn(user); + const [userWithAccessToken, backofficeSession] = + await this.authService.logIn(user); + if (backofficeSession !== undefined) { + const cookieName = this.configService.get( + 'BACKOFFICE_SESSION_COOKIE_NAME', + ); + const cookieValue = + this.backofficeService.generateCookieFromBackofficeSession( + backofficeSession, + ); + res.cookie(cookieName, cookieValue, { + ...backofficeSession.sess.cookie, + sameSite: 'lax', + }); + } + return { body: userWithAccessToken, status: 201, diff --git a/api/src/modules/auth/authentication.module.ts b/api/src/modules/auth/authentication.module.ts index aa00ecb6..5cfda3ca 100644 --- a/api/src/modules/auth/authentication.module.ts +++ b/api/src/modules/auth/authentication.module.ts @@ -14,9 +14,12 @@ import { JwtManager } from '@api/modules/auth/services/jwt.manager'; import { ConfirmAccountStrategy } from '@api/modules/auth/strategies/confirm-account.strategy'; import { PasswordManager } from '@api/modules/auth/services/password.manager'; import { EmailConfirmationJwtStrategy } from '@api/modules/auth/strategies/email-update.strategy'; +import { BackOfficeSession } from '@shared/entities/users/backoffice-session'; +import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ + TypeOrmModule.forFeature([BackOfficeSession]), PassportModule.register({ defaultStrategy: 'jwt' }), JwtModule.registerAsync({ imports: [ApiConfigModule], diff --git a/api/src/modules/auth/authentication.service.ts b/api/src/modules/auth/authentication.service.ts index a2050c41..cc565951 100644 --- a/api/src/modules/auth/authentication.service.ts +++ b/api/src/modules/auth/authentication.service.ts @@ -1,3 +1,5 @@ +// Does not work without * as uid +import * as uid from 'uid-safe'; import { ConflictException, Injectable, @@ -21,6 +23,13 @@ import { RequestEmailUpdateDto } from '@shared/dtos/users/request-email-update.d import { SendEmailConfirmationEmailCommand } from '@api/modules/notifications/email/commands/send-email-confirmation-email.command'; import { PasswordManager } from '@api/modules/auth/services/password.manager'; import { API_EVENT_TYPES } from '@api/modules/api-events/events.enum'; +import { Repository } from 'typeorm'; +import { + BACKOFFICE_SESSIONS_TABLE, + BackOfficeSession, +} from '@shared/entities/users/backoffice-session'; +import { ROLES } from '@shared/entities/users/roles.enum'; +import { InjectRepository } from '@nestjs/typeorm'; @Injectable() export class AuthenticationService { @@ -31,6 +40,8 @@ export class AuthenticationService { private readonly commandBus: CommandBus, private readonly eventBus: EventBus, private readonly passwordManager: PasswordManager, + @InjectRepository(BackOfficeSession) + private readonly backOfficeSessionRepository: Repository, ) {} async validateUser(email: string, password: string): Promise { const user = await this.usersService.findByEmail(email); @@ -81,9 +92,67 @@ export class AuthenticationService { }; } - async logIn(user: User): Promise { + private async createBackOfficeSession( + user: User, + accessToken: string, + ): Promise { + // We replicate what adminjs does by default using postgres as session storage (the default in memory session storage is not production ready) + // This implementation is not compatible with many devices per user + await this.backOfficeSessionRepository + .createQueryBuilder() + .delete() + .from(BACKOFFICE_SESSIONS_TABLE) + .where(`sess -> 'adminUser' ->> 'id' = :id`, { id: user.id }) + .execute(); + + const currentDate = new Date(); + const sessionExpirationDate = new Date( + Date.UTC( + currentDate.getUTCFullYear() + 1, + currentDate.getUTCMonth(), + currentDate.getUTCDate(), + currentDate.getUTCHours(), + currentDate.getUTCMinutes(), + currentDate.getUTCSeconds(), + ), + ); + const backofficeSession: BackOfficeSession = { + sid: uid.sync(24), + sess: { + cookie: { + secure: false, + httpOnly: true, + path: '/', + }, + adminUser: { + id: user.id, + email: user.email, + name: user.name, + partnerName: user.partnerName, + isActive: true, + role: user.role, + createdAt: user.createdAt, + accessToken, + }, + }, + expire: sessionExpirationDate, + }; + await this.backOfficeSessionRepository.insert(backofficeSession); + return backofficeSession; + } + + async logIn(user: User): Promise<[UserWithAccessToken, BackOfficeSession?]> { const { accessToken } = await this.jwtManager.signAccessToken(user.id); - return { user, accessToken }; + if (user.role !== ROLES.ADMIN) { + return [{ user, accessToken }]; + } + + // An adminjs session needs to be created for the admin user + const backofficeSession = await this.createBackOfficeSession( + user, + accessToken, + ); + return [{ user, accessToken }, backofficeSession]; } async signUp(user: User, signUpDto: SignUpDto): Promise { diff --git a/api/src/modules/auth/backoffice.service.ts b/api/src/modules/auth/backoffice.service.ts new file mode 100644 index 00000000..df1c2907 --- /dev/null +++ b/api/src/modules/auth/backoffice.service.ts @@ -0,0 +1,25 @@ +import { BackOfficeSession } from '@shared/entities/users/backoffice-session'; +import * as crypto from 'crypto'; +import { ApiConfigService } from '../config/app-config.service'; +import { Inject } from '@nestjs/common'; + +export class BackofficeService { + constructor( + @Inject(ApiConfigService) + private readonly configService: ApiConfigService, + ) {} + + public generateCookieFromBackofficeSession( + backofficeSession: BackOfficeSession, + ): string { + const cookieSecret = this.configService.get( + 'BACKOFFICE_SESSION_COOKIE_SECRET', + ); + const hmac = crypto + .createHmac('sha256', cookieSecret) + .update(backofficeSession.sid) + .digest('base64') + .replace(/=+$/, ''); + return `s:${backofficeSession.sid}.${hmac}`; + } +} diff --git a/api/src/modules/config/app-config.module.ts b/api/src/modules/config/app-config.module.ts index de6c3806..41cf4166 100644 --- a/api/src/modules/config/app-config.module.ts +++ b/api/src/modules/config/app-config.module.ts @@ -19,6 +19,26 @@ import { JwtConfigHandler } from '@api/modules/config/auth-config.handler'; resolveConfigPath(`shared/config/.env.${process.env.NODE_ENV}`), resolveConfigPath(`shared/config/.env`), ], + validate(config) { + const expectedVariables = [ + 'BACKOFFICE_SESSION_COOKIE_NAME', + 'BACKOFFICE_SESSION_COOKIE_SECRET', + ]; + + const missingVariables = []; + for (const expectedVariable of expectedVariables) { + if (config[expectedVariable] === undefined) { + missingVariables.push(expectedVariable); + } + } + + if (missingVariables.length > 0) { + throw new Error( + `Missing required environment variables: ${missingVariables.join(', ')}`, + ); + } + return config; + }, }), DatabaseModule, ], diff --git a/api/test/integration/auth/sign-in.spec.ts b/api/test/integration/auth/sign-in.spec.ts index c94cf510..c98b83ee 100644 --- a/api/test/integration/auth/sign-in.spec.ts +++ b/api/test/integration/auth/sign-in.spec.ts @@ -77,6 +77,34 @@ describe('Sign In', () => { expect(response.body.accessToken).toBeDefined(); }); + test('Should return 201 an access token and set a backoffice cookie when an admin user successfully signs in', async () => { + // Given a user exists with valid credentials + const user = await testManager.mocks().createUser({ + role: ROLES.ADMIN, + email: 'test@test.com', + isActive: true, + password: '12345678', + }); + + // And the user tries to sign in with valid credentials + const response = await testManager + .request() + .post(authContract.login.path) + .send({ + email: 'test@test.com', + password: '12345678', + }); + + // We should get back OK response and an access token + expect(response.status).toBe(HttpStatus.CREATED); + expect(response.body.accessToken).toBeDefined(); + const setCookieHeader = response.headers['set-cookie']; + expect(setCookieHeader).toHaveLength(1); + expect(decodeURIComponent(setCookieHeader[0])).toMatch( + /^backoffice=s:[^\s]+\.[^\s]+;/, + ); + }); + test('Should return UNAUTHORIZED when trying to sign in with an inactive account', async () => { // Given a user exists with valid credentials const user = await testManager.mocks().createUser({ diff --git a/admin/Dockerfile b/backoffice/Dockerfile similarity index 100% rename from admin/Dockerfile rename to backoffice/Dockerfile diff --git a/admin/components/dashboard.tsx b/backoffice/components/dashboard.tsx similarity index 100% rename from admin/components/dashboard.tsx rename to backoffice/components/dashboard.tsx diff --git a/admin/datasource.ts b/backoffice/datasource.ts similarity index 95% rename from admin/datasource.ts rename to backoffice/datasource.ts index 4aaa52f9..05d51f68 100644 --- a/admin/datasource.ts +++ b/backoffice/datasource.ts @@ -1,3 +1,6 @@ +import "reflect-metadata"; +import dotenv from "dotenv"; +dotenv.config({ path: `../shared/config/.env` }); import { DataSource } from "typeorm"; import { User } from "@shared/entities/users/user.entity.js"; import { ApiEventsEntity } from "@api/modules/api-events/api-events.entity.js"; @@ -32,6 +35,7 @@ import { ModelAssumptions } from "@shared/entities/model-assumptions.entity.js"; import { UserUploadCostInputs } from "@shared/entities/users/user-upload-cost-inputs.entity.js"; import { UserUploadRestorationInputs } from "@shared/entities/users/user-upload-restoration-inputs.entity.js"; import { UserUploadConservationInputs } from "@shared/entities/users/user-upload-conservation-inputs.entity.js"; +import { BackOfficeSession } from "@shared/entities/users/backoffice-session.js"; import { CustomProject } from "@shared/entities/custom-project.entity.js"; // TODO: If we import the COMMON_DATABASE_ENTITIES from shared, we get an error where DataSouce is not set for a given entity @@ -70,6 +74,8 @@ export const ADMINJS_ENTITIES = [ BaseSize, BaseIncrease, ModelAssumptions, + CustomProject, + BackOfficeSession ]; export const dataSource = new DataSource({ @@ -86,4 +92,4 @@ export const dataSource = new DataSource({ ? { rejectUnauthorized: false } : false, logging: false, -}); +}); \ No newline at end of file diff --git a/admin/index.ts b/backoffice/index.ts similarity index 81% rename from admin/index.ts rename to backoffice/index.ts index a6bdaa1b..68f2a73c 100644 --- a/admin/index.ts +++ b/backoffice/index.ts @@ -1,9 +1,12 @@ import "reflect-metadata"; -import AdminJS, { ComponentLoader } from "adminjs"; +import AdminJS, { BaseAuthProvider, ComponentLoader } from "adminjs"; import AdminJSExpress from "@adminjs/express"; -import express from "express"; +import express, { Request, Response } from "express"; import * as AdminJSTypeorm from "@adminjs/typeorm"; import { dataSource } from "./datasource.js"; +import pg from "pg"; +import connectPgSimple from "connect-pg-simple"; +import session from "express-session"; import { AuthProvider } from "./providers/auth.provider.js"; import { UserResource } from "./resources/users/user.resource.js"; import { FeasibilityAnalysisResource } from "./resources/feasability-analysis/feasability-analysis.resource.js"; @@ -36,6 +39,7 @@ import { UserUploadCostInputs } from "@shared/entities/users/user-upload-cost-in import { UserUploadConservationInputs } from "@shared/entities/users/user-upload-conservation-inputs.entity.js"; import { UserUploadRestorationInputs } from "@shared/entities/users/user-upload-restoration-inputs.entity.js"; import { GLOBAL_COMMON_PROPERTIES } from "./resources/common/common.resources.js"; +import { BACKOFFICE_SESSIONS_TABLE } from "@shared/entities/users/backoffice-session.js"; import { CountryResource } from "./resources/countries/country.resource.js"; AdminJS.registerAdapter({ @@ -164,12 +168,45 @@ const start = async () => { }, }); - const adminRouter = AdminJSExpress.buildAuthenticatedRouter(admin, { - provider: authProvider, - cookiePassword: "some-secret", + const PgStore = connectPgSimple(session); + const sessionStore = new PgStore({ + pool: new pg.Pool({ + host: process.env.DB_HOST || "localhost", + user: process.env.DB_USERNAME || "blue-carbon-cost", + password: process.env.DB_PASSWORD || "blue-carbon-cost", + database: process.env.DB_NAME || "blc-dev", + port: 5432 + }), + tableName: BACKOFFICE_SESSIONS_TABLE, }); - const router = AdminJSExpress.buildRouter(admin); + const customRouter = express.Router(); + // Redirect to the app's login page + customRouter.get('/login', (req, res) => { + res.redirect('/auth/signin'); + }); + + const sessionCookieName = process.env.BACKOFFICE_SESSION_COOKIE_NAME as string; + const sessionCookieSecret = process.env.BACKOFFICE_SESSION_COOKIE_SECRET as string; + const adminRouter = AdminJSExpress.buildAuthenticatedRouter( + admin, + { + provider: authProvider as BaseAuthProvider, + cookieName: sessionCookieName, + cookiePassword: sessionCookieSecret, + }, + customRouter, + { + store: sessionStore, + secret: sessionCookieSecret, + saveUninitialized: false, + resave: false, + cookie: { + secure: false, + maxAge: undefined, + } + } + ); app.use(admin.options.rootPath, adminRouter); diff --git a/admin/package.json b/backoffice/package.json similarity index 72% rename from admin/package.json rename to backoffice/package.json index 0ef4cd33..59076955 100644 --- a/admin/package.json +++ b/backoffice/package.json @@ -1,5 +1,5 @@ { - "name": "admin", + "name": "backoffice", "version": "1.0.0", "description": "", "type": "module", @@ -15,6 +15,9 @@ "@adminjs/express": "^6.1.0", "@adminjs/typeorm": "^5.0.1", "adminjs": "^7.8.13", + "connect-pg-simple": "^10.0.0", + "cookie": "^1.0.2", + "cookie-parser": "^1.4.7", "express": "^4.21.0", "express-formidable": "^1.2.0", "express-session": "^1.18.0", @@ -26,8 +29,13 @@ "typescript": "catalog:" }, "devDependencies": { + "@types/connect-pg-simple": "^7.0.3", + "@types/cookie-parser": "^1.4.8", "@types/express": "^4.17.17", + "@types/express-session": "^1.18.1", "@types/node": "^22.7.4", + "@types/pg": "^8.11.10", + "dotenv": "16.4.5", "ts-node": "^10.9.1", "tsx": "^4.19.1" } diff --git a/admin/providers/auth.provider.ts b/backoffice/providers/auth.provider.ts similarity index 75% rename from admin/providers/auth.provider.ts rename to backoffice/providers/auth.provider.ts index e8850787..9432c7ec 100644 --- a/admin/providers/auth.provider.ts +++ b/backoffice/providers/auth.provider.ts @@ -1,5 +1,5 @@ import { BaseAuthProvider, LoginHandlerOptions } from "adminjs"; - +import type { Response } from "express"; import { ROLES } from "@shared/entities/users/roles.enum.js"; import { UserDto, UserWithAccessToken } from "@shared/dtos/users/user.dto.js"; import { API_URL } from "../index.js"; @@ -32,4 +32,12 @@ export class AuthProvider extends BaseAuthProvider { private isAdmin(user: UserDto) { return user.role === ROLES.ADMIN; } + + override async handleLogout({res}: {res: Response}) { + // Remove auth cookies + res.setHeader('Set-Cookie', [ + `backoffice=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly`, + `next-auth.session-token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly`, + ]); + } } diff --git a/admin/resources/base-increase/base-increase.resource.ts b/backoffice/resources/base-increase/base-increase.resource.ts similarity index 100% rename from admin/resources/base-increase/base-increase.resource.ts rename to backoffice/resources/base-increase/base-increase.resource.ts diff --git a/admin/resources/base-size/base-size.resource.ts b/backoffice/resources/base-size/base-size.resource.ts similarity index 100% rename from admin/resources/base-size/base-size.resource.ts rename to backoffice/resources/base-size/base-size.resource.ts diff --git a/admin/resources/baseline-reassesment/baseline-reassesment.resource.ts b/backoffice/resources/baseline-reassesment/baseline-reassesment.resource.ts similarity index 100% rename from admin/resources/baseline-reassesment/baseline-reassesment.resource.ts rename to backoffice/resources/baseline-reassesment/baseline-reassesment.resource.ts diff --git a/admin/resources/blue-carbon-project-planning/blue-carbon-project-planning.resource.ts b/backoffice/resources/blue-carbon-project-planning/blue-carbon-project-planning.resource.ts similarity index 100% rename from admin/resources/blue-carbon-project-planning/blue-carbon-project-planning.resource.ts rename to backoffice/resources/blue-carbon-project-planning/blue-carbon-project-planning.resource.ts diff --git a/admin/resources/carbon-estandard-fees/carbon-estandard-fees.resource.ts b/backoffice/resources/carbon-estandard-fees/carbon-estandard-fees.resource.ts similarity index 100% rename from admin/resources/carbon-estandard-fees/carbon-estandard-fees.resource.ts rename to backoffice/resources/carbon-estandard-fees/carbon-estandard-fees.resource.ts diff --git a/admin/resources/carbon-righs/carbon-rights.resource.ts b/backoffice/resources/carbon-righs/carbon-rights.resource.ts similarity index 100% rename from admin/resources/carbon-righs/carbon-rights.resource.ts rename to backoffice/resources/carbon-righs/carbon-rights.resource.ts diff --git a/admin/resources/common/common.resources.ts b/backoffice/resources/common/common.resources.ts similarity index 100% rename from admin/resources/common/common.resources.ts rename to backoffice/resources/common/common.resources.ts diff --git a/admin/resources/community-benefit/community-benefit.resource.ts b/backoffice/resources/community-benefit/community-benefit.resource.ts similarity index 100% rename from admin/resources/community-benefit/community-benefit.resource.ts rename to backoffice/resources/community-benefit/community-benefit.resource.ts diff --git a/admin/resources/community-cash-flow/community-cash-flow.resource.ts b/backoffice/resources/community-cash-flow/community-cash-flow.resource.ts similarity index 100% rename from admin/resources/community-cash-flow/community-cash-flow.resource.ts rename to backoffice/resources/community-cash-flow/community-cash-flow.resource.ts diff --git a/admin/resources/community-representation/community-representation.resource.ts b/backoffice/resources/community-representation/community-representation.resource.ts similarity index 100% rename from admin/resources/community-representation/community-representation.resource.ts rename to backoffice/resources/community-representation/community-representation.resource.ts diff --git a/admin/resources/conservation-and-planning-admin/conservation-and-planning-admin.resource.ts b/backoffice/resources/conservation-and-planning-admin/conservation-and-planning-admin.resource.ts similarity index 100% rename from admin/resources/conservation-and-planning-admin/conservation-and-planning-admin.resource.ts rename to backoffice/resources/conservation-and-planning-admin/conservation-and-planning-admin.resource.ts diff --git a/admin/resources/countries/country.resource.ts b/backoffice/resources/countries/country.resource.ts similarity index 100% rename from admin/resources/countries/country.resource.ts rename to backoffice/resources/countries/country.resource.ts diff --git a/admin/resources/data-collection-and-field-cost/data-collection-and-field-cost.resource.ts b/backoffice/resources/data-collection-and-field-cost/data-collection-and-field-cost.resource.ts similarity index 100% rename from admin/resources/data-collection-and-field-cost/data-collection-and-field-cost.resource.ts rename to backoffice/resources/data-collection-and-field-cost/data-collection-and-field-cost.resource.ts diff --git a/admin/resources/ecosystem-loss/ecosystem-loss.resource.ts b/backoffice/resources/ecosystem-loss/ecosystem-loss.resource.ts similarity index 100% rename from admin/resources/ecosystem-loss/ecosystem-loss.resource.ts rename to backoffice/resources/ecosystem-loss/ecosystem-loss.resource.ts diff --git a/admin/resources/emission-factors/emission-factors.resource.ts b/backoffice/resources/emission-factors/emission-factors.resource.ts similarity index 100% rename from admin/resources/emission-factors/emission-factors.resource.ts rename to backoffice/resources/emission-factors/emission-factors.resource.ts diff --git a/admin/resources/feasability-analysis/feasability-analysis.resource.ts b/backoffice/resources/feasability-analysis/feasability-analysis.resource.ts similarity index 100% rename from admin/resources/feasability-analysis/feasability-analysis.resource.ts rename to backoffice/resources/feasability-analysis/feasability-analysis.resource.ts diff --git a/admin/resources/financing-cost/financing-cost.resource.ts b/backoffice/resources/financing-cost/financing-cost.resource.ts similarity index 100% rename from admin/resources/financing-cost/financing-cost.resource.ts rename to backoffice/resources/financing-cost/financing-cost.resource.ts diff --git a/admin/resources/implementation-labor-cost/implementation-labor-cost.resource.ts b/backoffice/resources/implementation-labor-cost/implementation-labor-cost.resource.ts similarity index 100% rename from admin/resources/implementation-labor-cost/implementation-labor-cost.resource.ts rename to backoffice/resources/implementation-labor-cost/implementation-labor-cost.resource.ts diff --git a/admin/resources/long-term-project-operating/long-term-project-operating.resource.ts b/backoffice/resources/long-term-project-operating/long-term-project-operating.resource.ts similarity index 100% rename from admin/resources/long-term-project-operating/long-term-project-operating.resource.ts rename to backoffice/resources/long-term-project-operating/long-term-project-operating.resource.ts diff --git a/admin/resources/maintenance/maintenance.resource.ts b/backoffice/resources/maintenance/maintenance.resource.ts similarity index 100% rename from admin/resources/maintenance/maintenance.resource.ts rename to backoffice/resources/maintenance/maintenance.resource.ts diff --git a/admin/resources/model-assumptions/model-assumptions.resource.ts b/backoffice/resources/model-assumptions/model-assumptions.resource.ts similarity index 100% rename from admin/resources/model-assumptions/model-assumptions.resource.ts rename to backoffice/resources/model-assumptions/model-assumptions.resource.ts diff --git a/admin/resources/monitoring-cost/monitoring-cost.resource.ts b/backoffice/resources/monitoring-cost/monitoring-cost.resource.ts similarity index 100% rename from admin/resources/monitoring-cost/monitoring-cost.resource.ts rename to backoffice/resources/monitoring-cost/monitoring-cost.resource.ts diff --git a/admin/resources/mrv/mrv.resource.ts b/backoffice/resources/mrv/mrv.resource.ts similarity index 100% rename from admin/resources/mrv/mrv.resource.ts rename to backoffice/resources/mrv/mrv.resource.ts diff --git a/admin/resources/project-size/project-size.resource.ts b/backoffice/resources/project-size/project-size.resource.ts similarity index 100% rename from admin/resources/project-size/project-size.resource.ts rename to backoffice/resources/project-size/project-size.resource.ts diff --git a/admin/resources/projects/projects.resource.ts b/backoffice/resources/projects/projects.resource.ts similarity index 100% rename from admin/resources/projects/projects.resource.ts rename to backoffice/resources/projects/projects.resource.ts diff --git a/admin/resources/restorable-land/restorable-land.resource.ts b/backoffice/resources/restorable-land/restorable-land.resource.ts similarity index 100% rename from admin/resources/restorable-land/restorable-land.resource.ts rename to backoffice/resources/restorable-land/restorable-land.resource.ts diff --git a/admin/resources/sequestration-rate/sequestration-rate.resource.ts b/backoffice/resources/sequestration-rate/sequestration-rate.resource.ts similarity index 100% rename from admin/resources/sequestration-rate/sequestration-rate.resource.ts rename to backoffice/resources/sequestration-rate/sequestration-rate.resource.ts diff --git a/admin/resources/users/user.actions.ts b/backoffice/resources/users/user.actions.ts similarity index 100% rename from admin/resources/users/user.actions.ts rename to backoffice/resources/users/user.actions.ts diff --git a/admin/resources/users/user.resource.ts b/backoffice/resources/users/user.resource.ts similarity index 100% rename from admin/resources/users/user.resource.ts rename to backoffice/resources/users/user.resource.ts diff --git a/admin/resources/validation-cost/validation-cost.resource.ts b/backoffice/resources/validation-cost/validation-cost.resource.ts similarity index 100% rename from admin/resources/validation-cost/validation-cost.resource.ts rename to backoffice/resources/validation-cost/validation-cost.resource.ts diff --git a/admin/tsconfig.json b/backoffice/tsconfig.json similarity index 100% rename from admin/tsconfig.json rename to backoffice/tsconfig.json diff --git a/client/src/app/auth/api/[...nextauth]/config.ts b/client/src/app/auth/api/[...nextauth]/config.ts index a7672551..f68bbb0e 100644 --- a/client/src/app/auth/api/[...nextauth]/config.ts +++ b/client/src/app/auth/api/[...nextauth]/config.ts @@ -1,3 +1,4 @@ +import { cookies } from "next/headers"; import { UserWithAccessToken } from "@shared/dtos/users/user.dto"; import { LogInSchema } from "@shared/schemas/auth/login.schema"; import type { @@ -36,7 +37,6 @@ export const config = { let access: UserWithAccessToken | null = null; const { email, password } = await LogInSchema.parseAsync(credentials); - const response = await client.auth.login.mutation({ body: { email, @@ -44,6 +44,21 @@ export const config = { }, }); + // Check if adminjs was set in the response + const setCookieHeaders = response.headers.get("set-cookie"); + if (setCookieHeaders !== null) { + const [cookieName, cookieValue] = decodeURIComponent(setCookieHeaders) + .split(";")[0] + .split("="); + + const cookieStore = cookies(); + cookieStore.set(cookieName, cookieValue, { + path: "/", + sameSite: "lax", + httpOnly: true, + }); + } + if (response.status === 201) { access = response.body; } diff --git a/client/src/lib/query-client.ts b/client/src/lib/query-client.ts index e5a54945..239c362a 100644 --- a/client/src/lib/query-client.ts +++ b/client/src/lib/query-client.ts @@ -19,6 +19,7 @@ function makeQueryClient() { const client = initQueryClient(router, { validateResponse: true, baseUrl: process.env.NEXT_PUBLIC_API_URL as string, + credentials: "include", }); export { client, makeQueryClient }; diff --git a/docker-compose.yml b/docker-compose.yml index d9983616..b528621e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,7 +27,8 @@ services: - "1000:1000" networks: - 4-growth-docker-network - + depends_on: + - database client: build: @@ -42,6 +43,22 @@ services: networks: - 4-growth-docker-network + # Used to test the integration between the client, api and backoffice. + # Some dependencies are disabled as we typically use nginx and database containers only. + nginx: + image: nginx + volumes: + - ./nginx/conf.d:/etc/nginx/conf.d + ports: + - 80:80 + extra_hosts: + - "host.docker.internal:host-gateway" + depends_on: + - database + # - client + # - api + # - admin + database: image: postgis/postgis:16-3.4 container_name: blue-carbon-cost-db diff --git a/nginx/conf.d/application.conf b/nginx/conf.d/application.conf new file mode 100644 index 00000000..46803bef --- /dev/null +++ b/nginx/conf.d/application.conf @@ -0,0 +1,57 @@ +upstream api { + server host.docker.internal:4000; +} + +upstream client { + server host.docker.internal:3000; +} + +upstream admin { + server host.docker.internal:1000; +} + +server { + listen 80; + + location / { + proxy_pass http://client; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /api/ { + rewrite ^/api/?(.*)$ /$1 break; + proxy_pass http://api; + proxy_http_version 1.1; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_pass_request_headers on; + client_max_body_size 200m; + } + + location /admin/ { + proxy_pass http://admin; + proxy_http_version 1.1; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_pass_request_headers on; + client_max_body_size 200m; + } +} + + + diff --git a/package.json b/package.json index e3fb6bb7..9406605c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "api:dev": "pnpm --filter api run start:dev", "api:build": "pnpm --filter api run build", "api:prod": "NODE_ENV=production pnpm --filter api run start:prod", - "admin:prod": "NODE_ENV=production pnpm --filter admin run start:prod", + "backoffice:prod": "NODE_ENV=production pnpm --filter backoffice run start:prod", "client:deps": "pnpm --filter client install", "client:dev": "pnpm --filter client run dev", "client:build": "pnpm --filter client run build", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94d18513..7d13eac3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,61 +44,6 @@ importers: .: {} - admin: - dependencies: - '@adminjs/design-system': - specifier: ^4.1.1 - version: 4.1.1(@babel/core@7.25.2)(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@types/react@18.3.5)(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1) - '@adminjs/express': - specifier: ^6.1.0 - version: 6.1.0(adminjs@7.8.13(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@types/babel__core@7.20.5)(@types/react-dom@18.3.0)(@types/react@18.3.5))(express-formidable@1.2.0)(express-session@1.18.0)(express@4.21.0)(tslib@2.7.0) - '@adminjs/typeorm': - specifier: ^5.0.1 - version: 5.0.1(adminjs@7.8.13(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@types/babel__core@7.20.5)(@types/react-dom@18.3.0)(@types/react@18.3.5))(typeorm@0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))) - adminjs: - specifier: ^7.8.13 - version: 7.8.13(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@types/babel__core@7.20.5)(@types/react-dom@18.3.0)(@types/react@18.3.5) - express: - specifier: ^4.21.0 - version: 4.21.0 - express-formidable: - specifier: ^1.2.0 - version: 1.2.0 - express-session: - specifier: ^1.18.0 - version: 1.18.0 - nodemon: - specifier: ^3.1.7 - version: 3.1.7 - pg: - specifier: 'catalog:' - version: 8.12.0 - reflect-metadata: - specifier: 'catalog:' - version: 0.2.2 - tslib: - specifier: ^2.7.0 - version: 2.7.0 - typeorm: - specifier: 'catalog:' - version: 0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5)) - typescript: - specifier: 'catalog:' - version: 5.4.5 - devDependencies: - '@types/express': - specifier: ^4.17.17 - version: 4.17.21 - '@types/node': - specifier: ^22.7.4 - version: 22.7.5 - ts-node: - specifier: ^10.9.1 - version: 10.9.2(@types/node@22.7.5)(typescript@5.4.5) - tsx: - specifier: ^4.19.1 - version: 4.19.1 - api: dependencies: '@aws-sdk/client-ses': @@ -182,6 +127,9 @@ importers: typeorm: specifier: 'catalog:' version: 0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5)) + uid-safe: + specifier: ^2.1.5 + version: 2.1.5 xlsx: specifier: ^0.18.5 version: 0.18.5 @@ -231,6 +179,9 @@ importers: '@types/supertest': specifier: ^6.0.0 version: 6.0.2 + '@types/uid-safe': + specifier: ^2.1.5 + version: 2.1.5 '@typescript-eslint/eslint-plugin': specifier: ^7.0.0 version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) @@ -277,6 +228,85 @@ importers: specifier: 'catalog:' version: 5.4.5 + backoffice: + dependencies: + '@adminjs/design-system': + specifier: ^4.1.1 + version: 4.1.1(@babel/core@7.25.2)(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@types/react@18.3.5)(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1) + '@adminjs/express': + specifier: ^6.1.0 + version: 6.1.0(adminjs@7.8.13(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@types/babel__core@7.20.5)(@types/react-dom@18.3.0)(@types/react@18.3.5))(express-formidable@1.2.0)(express-session@1.18.0)(express@4.21.0)(tslib@2.7.0) + '@adminjs/typeorm': + specifier: ^5.0.1 + version: 5.0.1(adminjs@7.8.13(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@types/babel__core@7.20.5)(@types/react-dom@18.3.0)(@types/react@18.3.5))(typeorm@0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5))) + adminjs: + specifier: ^7.8.13 + version: 7.8.13(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@types/babel__core@7.20.5)(@types/react-dom@18.3.0)(@types/react@18.3.5) + connect-pg-simple: + specifier: ^10.0.0 + version: 10.0.0 + cookie: + specifier: ^1.0.2 + version: 1.0.2 + cookie-parser: + specifier: ^1.4.7 + version: 1.4.7 + express: + specifier: ^4.21.0 + version: 4.21.0 + express-formidable: + specifier: ^1.2.0 + version: 1.2.0 + express-session: + specifier: ^1.18.0 + version: 1.18.0 + nodemon: + specifier: ^3.1.7 + version: 3.1.7 + pg: + specifier: 'catalog:' + version: 8.12.0 + reflect-metadata: + specifier: 'catalog:' + version: 0.2.2 + tslib: + specifier: ^2.7.0 + version: 2.7.0 + typeorm: + specifier: 'catalog:' + version: 0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.4.5)) + typescript: + specifier: 'catalog:' + version: 5.4.5 + devDependencies: + '@types/connect-pg-simple': + specifier: ^7.0.3 + version: 7.0.3 + '@types/cookie-parser': + specifier: ^1.4.8 + version: 1.4.8(@types/express@4.17.21) + '@types/express': + specifier: ^4.17.17 + version: 4.17.21 + '@types/express-session': + specifier: ^1.18.1 + version: 1.18.1 + '@types/node': + specifier: ^22.7.4 + version: 22.7.5 + '@types/pg': + specifier: ^8.11.10 + version: 8.11.10 + dotenv: + specifier: 16.4.5 + version: 16.4.5 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@22.7.5)(typescript@5.4.5) + tsx: + specifier: ^4.19.1 + version: 4.19.1 + client: dependencies: '@hookform/resolvers': @@ -709,6 +739,10 @@ packages: resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.25.4': resolution: {integrity: sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==} engines: {node: '>=6.9.0'} @@ -729,6 +763,10 @@ packages: resolution: {integrity: sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==} engines: {node: '>=6.9.0'} + '@babel/generator@7.26.2': + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.25.7': resolution: {integrity: sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==} engines: {node: '>=6.9.0'} @@ -770,8 +808,8 @@ packages: resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.25.7': - resolution: {integrity: sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==} + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} '@babel/helper-module-transforms@7.25.7': @@ -820,6 +858,10 @@ packages: resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.24.7': resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} @@ -828,6 +870,10 @@ packages: resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.24.8': resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} engines: {node: '>=6.9.0'} @@ -862,6 +908,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.7': resolution: {integrity: sha512-UV9Lg53zyebzD1DwQoT9mzkEKa922LNUp5YkTJ6Uta0RbyXaQNUgcvSt7qIu1PpPzVb6rd10OVNTzkyBGeVmxQ==} engines: {node: '>=6.9.0'} @@ -1400,6 +1451,10 @@ packages: resolution: {integrity: sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==} engines: {node: '>=6.9.0'} + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.25.6': resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==} engines: {node: '>=6.9.0'} @@ -1408,6 +1463,10 @@ packages: resolution: {integrity: sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.25.9': + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + engines: {node: '>=6.9.0'} + '@babel/types@7.25.6': resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} engines: {node: '>=6.9.0'} @@ -1416,6 +1475,10 @@ packages: resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==} engines: {node: '>=6.9.0'} + '@babel/types@7.26.0': + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -3194,9 +3257,17 @@ packages: '@types/body-parser@1.19.5': resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + '@types/connect-pg-simple@7.0.3': + resolution: {integrity: sha512-NGCy9WBlW2bw+J/QlLnFZ9WjoGs6tMo3LAut6mY4kK+XHzue//lpNVpAvYRpIwM969vBRAM2Re0izUvV6kt+NA==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/cookie-parser@1.4.8': + resolution: {integrity: sha512-l37JqFrOJ9yQfRQkljb41l0xVphc7kg5JTjjr+pLRZ0IyZ49V4BQ8vbF4Ut2C2e+WH4al3xD3ZwYwIUfnbT4NQ==} + peerDependencies: + '@types/express': '*' + '@types/cookiejar@2.1.5': resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} @@ -3302,6 +3373,9 @@ packages: '@types/express-serve-static-core@4.19.5': resolution: {integrity: sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==} + '@types/express-session@1.18.1': + resolution: {integrity: sha512-S6TkD/lljxDlQ2u/4A70luD8/ZxZcrU5pQwI1rVXCiaVIywoFgbA+PIUNDjPhQpPdK0dGleLtYc/y7XWBfclBg==} + '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} @@ -3413,6 +3487,9 @@ packages: '@types/pbf@3.0.5': resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==} + '@types/pg@8.11.10': + resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} + '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} @@ -3452,6 +3529,9 @@ packages: '@types/supertest@6.0.2': resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==} + '@types/uid-safe@2.1.5': + resolution: {integrity: sha512-RwEfbxqXKEay2b5p8QQVllfnMbVPUZChiKKZ2M6+OSRRmvr4HTCCUZTWhr/QlmrMnNE0ViNBBbP1+5plF9OGRw==} + '@types/use-sync-external-store@0.0.3': resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==} @@ -4140,6 +4220,10 @@ packages: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} engines: {'0': node >= 0.8} + connect-pg-simple@10.0.0: + resolution: {integrity: sha512-pBGVazlqiMrackzCr0eKhn4LO5trJXsOX0nQoey9wCOayh80MYtThCbq8eoLsjpiWgiok/h+1/uti9/2/Una8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=22.0.0} + consola@2.15.3: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} @@ -4160,6 +4244,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-parser@1.4.7: + resolution: {integrity: sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==} + engines: {node: '>= 0.8.0'} + cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} @@ -4174,6 +4262,14 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} @@ -6195,6 +6291,9 @@ packages: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} + obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + oidc-token-hash@5.0.3: resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==} engines: {node: ^10.13.0 || >=12.0.0} @@ -6355,6 +6454,10 @@ packages: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} + pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + pg-pool@3.6.2: resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==} peerDependencies: @@ -6367,6 +6470,10 @@ packages: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} + pg-types@4.0.2: + resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} + engines: {node: '>=10'} + pg@8.12.0: resolution: {integrity: sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==} engines: {node: '>= 8.0.0'} @@ -6485,18 +6592,37 @@ packages: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} + postgres-array@3.0.2: + resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} + engines: {node: '>=12'} + postgres-bytea@1.0.0: resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} engines: {node: '>=0.10.0'} + postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + postgres-date@1.0.7: resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} engines: {node: '>=0.10.0'} + postgres-date@2.1.0: + resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} + engines: {node: '>=12'} + postgres-interval@1.2.0: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} + postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + + postgres-range@1.1.4: + resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + potpack@2.0.0: resolution: {integrity: sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==} @@ -8470,6 +8596,12 @@ snapshots: '@babel/highlight': 7.25.7 picocolors: 1.1.0 + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.0 + '@babel/compat-data@7.25.4': {} '@babel/compat-data@7.25.7': {} @@ -8482,10 +8614,10 @@ snapshots: '@babel/helper-compilation-targets': 7.25.7 '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.2) '@babel/helpers': 7.25.6 - '@babel/parser': 7.25.7 - '@babel/template': 7.25.7 - '@babel/traverse': 7.25.7(supports-color@5.5.0) - '@babel/types': 7.25.7 + '@babel/parser': 7.25.6 + '@babel/template': 7.25.0 + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 convert-source-map: 2.0.0 debug: 4.3.6(supports-color@5.5.0) gensync: 1.0.0-beta.2 @@ -8508,6 +8640,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 + '@babel/generator@7.26.2': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + '@babel/helper-annotate-as-pure@7.25.7': dependencies: '@babel/types': 7.25.7 @@ -8573,24 +8713,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.24.7': + '@babel/helper-module-imports@7.24.7(supports-color@5.5.0)': dependencies: - '@babel/traverse': 7.25.6 - '@babel/types': 7.25.6 + '@babel/traverse': 7.25.7(supports-color@5.5.0) + '@babel/types': 7.25.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.25.7(supports-color@5.5.0)': + '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/traverse': 7.25.7(supports-color@5.5.0) - '@babel/types': 7.25.7 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color '@babel/helper-module-transforms@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.25.7(supports-color@5.5.0) + '@babel/helper-module-imports': 7.25.9 '@babel/helper-simple-access': 7.25.7 '@babel/helper-validator-identifier': 7.25.7 '@babel/traverse': 7.25.7(supports-color@5.5.0) @@ -8641,10 +8781,14 @@ snapshots: '@babel/helper-string-parser@7.25.7': {} + '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-validator-identifier@7.24.7': {} '@babel/helper-validator-identifier@7.25.7': {} + '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-option@7.24.8': {} '@babel/helper-validator-option@7.25.7': {} @@ -8684,6 +8828,10 @@ snapshots: dependencies: '@babel/types': 7.25.7 + '@babel/parser@7.26.2': + dependencies: + '@babel/types': 7.26.0 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -8862,7 +9010,7 @@ snapshots: '@babel/plugin-transform-async-to-generator@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.25.7(supports-color@5.5.0) + '@babel/helper-module-imports': 7.25.9 '@babel/helper-plugin-utils': 7.25.7 '@babel/helper-remap-async-to-generator': 7.25.7(@babel/core@7.25.2) transitivePeerDependencies: @@ -9127,7 +9275,7 @@ snapshots: dependencies: '@babel/core': 7.25.2 '@babel/helper-annotate-as-pure': 7.25.7 - '@babel/helper-module-imports': 7.25.7(supports-color@5.5.0) + '@babel/helper-module-imports': 7.25.9 '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-syntax-jsx': 7.25.7(@babel/core@7.25.2) '@babel/types': 7.25.7 @@ -9154,7 +9302,7 @@ snapshots: '@babel/plugin-transform-runtime@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.25.7(supports-color@5.5.0) + '@babel/helper-module-imports': 7.25.9 '@babel/helper-plugin-utils': 7.25.7 babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.2) babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.2) @@ -9369,6 +9517,12 @@ snapshots: '@babel/parser': 7.25.7 '@babel/types': 7.25.7 + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@babel/traverse@7.25.6': dependencies: '@babel/code-frame': 7.24.7 @@ -9393,6 +9547,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + debug: 4.3.6(supports-color@5.5.0) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + '@babel/types@7.25.6': dependencies: '@babel/helper-string-parser': 7.24.8 @@ -9405,6 +9571,11 @@ snapshots: '@babel/helper-validator-identifier': 7.25.7 to-fast-properties: 2.0.0 + '@babel/types@7.26.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@bcoe/v8-coverage@0.2.3': {} '@colors/colors@1.5.0': @@ -9427,7 +9598,7 @@ snapshots: '@emotion/babel-plugin@11.12.0': dependencies: - '@babel/helper-module-imports': 7.25.7(supports-color@5.5.0) + '@babel/helper-module-imports': 7.24.7(supports-color@5.5.0) '@babel/runtime': 7.25.7 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 @@ -10626,7 +10797,7 @@ snapshots: '@rollup/plugin-babel@6.0.4(@babel/core@7.25.2)(@types/babel__core@7.20.5)(rollup@4.24.0)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.24.7 + '@babel/helper-module-imports': 7.24.7(supports-color@5.5.0) '@rollup/pluginutils': 5.1.2(rollup@4.24.0) optionalDependencies: '@types/babel__core': 7.20.5 @@ -11357,10 +11528,20 @@ snapshots: '@types/connect': 3.4.38 '@types/node': 22.7.5 + '@types/connect-pg-simple@7.0.3': + dependencies: + '@types/express': 4.17.21 + '@types/express-session': 1.18.1 + '@types/pg': 8.11.10 + '@types/connect@3.4.38': dependencies: '@types/node': 22.7.5 + '@types/cookie-parser@1.4.8(@types/express@4.17.21)': + dependencies: + '@types/express': 4.17.21 + '@types/cookiejar@2.1.5': {} '@types/d3-array@3.2.1': {} @@ -11491,6 +11672,10 @@ snapshots: '@types/range-parser': 1.2.7 '@types/send': 0.17.4 + '@types/express-session@1.18.1': + dependencies: + '@types/express': 4.17.21 + '@types/express@4.17.21': dependencies: '@types/body-parser': 1.19.5 @@ -11619,6 +11804,12 @@ snapshots: '@types/pbf@3.0.5': {} + '@types/pg@8.11.10': + dependencies: + '@types/node': 22.7.5 + pg-protocol: 1.6.1 + pg-types: 4.0.2 + '@types/prop-types@15.7.12': {} '@types/qs@6.9.15': {} @@ -11669,6 +11860,8 @@ snapshots: '@types/methods': 1.1.4 '@types/superagent': 8.1.9 + '@types/uid-safe@2.1.5': {} + '@types/use-sync-external-store@0.0.3': {} '@types/uuid@9.0.8': {} @@ -12172,7 +12365,7 @@ snapshots: babel-plugin-styled-components@2.1.4(@babel/core@7.25.2)(styled-components@5.3.9(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(supports-color@5.5.0): dependencies: '@babel/helper-annotate-as-pure': 7.25.7 - '@babel/helper-module-imports': 7.25.7(supports-color@5.5.0) + '@babel/helper-module-imports': 7.24.7(supports-color@5.5.0) '@babel/plugin-syntax-jsx': 7.25.7(@babel/core@7.25.2) lodash: 4.17.21 picomatch: 2.3.1 @@ -12545,6 +12738,12 @@ snapshots: readable-stream: 2.3.8 typedarray: 0.0.6 + connect-pg-simple@10.0.0: + dependencies: + pg: 8.12.0 + transitivePeerDependencies: + - pg-native + consola@2.15.3: {} console-control-strings@1.1.0: {} @@ -12559,6 +12758,11 @@ snapshots: convert-source-map@2.0.0: {} + cookie-parser@1.4.7: + dependencies: + cookie: 0.7.2 + cookie-signature: 1.0.6 + cookie-signature@1.0.6: {} cookie-signature@1.0.7: {} @@ -12567,6 +12771,10 @@ snapshots: cookie@0.6.0: {} + cookie@0.7.2: {} + + cookie@1.0.2: {} + cookiejar@2.1.4: {} core-js-compat@3.38.1: @@ -13129,7 +13337,7 @@ snapshots: '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.0) eslint-plugin-react: 7.35.2(eslint@8.57.0) @@ -13153,13 +13361,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.1.0 @@ -13172,14 +13380,14 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.9.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.9.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -13194,7 +13402,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -15104,6 +15312,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + obuf@1.1.2: {} + oidc-token-hash@5.0.3: {} on-finished@2.4.1: @@ -15265,6 +15475,8 @@ snapshots: pg-int8@1.0.1: {} + pg-numeric@1.0.2: {} + pg-pool@3.6.2(pg@8.12.0): dependencies: pg: 8.12.0 @@ -15279,6 +15491,16 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 + pg-types@4.0.2: + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.2 + postgres-bytea: 3.0.0 + postgres-date: 2.1.0 + postgres-interval: 3.0.0 + postgres-range: 1.1.4 + pg@8.12.0: dependencies: pg-connection-string: 2.6.4 @@ -15379,14 +15601,26 @@ snapshots: postgres-array@2.0.0: {} + postgres-array@3.0.2: {} + postgres-bytea@1.0.0: {} + postgres-bytea@3.0.0: + dependencies: + obuf: 1.1.2 + postgres-date@1.0.7: {} + postgres-date@2.1.0: {} + postgres-interval@1.2.0: dependencies: xtend: 4.0.2 + postgres-interval@3.0.0: {} + + postgres-range@1.1.4: {} + potpack@2.0.0: {} preact-render-to-string@5.2.6(preact@10.24.2): @@ -16264,7 +16498,7 @@ snapshots: styled-components@5.3.9(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1): dependencies: - '@babel/helper-module-imports': 7.25.7(supports-color@5.5.0) + '@babel/helper-module-imports': 7.24.7(supports-color@5.5.0) '@babel/traverse': 7.25.7(supports-color@5.5.0) '@emotion/is-prop-valid': 1.3.1 '@emotion/stylis': 0.8.5 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 53210b18..61972c7e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,7 +4,7 @@ packages: - 'shared/**' - 'e2e/**' - 'data/**' - - 'admin/**' + - 'backoffice/**' catalog: diff --git a/shared/config/.env.test b/shared/config/.env.test index 968fd785..5e2e689b 100644 --- a/shared/config/.env.test +++ b/shared/config/.env.test @@ -26,4 +26,8 @@ EMAIL_CONFIRMATION_TOKEN_EXPIRES_IN=2h AWS_SES_ACCESS_KEY_ID=test AWS_SES_ACCESS_KEY_SECRET=test AWS_SES_DOMAIN=test -AWS_REGION=test \ No newline at end of file +AWS_REGION=test + +# Adminjs cookie configuration +BACKOFFICE_SESSION_COOKIE_NAME=backoffice +BACKOFFICE_SESSION_COOKIE_SECRET=backoffice-cookie-secret \ No newline at end of file diff --git a/shared/dtos/users/user.dto.ts b/shared/dtos/users/user.dto.ts index c92b0fc1..61cee197 100644 --- a/shared/dtos/users/user.dto.ts +++ b/shared/dtos/users/user.dto.ts @@ -1,9 +1,11 @@ import { OmitType } from "@nestjs/mapped-types"; +import { BackOfficeSession } from "@shared/entities/users/backoffice-session"; import { User } from "@shared/entities/users/user.entity"; export type UserWithAccessToken = { user: UserDto; accessToken: string; + backofficeSession?: BackOfficeSession }; export class UserDto extends OmitType(User, ["password"]) {} diff --git a/shared/entities/custom-project.entity.ts b/shared/entities/custom-project.entity.ts index 270873dd..6005cf29 100644 --- a/shared/entities/custom-project.entity.ts +++ b/shared/entities/custom-project.entity.ts @@ -21,7 +21,7 @@ export class CustomProject { @PrimaryGeneratedColumn("uuid") id?: string; - @Column({ type: "varchar", name: "project_name" }) + @Column({ name: "project_name", type: "varchar" }) projectName: string; @Column({ name: "total_cost_npv", type: "decimal", nullable: true }) diff --git a/shared/entities/users/backoffice-session.ts b/shared/entities/users/backoffice-session.ts new file mode 100644 index 00000000..12fc82ce --- /dev/null +++ b/shared/entities/users/backoffice-session.ts @@ -0,0 +1,18 @@ +import { Entity, Column, PrimaryColumn } from "typeorm"; + +export const BACKOFFICE_SESSIONS_TABLE = "backoffice_sessions"; + +@Entity(BACKOFFICE_SESSIONS_TABLE) +export class BackOfficeSession { + @PrimaryColumn("varchar") + sid: string; + + @Column("json") + sess: { + cookie: any, + adminUser: any, + }; + + @Column("timestamp", { precision: 6, nullable: true }) + expire?: Date; +} diff --git a/shared/lib/db-entities.ts b/shared/lib/db-entities.ts index c1e963e0..eb75b6af 100644 --- a/shared/lib/db-entities.ts +++ b/shared/lib/db-entities.ts @@ -34,6 +34,7 @@ import { UserUploadCostInputs } from "@shared/entities/users/user-upload-cost-in import { UserUploadRestorationInputs } from "@shared/entities/users/user-upload-restoration-inputs.entity"; import { UserUploadConservationInputs } from "@shared/entities/users/user-upload-conservation-inputs.entity"; import { ProjectScorecard } from "@shared/entities/project-scorecard.entity"; +import { BackOfficeSession } from "@shared/entities/users/backoffice-session"; export const COMMON_DATABASE_ENTITIES = [ User, @@ -72,4 +73,5 @@ export const COMMON_DATABASE_ENTITIES = [ UserUploadRestorationInputs, UserUploadConservationInputs, ProjectScorecard, + BackOfficeSession ];