diff --git a/packages/backend/server/src/app.module.ts b/packages/backend/server/src/app.module.ts index 4a3915e98538..1382ef09578c 100644 --- a/packages/backend/server/src/app.module.ts +++ b/packages/backend/server/src/app.module.ts @@ -16,6 +16,7 @@ import { ADD_ENABLED_FEATURES, ServerConfigModule } from './core/config'; import { DocModule } from './core/doc'; import { FeatureModule } from './core/features'; import { QuotaModule } from './core/quota'; +import { CustomSetupModule } from './core/setup'; import { StorageModule } from './core/storage'; import { SyncModule } from './core/sync'; import { UserModule } from './core/user'; @@ -175,13 +176,11 @@ function buildAppModule() { // self hosted server only .useIf( config => config.isSelfhosted, + CustomSetupModule, ServeStaticModule.forRoot({ rootPath: join('/app', 'static'), exclude: ['/admin*'], - }) - ) - .useIf( - config => config.isSelfhosted, + }), ServeStaticModule.forRoot({ rootPath: join('/app', 'static', 'admin'), serveRoot: '/admin', diff --git a/packages/backend/server/src/core/auth/service.ts b/packages/backend/server/src/core/auth/service.ts index cff379071597..3619fef2d03b 100644 --- a/packages/backend/server/src/core/auth/service.ts +++ b/packages/backend/server/src/core/auth/service.ts @@ -5,14 +5,7 @@ import { PrismaClient } from '@prisma/client'; import type { CookieOptions, Request, Response } from 'express'; import { assign, omit } from 'lodash-es'; -import { - Config, - CryptoHelper, - EmailAlreadyUsed, - MailService, - WrongSignInCredentials, - WrongSignInMethod, -} from '../../fundamentals'; +import { Config, EmailAlreadyUsed, MailService } from '../../fundamentals'; import { FeatureManagementService } from '../features/management'; import { QuotaService } from '../quota/service'; import { QuotaType } from '../quota/types'; @@ -74,20 +67,19 @@ export class AuthService implements OnApplicationBootstrap { private readonly mailer: MailService, private readonly feature: FeatureManagementService, private readonly quota: QuotaService, - private readonly user: UserService, - private readonly crypto: CryptoHelper + private readonly user: UserService ) {} async onApplicationBootstrap() { if (this.config.node.dev) { try { - const [email, name, pwd] = ['dev@affine.pro', 'Dev User', 'dev']; + const [email, name, password] = ['dev@affine.pro', 'Dev User', 'dev']; let devUser = await this.user.findUserByEmail(email); if (!devUser) { devUser = await this.user.createUser({ email, name, - password: await this.crypto.encryptPassword(pwd), + password, }); } await this.quota.switchUserQuota(devUser.id, QuotaType.ProPlanV1); @@ -114,36 +106,17 @@ export class AuthService implements OnApplicationBootstrap { throw new EmailAlreadyUsed(); } - const hashedPassword = await this.crypto.encryptPassword(password); - return this.user .createUser({ name, email, - password: hashedPassword, + password, }) .then(sessionUser); } async signIn(email: string, password: string) { - const user = await this.user.findUserWithHashedPasswordByEmail(email); - - if (!user) { - throw new WrongSignInCredentials(); - } - - if (!user.password) { - throw new WrongSignInMethod(); - } - - const passwordMatches = await this.crypto.verifyPassword( - password, - user.password - ); - - if (!passwordMatches) { - throw new WrongSignInCredentials(); - } + const user = await this.user.signIn(email, password); return sessionUser(user); } @@ -382,8 +355,7 @@ export class AuthService implements OnApplicationBootstrap { id: string, newPassword: string ): Promise> { - const hashedPassword = await this.crypto.encryptPassword(newPassword); - return this.user.updateUser(id, { password: hashedPassword }); + return this.user.updateUser(id, { password: newPassword }); } async changeEmail( diff --git a/packages/backend/server/src/core/config/resolver.ts b/packages/backend/server/src/core/config/resolver.ts index 63df8fdce07d..bffcc7cd09b1 100644 --- a/packages/backend/server/src/core/config/resolver.ts +++ b/packages/backend/server/src/core/config/resolver.ts @@ -9,7 +9,7 @@ import { ResolveField, Resolver, } from '@nestjs/graphql'; -import { RuntimeConfig, RuntimeConfigType } from '@prisma/client'; +import { PrismaClient, RuntimeConfig, RuntimeConfigType } from '@prisma/client'; import { GraphQLJSON, GraphQLJSONObject } from 'graphql-scalars'; import { Config, DeploymentType, URLHelper } from '../../fundamentals'; @@ -115,7 +115,8 @@ export class ServerFlagsType implements ServerFlags { export class ServerConfigResolver { constructor( private readonly config: Config, - private readonly url: URLHelper + private readonly url: URLHelper, + private readonly db: PrismaClient ) {} @Public() @@ -165,6 +166,13 @@ export class ServerConfigResolver { return flags; }, {} as ServerFlagsType); } + + @ResolveField(() => Boolean, { + description: 'whether server has been initialized', + }) + async initialized() { + return (await this.db.user.count()) > 0; + } } @Resolver(() => ServerRuntimeConfigType) diff --git a/packages/backend/server/src/core/features/management.ts b/packages/backend/server/src/core/features/management.ts index 3cd304d4d8a8..9ff93bb5b773 100644 --- a/packages/backend/server/src/core/features/management.ts +++ b/packages/backend/server/src/core/features/management.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; -import { Config } from '../../fundamentals'; +import { Config, type EventPayload, OnEvent } from '../../fundamentals'; import { UserService } from '../user/service'; import { FeatureService } from './service'; import { FeatureType } from './types'; @@ -167,4 +167,9 @@ export class FeatureManagementService { async listFeatureWorkspaces(feature: FeatureType) { return this.feature.listFeatureWorkspaces(feature); } + + @OnEvent('user.admin.created') + async onAdminUserCreated({ id }: EventPayload<'user.admin.created'>) { + await this.addAdmin(id); + } } diff --git a/packages/backend/server/src/core/features/resolver.ts b/packages/backend/server/src/core/features/resolver.ts index cc983860ac53..1d002a466b91 100644 --- a/packages/backend/server/src/core/features/resolver.ts +++ b/packages/backend/server/src/core/features/resolver.ts @@ -47,7 +47,8 @@ export class FeatureManagementResolver { if (user) { return this.feature.addEarlyAccess(user.id, type); } else { - const user = await this.users.createAnonymousUser(email, { + const user = await this.users.createUser({ + email, registered: false, }); return this.feature.addEarlyAccess(user.id, type); diff --git a/packages/backend/server/src/core/setup/controller.ts b/packages/backend/server/src/core/setup/controller.ts new file mode 100644 index 000000000000..6ceeb8777279 --- /dev/null +++ b/packages/backend/server/src/core/setup/controller.ts @@ -0,0 +1,66 @@ +import { Body, Controller, Post, Req, Res } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; +import type { Request, Response } from 'express'; + +import { + ActionForbidden, + EventEmitter, + InternalServerError, + MutexService, + PasswordRequired, +} from '../../fundamentals'; +import { AuthService, Public } from '../auth'; +import { UserService } from '../user/service'; + +interface CreateUserInput { + email: string; + password: string; +} + +@Controller('/api/setup') +export class CustomSetupController { + constructor( + private readonly db: PrismaClient, + private readonly user: UserService, + private readonly auth: AuthService, + private readonly event: EventEmitter, + private readonly mutex: MutexService + ) {} + + @Public() + @Post('/create-admin-user') + async createAdmin( + @Req() req: Request, + @Res() res: Response, + @Body() input: CreateUserInput + ) { + if (!input.password) { + throw new PasswordRequired(); + } + + await using lock = await this.mutex.lock('createFirstAdmin'); + + if (!lock) { + throw new InternalServerError(); + } + + if ((await this.db.user.count()) > 0) { + throw new ActionForbidden('First user already created'); + } + + const user = await this.user.createUser({ + email: input.email, + password: input.password, + registered: true, + }); + + try { + await this.event.emitAsync('user.admin.created', user); + await this.auth.setCookie(req, res, user); + res.send({ id: user.id, email: user.email, name: user.name }); + } catch (e) { + await this.user.deleteUser(user.id); + throw e; + } + } +} diff --git a/packages/backend/server/src/core/setup/index.ts b/packages/backend/server/src/core/setup/index.ts new file mode 100644 index 000000000000..56fe7cfd790a --- /dev/null +++ b/packages/backend/server/src/core/setup/index.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { AuthModule } from '../auth'; +import { UserModule } from '../user'; +import { CustomSetupController } from './controller'; + +@Module({ + imports: [AuthModule, UserModule], + controllers: [CustomSetupController], +}) +export class CustomSetupModule {} diff --git a/packages/backend/server/src/core/user/resolver.ts b/packages/backend/server/src/core/user/resolver.ts index 9aa8a136e84d..e16050e02d17 100644 --- a/packages/backend/server/src/core/user/resolver.ts +++ b/packages/backend/server/src/core/user/resolver.ts @@ -12,13 +12,7 @@ import { PrismaClient } from '@prisma/client'; import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'; import { isNil, omitBy } from 'lodash-es'; -import { - Config, - CryptoHelper, - type FileUpload, - Throttle, - UserNotFound, -} from '../../fundamentals'; +import { type FileUpload, Throttle, UserNotFound } from '../../fundamentals'; import { CurrentUser } from '../auth/current-user'; import { Public } from '../auth/guard'; import { sessionUser } from '../auth/service'; @@ -177,9 +171,7 @@ class CreateUserInput { export class UserManagementResolver { constructor( private readonly db: PrismaClient, - private readonly user: UserService, - private readonly crypto: CryptoHelper, - private readonly config: Config + private readonly user: UserService ) {} @Query(() => [UserType], { @@ -222,22 +214,9 @@ export class UserManagementResolver { async createUser( @Args({ name: 'input', type: () => CreateUserInput }) input: CreateUserInput ) { - validators.assertValidEmail(input.email); - if (input.password) { - const config = await this.config.runtime.fetchAll({ - 'auth/password.max': true, - 'auth/password.min': true, - }); - validators.assertValidPassword(input.password, { - max: config['auth/password.max'], - min: config['auth/password.min'], - }); - } - - const { id } = await this.user.createAnonymousUser(input.email, { - password: input.password - ? await this.crypto.encryptPassword(input.password) - : undefined, + const { id } = await this.user.createUser({ + email: input.email, + password: input.password, registered: true, }); diff --git a/packages/backend/server/src/core/user/service.ts b/packages/backend/server/src/core/user/service.ts index 1f70deb52838..5ba60788fc97 100644 --- a/packages/backend/server/src/core/user/service.ts +++ b/packages/backend/server/src/core/user/service.ts @@ -3,12 +3,16 @@ import { Prisma, PrismaClient } from '@prisma/client'; import { Config, + CryptoHelper, EmailAlreadyUsed, EventEmitter, type EventPayload, OnEvent, + WrongSignInCredentials, + WrongSignInMethod, } from '../../fundamentals'; import { Quota_FreePlanV1_1 } from '../quota/schema'; +import { validators } from '../utils/validators'; @Injectable() export class UserService { @@ -26,6 +30,7 @@ export class UserService { constructor( private readonly config: Config, + private readonly crypto: CryptoHelper, private readonly prisma: PrismaClient, private readonly emitter: EventEmitter ) {} @@ -35,7 +40,7 @@ export class UserService { name: 'Unnamed', features: { create: { - reason: 'created by invite sign up', + reason: 'sign up', activated: true, feature: { connect: { @@ -47,30 +52,39 @@ export class UserService { }; } - async createUser(data: Prisma.UserCreateInput) { - return this.prisma.user.create({ - select: this.defaultUserSelect, - data: { - ...this.userCreatingData, - ...data, - }, - }); - } - - async createAnonymousUser( - email: string, - data?: Partial + async createUser( + data: Omit & { name?: string } ) { - const user = await this.findUserByEmail(email); + validators.assertValidEmail(data.email); + const user = await this.findUserByEmail(data.email); if (user) { throw new EmailAlreadyUsed(); } - return this.createUser({ - email, - name: email.split('@')[0], - ...data, + if (data.password) { + const config = await this.config.runtime.fetchAll({ + 'auth/password.max': true, + 'auth/password.min': true, + }); + validators.assertValidPassword(data.password, { + max: config['auth/password.max'], + min: config['auth/password.min'], + }); + + data.password = await this.crypto.encryptPassword(data.password); + } + + if (!data.name) { + data.name = data.email.split('@')[0]; + } + + return this.prisma.user.create({ + select: this.defaultUserSelect, + data: { + ...this.userCreatingData, + ...data, + }, }); } @@ -86,6 +100,7 @@ export class UserService { } async findUserByEmail(email: string) { + validators.assertValidEmail(email); return this.prisma.user.findFirst({ where: { email: { @@ -101,6 +116,7 @@ export class UserService { * supposed to be used only for `Credential SignIn` */ async findUserWithHashedPasswordByEmail(email: string) { + validators.assertValidEmail(email); return this.prisma.user.findFirst({ where: { email: { @@ -111,15 +127,27 @@ export class UserService { }); } - async findOrCreateUser( - email: string, - data?: Partial - ) { - const user = await this.findUserByEmail(email); - if (user) { - return user; + async signIn(email: string, password: string) { + const user = await this.findUserWithHashedPasswordByEmail(email); + + if (!user) { + throw new WrongSignInCredentials(); + } + + if (!user.password) { + throw new WrongSignInMethod(); } - return this.createAnonymousUser(email, data); + + const passwordMatches = await this.crypto.verifyPassword( + password, + user.password + ); + + if (!passwordMatches) { + throw new WrongSignInCredentials(); + } + + return user; } async fulfillUser( @@ -160,9 +188,23 @@ export class UserService { async updateUser( id: string, - data: Prisma.UserUpdateInput, + data: Omit & { + password?: string | null; + }, select: Prisma.UserSelect = this.defaultUserSelect ) { + if (data.password) { + const config = await this.config.runtime.fetchAll({ + 'auth/password.max': true, + 'auth/password.min': true, + }); + validators.assertValidPassword(data.password, { + max: config['auth/password.max'], + min: config['auth/password.min'], + }); + + data.password = await this.crypto.encryptPassword(data.password); + } const user = await this.prisma.user.update({ where: { id }, data, select }); this.emitter.emit('user.updated', user); diff --git a/packages/backend/server/src/core/user/types.ts b/packages/backend/server/src/core/user/types.ts index ab6ac51e85af..e57eb656ab14 100644 --- a/packages/backend/server/src/core/user/types.ts +++ b/packages/backend/server/src/core/user/types.ts @@ -7,6 +7,7 @@ import { } from '@nestjs/graphql'; import type { User } from '@prisma/client'; +import type { Payload } from '../../fundamentals/event/def'; import { CurrentUser } from '../auth/current-user'; @ObjectType() @@ -81,3 +82,11 @@ export class UpdateUserInput implements Partial { @Field({ description: 'User name', nullable: true }) name?: string; } + +declare module '../../fundamentals/event/def' { + interface UserEvents { + admin: { + created: Payload<{ id: string }>; + }; + } +} diff --git a/packages/backend/server/src/core/workspaces/resolvers/workspace.ts b/packages/backend/server/src/core/workspaces/resolvers/workspace.ts index 5bd6be71172f..302d056c4963 100644 --- a/packages/backend/server/src/core/workspaces/resolvers/workspace.ts +++ b/packages/backend/server/src/core/workspaces/resolvers/workspace.ts @@ -342,7 +342,8 @@ export class WorkspaceResolver { // only invite if the user is not already in the workspace if (originRecord) return originRecord.id; } else { - target = await this.users.createAnonymousUser(email, { + target = await this.users.createUser({ + email, registered: false, }); } diff --git a/packages/backend/server/src/data/migrations/99999-self-host-admin.ts b/packages/backend/server/src/data/migrations/99999-self-host-admin.ts index 69097c36c391..327772af58c8 100644 --- a/packages/backend/server/src/data/migrations/99999-self-host-admin.ts +++ b/packages/backend/server/src/data/migrations/99999-self-host-admin.ts @@ -2,33 +2,14 @@ import { ModuleRef } from '@nestjs/core'; import { PrismaClient } from '@prisma/client'; import { FeatureManagementService } from '../../core/features'; -import { UserService } from '../../core/user'; -import { Config, CryptoHelper } from '../../fundamentals'; +import { Config } from '../../fundamentals'; export class SelfHostAdmin1 { // do the migration static async up(db: PrismaClient, ref: ModuleRef) { const config = ref.get(Config, { strict: false }); if (config.isSelfhosted) { - const crypto = ref.get(CryptoHelper, { strict: false }); - const user = ref.get(UserService, { strict: false }); const feature = ref.get(FeatureManagementService, { strict: false }); - if ( - !process.env.AFFINE_ADMIN_EMAIL || - !process.env.AFFINE_ADMIN_PASSWORD - ) { - throw new Error( - 'You have to set AFFINE_ADMIN_EMAIL and AFFINE_ADMIN_PASSWORD environment variables to generate the initial user for self-hosted AFFiNE Server.' - ); - } - - await user.findOrCreateUser(process.env.AFFINE_ADMIN_EMAIL, { - name: 'AFFINE First User', - emailVerifiedAt: new Date(), - password: await crypto.encryptPassword( - process.env.AFFINE_ADMIN_PASSWORD - ), - }); const firstUser = await db.user.findFirst({ orderBy: { diff --git a/packages/backend/server/src/fundamentals/config/runtime/service.ts b/packages/backend/server/src/fundamentals/config/runtime/service.ts index 66eed7717e49..bf043b6f345a 100644 --- a/packages/backend/server/src/fundamentals/config/runtime/service.ts +++ b/packages/backend/server/src/fundamentals/config/runtime/service.ts @@ -3,7 +3,7 @@ import { Inject, Injectable, Logger, - OnApplicationBootstrap, + OnModuleInit, } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; import { difference, keyBy } from 'lodash-es'; @@ -45,7 +45,7 @@ function validateConfigType( * }) */ @Injectable() -export class Runtime implements OnApplicationBootstrap { +export class Runtime implements OnModuleInit { private readonly logger = new Logger('App:RuntimeConfig'); constructor( @@ -54,7 +54,7 @@ export class Runtime implements OnApplicationBootstrap { @Inject(forwardRef(() => Cache)) private readonly cache: Cache ) {} - async onApplicationBootstrap() { + async onModuleInit() { await this.upgradeDB(); } diff --git a/packages/backend/server/src/fundamentals/error/def.ts b/packages/backend/server/src/fundamentals/error/def.ts index d9d1702d9335..facff3cbb841 100644 --- a/packages/backend/server/src/fundamentals/error/def.ts +++ b/packages/backend/server/src/fundamentals/error/def.ts @@ -254,6 +254,10 @@ export const USER_FRIENDLY_ERRORS = { message: ({ min, max }) => `Password must be between ${min} and ${max} characters`, }, + password_required: { + type: 'invalid_input', + message: 'Password is required.', + }, wrong_sign_in_method: { type: 'invalid_input', message: diff --git a/packages/backend/server/src/fundamentals/error/errors.gen.ts b/packages/backend/server/src/fundamentals/error/errors.gen.ts index bbdcb9694dfd..a793399d4521 100644 --- a/packages/backend/server/src/fundamentals/error/errors.gen.ts +++ b/packages/backend/server/src/fundamentals/error/errors.gen.ts @@ -101,6 +101,12 @@ export class InvalidPasswordLength extends UserFriendlyError { } } +export class PasswordRequired extends UserFriendlyError { + constructor(message?: string) { + super('invalid_input', 'password_required', message); + } +} + export class WrongSignInMethod extends UserFriendlyError { constructor(message?: string) { super('invalid_input', 'wrong_sign_in_method', message); @@ -496,6 +502,7 @@ export enum ErrorNames { OAUTH_ACCOUNT_ALREADY_CONNECTED, INVALID_EMAIL, INVALID_PASSWORD_LENGTH, + PASSWORD_REQUIRED, WRONG_SIGN_IN_METHOD, EARLY_ACCESS_REQUIRED, SIGN_UP_FORBIDDEN, diff --git a/packages/backend/server/src/fundamentals/mutex/mutex.ts b/packages/backend/server/src/fundamentals/mutex/mutex.ts index 68d744ed17fc..42749b2bc4c6 100644 --- a/packages/backend/server/src/fundamentals/mutex/mutex.ts +++ b/packages/backend/server/src/fundamentals/mutex/mutex.ts @@ -1,10 +1,10 @@ import { randomUUID } from 'node:crypto'; import { Inject, Injectable, Logger, Scope } from '@nestjs/common'; -import { ModuleRef } from '@nestjs/core'; -import { CONTEXT } from '@nestjs/graphql'; +import { ModuleRef, REQUEST } from '@nestjs/core'; +import type { Request } from 'express'; -import type { GraphqlContext } from '../graphql'; +import { GraphqlContext } from '../graphql'; import { retryable } from '../utils/promise'; import { Locker } from './local-lock'; @@ -17,7 +17,7 @@ export class MutexService { private readonly locker: Locker; constructor( - @Inject(CONTEXT) private readonly context: GraphqlContext, + @Inject(REQUEST) private readonly request: Request | GraphqlContext, private readonly ref: ModuleRef ) { // nestjs will always find and injecting the locker from local module @@ -31,11 +31,12 @@ export class MutexService { } protected getId() { - let id = this.context.req.headers['x-transaction-id'] as string; + const req = 'req' in this.request ? this.request.req : this.request; + let id = req.headers['x-transaction-id'] as string; if (!id) { id = randomUUID(); - this.context.req.headers['x-transaction-id'] = id; + req.headers['x-transaction-id'] = id; } return id; diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index b600056cf603..3673cc5798d3 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -249,6 +249,7 @@ enum ErrorNames { OAUTH_ACCOUNT_ALREADY_CONNECTED OAUTH_STATE_EXPIRED PAGE_IS_NOT_PUBLIC + PASSWORD_REQUIRED RUNTIME_CONFIG_NOT_FOUND SAME_EMAIL_PROVIDED SAME_SUBSCRIPTION_RECURRING @@ -622,6 +623,9 @@ type ServerConfigType { """server flavor""" flavor: String! @deprecated(reason: "use `features`") + """whether server has been initialized""" + initialized: Boolean! + """server identical name could be shown as badge on user interface""" name: String! oauthProviders: [OAuthProviderType!]! diff --git a/packages/backend/server/tests/config.spec.ts b/packages/backend/server/tests/config.spec.ts index 45a3ad9e968f..23e934592716 100644 --- a/packages/backend/server/tests/config.spec.ts +++ b/packages/backend/server/tests/config.spec.ts @@ -7,7 +7,7 @@ import { createTestingModule } from './utils'; let config: Config; let module: TestingModule; test.beforeEach(async () => { - module = await createTestingModule(); + module = await createTestingModule({}, false); config = module.get(Config); }); @@ -33,4 +33,6 @@ test('should be able to override config', async t => { const config = module.get(Config); t.is(config.server.host, 'testing'); + + await module.close(); }); diff --git a/packages/backend/server/tests/doc.spec.ts b/packages/backend/server/tests/doc.spec.ts index f0c1fcd05695..aaa4201cd446 100644 --- a/packages/backend/server/tests/doc.spec.ts +++ b/packages/backend/server/tests/doc.spec.ts @@ -13,9 +13,12 @@ import { Config } from '../src/fundamentals/config'; import { createTestingModule } from './utils'; const createModule = () => { - return createTestingModule({ - imports: [QuotaModule, StorageModule, DocModule], - }); + return createTestingModule( + { + imports: [QuotaModule, StorageModule, DocModule], + }, + false + ); }; let m: TestingModule; diff --git a/packages/backend/server/tests/utils/user.ts b/packages/backend/server/tests/utils/user.ts index e71dbd3abd5e..5e5265d1f4a2 100644 --- a/packages/backend/server/tests/utils/user.ts +++ b/packages/backend/server/tests/utils/user.ts @@ -1,5 +1,4 @@ import type { INestApplication } from '@nestjs/common'; -import { hashSync } from '@node-rs/argon2'; import request, { type Response } from 'supertest'; import { @@ -54,7 +53,7 @@ export async function signUp( const user = await app.get(UserService).createUser({ name, email, - password: hashSync(password), + password, emailVerifiedAt: autoVerifyEmail ? new Date() : null, }); const { sessionId } = await app.get(AuthService).createUserSession(user); diff --git a/packages/backend/server/tests/utils/utils.ts b/packages/backend/server/tests/utils/utils.ts index 5c3816f4e9c5..b685b6d79d28 100644 --- a/packages/backend/server/tests/utils/utils.ts +++ b/packages/backend/server/tests/utils/utils.ts @@ -11,7 +11,7 @@ import supertest from 'supertest'; import { AppModule, FunctionalityModules } from '../../src/app.module'; import { AuthGuard, AuthModule } from '../../src/core/auth'; import { UserFeaturesInit1698652531198 } from '../../src/data/migrations/1698652531198-user-features-init'; -import { GlobalExceptionFilter } from '../../src/fundamentals'; +import { Config, GlobalExceptionFilter } from '../../src/fundamentals'; import { GqlModule } from '../../src/fundamentals/graphql'; async function flushDB(client: PrismaClient) { @@ -67,7 +67,8 @@ class MockResolver { } export async function createTestingModule( - moduleDef: TestingModuleMeatdata = {} + moduleDef: TestingModuleMeatdata = {}, + init = true ) { // setting up let imports = moduleDef.imports ?? []; @@ -105,11 +106,19 @@ export async function createTestingModule( await initTestingDB(prisma); } + if (init) { + await m.init(); + + const config = m.get(Config); + // by pass password min length validation + await config.runtime.set('auth/password.min', 1); + } + return m; } export async function createTestingApp(moduleDef: TestingModuleMeatdata = {}) { - const m = await createTestingModule(moduleDef); + const m = await createTestingModule(moduleDef, false); const app = m.createNestApplication({ cors: true, @@ -134,6 +143,10 @@ export async function createTestingApp(moduleDef: TestingModuleMeatdata = {}) { await app.init(); + const config = app.get(Config); + // by pass password min length validation + await config.runtime.set('auth/password.min', 1); + return { module: m, app,