From ce6857289ae8b6ffd7c442a8df5389b0742b1d06 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 17 Dec 2025 10:48:00 +0100 Subject: [PATCH 01/15] add only opaque login --- .../20251216131653-add-registration-record.js | 19 +++ package.json | 1 + src/config/configuration.ts | 1 + src/externals/crypto/crypto.service.ts | 33 ++++++ src/modules/auth/auth.controller.spec.ts | 1 + src/modules/auth/auth.controller.ts | 111 +++++++++++++++++- src/modules/auth/dto/login-access.dto.ts | 43 +++++++ .../responses/login-access-response.dto.ts | 16 +++ .../auth/dto/responses/login-response.dto.ts | 3 + .../cache-manager/cache-manager.service.ts | 28 +++++ src/modules/user/user.attributes.ts | 2 + src/modules/user/user.domain.ts | 7 ++ src/modules/user/user.model.ts | 3 + src/modules/user/user.usecase.ts | 108 ++++++++++++++--- .../attributes/workspace-logs.attributes.ts | 2 + .../workspaces-logs.interceptor.ts | 20 ++++ yarn.lock | 5 + 17 files changed, 383 insertions(+), 20 deletions(-) create mode 100644 migrations/20251216131653-add-registration-record.js diff --git a/migrations/20251216131653-add-registration-record.js b/migrations/20251216131653-add-registration-record.js new file mode 100644 index 000000000..1429a6914 --- /dev/null +++ b/migrations/20251216131653-add-registration-record.js @@ -0,0 +1,19 @@ +'use strict'; + +const tableName = 'users'; +const newColumn = 'registration_record'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.addColumn(tableName, newColumn, { + type: Sequelize.STRING, + }); + }, + + async down(queryInterface, Sequelize) { + await queryInterface.removeColumn(tableName, newColumn, { + type: Sequelize.STRING, + }); + }, +}; diff --git a/package.json b/package.json index 8af7031d7..69295da42 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@nestjs/swagger": "^11.2.3", "@nestjs/throttler": "^6.4.0", "@sendgrid/mail": "^8.1.6", + "@serenity-kit/opaque": "^1.0.0", "@types/passport-jwt": "^3.0.13", "agentkeepalive": "^4.5.0", "axios": "^1.12.0", diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 0687607d1..63a035e54 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -36,6 +36,7 @@ export default () => ({ redisConnectionString: process.env.REDIS_CONNECTION_STRING, }, secrets: { + serverSetup: process.env.OPAQUE_SERVER_SETUP, magicIv: process.env.MAGIC_IV, magicSalt: process.env.MAGIC_SALT, cryptoSecret: process.env.CRYPTO_SECRET, diff --git a/src/externals/crypto/crypto.service.ts b/src/externals/crypto/crypto.service.ts index 70d2e8173..f4b1bd9f6 100644 --- a/src/externals/crypto/crypto.service.ts +++ b/src/externals/crypto/crypto.service.ts @@ -4,6 +4,8 @@ import { AesService } from './aes'; import CryptoJS from 'crypto-js'; import crypto from 'crypto'; import bcrypt from 'bcryptjs'; +import { server } from '@serenity-kit/opaque'; +import { v4 as uuidv4 } from 'uuid'; export enum AsymmetricEncryptionAlgorithms { EllipticCurve = 'ed25519', @@ -14,9 +16,11 @@ export class CryptoService { private readonly configService: ConfigService; private readonly aesService: AesService; private readonly cryptoSecret: string; + private readonly serverSetup: string; constructor(configService: ConfigService) { this.configService = configService; + this.serverSetup = this.configService.get('secrets.serverSetup'); this.aesService = new AesService( this.configService.get('secrets.magicIv'), this.configService.get('secrets.magicSalt'), @@ -170,4 +174,33 @@ export class CryptoService { return null; } } + + startLoginOpaque( + email: string, + registrationRecord: string, + startLoginRequest: string, + ): { loginResponse: string; serverLoginState: string } { + const { loginResponse, serverLoginState } = server.startLogin({ + userIdentifier: email, + registrationRecord, + serverSetup: this.serverSetup, + startLoginRequest, + }); + + return { loginResponse, serverLoginState }; + } + + finishLoginOpaque( + finishLoginRequest: string, + serverLoginState: string, + ): { sessionKey: string } { + return server.finishLogin({ + finishLoginRequest, + serverLoginState, + }); + } + + generateSessionID(): string { + return uuidv4(); + } } diff --git a/src/modules/auth/auth.controller.spec.ts b/src/modules/auth/auth.controller.spec.ts index 34e9966e5..c7e51adc9 100644 --- a/src/modules/auth/auth.controller.spec.ts +++ b/src/modules/auth/auth.controller.spec.ts @@ -80,6 +80,7 @@ describe('AuthController', () => { hasEccKeys: true, sKey: 'encryptedText', tfa: true, + useOpaqueLogin: false, }); }); diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 944d2380e..f911923ca 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -30,7 +30,11 @@ import { KeyServerUseCases } from '../keyserver/key-server.usecase'; import { ThrottlerGuard } from '../../guards/throttler.guard'; import { CryptoService } from '../../externals/crypto/crypto.service'; import { LoginDto } from './dto/login-dto'; -import { LoginAccessDto } from './dto/login-access.dto'; +import { + LoginAccessDto, + LoginAccessOpaqueStartDto, + LoginAccessOpaqueFinishDto, +} from './dto/login-access.dto'; import { User as UserDecorator } from './decorators/user.decorator'; import { TwoFactorAuthService } from './two-factor-auth.service'; import { DeleteTfaDto } from './dto/delete-tfa.dto'; @@ -40,7 +44,11 @@ import { WorkspaceLogType } from '../workspaces/attributes/workspace-logs.attrib import { AreCredentialsCorrectDto } from './dto/are-credentials-correct.dto'; import { AuditLog } from '../../common/audit-logs/decorators/audit-log.decorator'; import { AuditAction } from '../../common/audit-logs/audit-logs.attributes'; -import { LoginAccessResponseDto } from './dto/responses/login-access-response.dto'; +import { + LoginAccessResponseDto, + LoginAccessResponseOpaqueFinishDto, + LoginAccessResponseOpaqueStartDto, +} from './dto/responses/login-access-response.dto'; import { LoginResponseDto } from './dto/responses/login-response.dto'; import { JwtToken } from './decorators/get-jwt.decorator'; import { AuthUsecases } from './auth.usecase'; @@ -92,6 +100,7 @@ export class AuthController { tfa: required2FA, hasKyberKeys: !!keys.kyber, hasEccKeys: !!keys.ecc, + useOpaqueLogin: user.isOpaqueEnabled ?? false, }; } catch (err) { if (!(err instanceof HttpException)) { @@ -145,6 +154,104 @@ export class AuthController { } } + @Post('/login-opaque/start') + @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: 'Start opaque to access user account', + }) + @ApiOkResponse({ + description: 'User successfully complited first stage of opaque login', + type: LoginAccessResponseOpaqueStartDto, + }) + @Public() + @WorkspaceLogAction(WorkspaceLogType.LoginOpaqueStart) + async loginOpaqueStart( + @Body() body: LoginAccessOpaqueStartDto, + ): Promise { + Logger.log( + `[AUTH/LOGIN-ACCESS-OPAQUE-START] Attempting first step of opaque login for email: ${body.email}`, + ); + try { + const email = body.email.toLowerCase(); + const user = await this.userUseCases.findByEmail(email); + + if (!user) { + throw new UnauthorizedException('Wrong login credentials'); + } + + const { loginResponse, serverLoginState } = + this.cryptoService.startLoginOpaque( + email, + user.registrationRecord, + body.startLoginRequest, + ); + + await this.userUseCases.setLoginState(email, serverLoginState); + + Logger.log( + `[AUTH/LOGIN-ACCESS-OPAQUE-START] Successful first step of opaque login for email: ${body.email}`, + ); + return { loginResponse }; + } catch (error) { + Logger.error( + `[AUTH/LOGIN-ACCESS-OPAQUE-START] Failed first step of opaque login attempt for email: ${body.email}`, + error.stack, + ); + throw error; + } + } + + @Post('/login-opaque/finish') + @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: 'Finish opaque to access user account', + }) + @ApiOkResponse({ + description: 'User successfully complited first stage of opaque login', + type: LoginAccessResponseOpaqueFinishDto, + }) + @Public() + @WorkspaceLogAction(WorkspaceLogType.LoginOpaqueStart) + async loginOpaqueFinish( + @Body() body: LoginAccessOpaqueFinishDto, + ): Promise { + Logger.log( + `[AUTH/LOGIN-ACCESS-OPAQUE-FINISH] Attempting second step of opaque login for email: ${body.email}`, + ); + try { + const email = body.email.toLowerCase(); + const user = await this.userUseCases.findByEmail(email); + + if (!user) { + throw new UnauthorizedException('Wrong login credentials'); + } + + const serverLoginState = await this.userUseCases.getLoginState(email); + + const { sessionKey } = this.cryptoService.finishLoginOpaque( + body.finishLoginRequest, + serverLoginState, + ); + + const sessionID = this.cryptoService.generateSessionID(); + await this.userUseCases.setSessionKey(sessionID, sessionKey); + + const { user: userDTO, token } = + await this.userUseCases.loginAccessOpaque(email, body.tfa); + + Logger.log( + `[AUTH/LOGIN-ACCESS-OPAQUE-FINISH] Successful second step of opaque login for email: ${body.email}`, + ); + return { user: userDTO, sessionID, token }; + } catch (error) { + Logger.error( + `[AUTH/LOGIN-ACCESS-OPAQUE-START] Failed first step of opaque login attempt for email: ${body.email}`, + error.stack, + ); + throw error; + } + } + @Get('/logout') @HttpCode(HttpStatus.OK) @ApiBearerAuth() diff --git a/src/modules/auth/dto/login-access.dto.ts b/src/modules/auth/dto/login-access.dto.ts index fb337ed7f..e7d2b3975 100644 --- a/src/modules/auth/dto/login-access.dto.ts +++ b/src/modules/auth/dto/login-access.dto.ts @@ -77,3 +77,46 @@ export class LoginAccessDto { }) keys?: OptionalKeyGroup; } + +export class LoginAccessOpaqueStartDto { + @ApiProperty({ + example: 'user@internxt.com', + description: 'The email of the user', + }) + @IsNotEmpty() + @IsEmail() + email: string; + + @ApiProperty({ + example: 'startLoginRequest', + description: 'The request to start opaque login', + }) + @IsNotEmpty() + startLoginRequest: string; +} + +export class LoginAccessOpaqueFinishDto { + @ApiProperty({ + example: 'user@internxt.com', + description: 'The email of the user', + }) + @IsNotEmpty() + @IsEmail() + email: string; + + @ApiProperty({ + example: 'finishLoginRequest', + description: 'The request to finish opaque login', + }) + @IsNotEmpty() + finishLoginRequest: string; + + @ApiProperty({ + example: 'two_factor_authentication_code', + description: 'TFA', + required: false, + }) + @IsOptional() + @IsString() + tfa?: string; +} diff --git a/src/modules/auth/dto/responses/login-access-response.dto.ts b/src/modules/auth/dto/responses/login-access-response.dto.ts index 98859ba8b..51b86132c 100644 --- a/src/modules/auth/dto/responses/login-access-response.dto.ts +++ b/src/modules/auth/dto/responses/login-access-response.dto.ts @@ -14,3 +14,19 @@ export class LoginAccessResponseDto { @ApiProperty() newToken: string; } + +export class LoginAccessResponseOpaqueStartDto { + @ApiProperty() + loginResponse: string; +} + +export class LoginAccessResponseOpaqueFinishDto { + @ApiProperty({ type: UserDto }) + user: UserDto; + + @ApiProperty() + token: string; + + @ApiProperty() + sessionID: string; +} diff --git a/src/modules/auth/dto/responses/login-response.dto.ts b/src/modules/auth/dto/responses/login-response.dto.ts index 709b1dc6b..4c2ba3e07 100644 --- a/src/modules/auth/dto/responses/login-response.dto.ts +++ b/src/modules/auth/dto/responses/login-response.dto.ts @@ -15,4 +15,7 @@ export class LoginResponseDto { @ApiProperty() hasEccKeys: boolean; + + @ApiProperty() + useOpaqueLogin: boolean; } diff --git a/src/modules/cache-manager/cache-manager.service.ts b/src/modules/cache-manager/cache-manager.service.ts index d50622ca1..4a65730b3 100644 --- a/src/modules/cache-manager/cache-manager.service.ts +++ b/src/modules/cache-manager/cache-manager.service.ts @@ -8,6 +8,8 @@ export class CacheManagerService { private readonly LIMIT_KEY_PREFIX = 'limit:'; private readonly JWT_KEY_PREFIX = 'jwt:'; private readonly AVATAR_KEY_PREFIX = 'avatar:'; + private readonly SESSION_KEY_PREFIX = 'session:'; + private readonly LOGIN_STATE_PREFIX = 'loginstate:'; private readonly TTL_10_MINUTES = 10000 * 60; private readonly TTL_24_HOURS = 24 * 60 * 60 * 1000; @@ -100,4 +102,30 @@ export class CacheManagerService { async deleteUserAvatar(userUuid: string) { return this.cacheManager.del(`${this.AVATAR_KEY_PREFIX}${userUuid}`); } + + async getSessionKey(sessionID: string): Promise { + return this.cacheManager.get( + `${this.SESSION_KEY_PREFIX}${sessionID}`, + ); + } + + async setSessionKey(sessionID: string, sessionKey: string, ttl?: number) { + return this.cacheManager.set( + `${this.SESSION_KEY_PREFIX}${sessionID}`, + sessionKey, + ttl ?? this.TTL_10_MINUTES, + ); + } + + async getLoginState(email: string): Promise { + return this.cacheManager.get(`${this.LOGIN_STATE_PREFIX}${email}`); + } + + async setLoginState(email: string, serverLoginState: string, ttl?: number) { + return this.cacheManager.set( + `${this.LOGIN_STATE_PREFIX}${email}`, + serverLoginState, + ttl ?? this.TTL_10_MINUTES, + ); + } } diff --git a/src/modules/user/user.attributes.ts b/src/modules/user/user.attributes.ts index 7cf249341..fc039bbb9 100644 --- a/src/modules/user/user.attributes.ts +++ b/src/modules/user/user.attributes.ts @@ -30,4 +30,6 @@ export interface UserAttributes { emailVerified: boolean; updatedAt?: Date; createdAt?: Date; + isOpaqueEnabled?: boolean; + registrationRecord?: string; } diff --git a/src/modules/user/user.domain.ts b/src/modules/user/user.domain.ts index 680184e4a..e7851bfa4 100644 --- a/src/modules/user/user.domain.ts +++ b/src/modules/user/user.domain.ts @@ -31,6 +31,8 @@ export class User implements UserAttributes { emailVerified: boolean; updatedAt: Date; createdAt: Date; + isOpaqueEnabled: boolean; + registrationRecord?: string; constructor({ id, @@ -63,6 +65,8 @@ export class User implements UserAttributes { emailVerified, updatedAt, createdAt, + isOpaqueEnabled, + registrationRecord, }: UserAttributes) { this.id = id; this.userId = userId; @@ -94,6 +98,8 @@ export class User implements UserAttributes { this.emailVerified = emailVerified; this.updatedAt = updatedAt; this.createdAt = createdAt; + this.isOpaqueEnabled = isOpaqueEnabled; + this.registrationRecord = registrationRecord; } static build(user: UserAttributes): User { @@ -132,6 +138,7 @@ export class User implements UserAttributes { sharedWorkspace: this.sharedWorkspace, avatar: this.avatar, lastPasswordChangedAt: this.lastPasswordChangedAt, + isOpaqueEnabled: this.isOpaqueEnabled, }; } } diff --git a/src/modules/user/user.model.ts b/src/modules/user/user.model.ts index b47862ac6..dd5378bce 100644 --- a/src/modules/user/user.model.ts +++ b/src/modules/user/user.model.ts @@ -61,6 +61,9 @@ export class UserModel extends Model implements UserAttributes { @BelongsTo(() => FolderModel) rootFolder: FolderModel; + @Column + registrationRecord: string; + @Column hKey: Buffer; diff --git a/src/modules/user/user.usecase.ts b/src/modules/user/user.usecase.ts index b81982f02..c2cc15064 100644 --- a/src/modules/user/user.usecase.ts +++ b/src/modules/user/user.usecase.ts @@ -1542,15 +1542,10 @@ export class UserUseCases { return this.userRepository.getNotificationTokens(user.uuid); } - async loginAccess( - loginAccessDto: Omit< - LoginAccessDto, - 'privateKey' | 'publicKey' | 'revocateKey' | 'revocationKey' - > & { platform?: string }, - ) { + async getUserData(email: string): Promise { const MAX_LOGIN_FAIL_ATTEMPTS = 10; - const userData = await this.findByEmail(loginAccessDto.email.toLowerCase()); + const userData = await this.findByEmail(email.toLowerCase()); if (!userData) { throw new UnauthorizedException('Wrong login credentials'); @@ -1564,20 +1559,15 @@ export class UserUseCases { 'Your account has been blocked for security reasons. Please reach out to us', ); } + return userData; + } - const hashedPass = this.cryptoService.decryptText(loginAccessDto.password); - - if (hashedPass !== userData.password.toString()) { - await this.userRepository.loginFailed(userData, true); - throw new UnauthorizedException('Wrong login credentials'); - } - - const twoFactorEnabled = - userData.secret_2FA && userData.secret_2FA.length > 0; + verify2FAcode(token: string, secret: string) { + const twoFactorEnabled = secret && secret.length > 0; if (twoFactorEnabled) { const tfaResult = speakeasy.totp.verifyDelta({ - secret: userData.secret_2FA, - token: loginAccessDto.tfa, + secret, + token, encoding: 'base32', window: 2, }); @@ -1586,6 +1576,24 @@ export class UserUseCases { throw new UnauthorizedException('Wrong 2-factor auth code'); } } + } + + async loginAccess( + loginAccessDto: Omit< + LoginAccessDto, + 'privateKey' | 'publicKey' | 'revocateKey' | 'revocationKey' + > & { platform?: string }, + ) { + const userData = await this.getUserData(loginAccessDto.email); + const hashedPass = this.cryptoService.decryptText(loginAccessDto.password); + + if (hashedPass !== userData.password.toString()) { + await this.userRepository.loginFailed(userData, true); + throw new UnauthorizedException('Wrong login credentials'); + } + + this.verify2FAcode(loginAccessDto.tfa, userData.secret_2FA); + const { token, newToken } = await this.getAuthTokens( userData, undefined, @@ -1667,6 +1675,54 @@ export class UserUseCases { return { user, token, userTeam: null, newToken }; } + async loginAccessOpaque(email: string, tfa: string) { + const userData = await this.getUserData(email); + this.verify2FAcode(tfa, userData.secret_2FA); + + const { newToken } = await this.getAuthTokens(userData, undefined, '3d'); + await this.userRepository.loginFailed(userData, false); + + this.updateByUuid(userData.uuid, { updatedAt: new Date() }); + + const rootFolder = await this.getOrCreateUserRootFolderAndBucket(userData); + + const userBucket = rootFolder?.bucket; + + const keys = await this.keyServerUseCases.findUserKeys(userData.id); + + const user = { + email: userData.email, + userId: userData.userId, + mnemonic: userData.mnemonic.toString(), + root_folder_id: rootFolder?.id, + rootFolderId: rootFolder?.uuid, + name: userData.name, + lastname: userData.lastname, + uuid: userData.uuid, + credit: userData.credit, + createdAt: userData.createdAt, + tierId: userData.tierId, + privateKey: null, + publicKey: null, + revocateKey: null, + keys: keys, + bucket: userBucket, + registerCompleted: userData.registerCompleted, + teams: false, + username: userData.username, + bridgeUser: userData.bridgeUser, + sharedWorkspace: userData.sharedWorkspace, + appSumoDetails: null, + hasReferralsProgram: false, + backupsBucket: userData.backupsBucket, + avatar: userData.avatar ? await this.getAvatarUrl(userData.avatar) : null, + emailVerified: userData.emailVerified, + lastPasswordChangedAt: userData.lastPasswordChangedAt, + }; + + return { user, token: newToken }; + } + async getOrCreateUserRootFolderAndBucket(user: User) { const rootFolder = await this.folderUseCases.getFolder(user.rootFolderId); @@ -2108,4 +2164,20 @@ export class UserUseCases { newToken: newToken, }; } + + async getSessionKey(sessionID: string): Promise { + return this.cacheManager.getSessionKey(sessionID); + } + + async setSessionKey(sessionID: string, sessionKey: string): Promise { + await this.cacheManager.setSessionKey(sessionID, sessionKey); + } + + async setLoginState(email: string, serverLoginState: string): Promise { + this.cacheManager.setLoginState(email, serverLoginState); + } + + async getLoginState(email: string): Promise { + return this.cacheManager.getLoginState(email); + } } diff --git a/src/modules/workspaces/attributes/workspace-logs.attributes.ts b/src/modules/workspaces/attributes/workspace-logs.attributes.ts index 8a7cca862..51e6a7b38 100644 --- a/src/modules/workspaces/attributes/workspace-logs.attributes.ts +++ b/src/modules/workspaces/attributes/workspace-logs.attributes.ts @@ -1,5 +1,7 @@ export enum WorkspaceLogType { Login = 'login', + LoginOpaqueStart = 'login-opaque-start', + LoginOpaqueFinish = 'login-opaque-finish', ChangedPassword = 'changed-password', Logout = 'logout', ShareFile = 'share-file', diff --git a/src/modules/workspaces/interceptors/workspaces-logs.interceptor.ts b/src/modules/workspaces/interceptors/workspaces-logs.interceptor.ts index 4208587da..da3670a7e 100644 --- a/src/modules/workspaces/interceptors/workspaces-logs.interceptor.ts +++ b/src/modules/workspaces/interceptors/workspaces-logs.interceptor.ts @@ -42,6 +42,8 @@ export class WorkspacesLogsInterceptor implements NestInterceptor { ) { this.actionHandler = { [WorkspaceLogType.Login]: this.logIn.bind(this), + [WorkspaceLogType.LoginOpaqueStart]: this.loginOpaqueStart.bind(this), + [WorkspaceLogType.LoginOpaqueFinish]: this.loginOpaqueFinish.bind(this), [WorkspaceLogType.ChangedPassword]: this.changedPassword.bind(this), [WorkspaceLogType.Logout]: this.logout.bind(this), [WorkspaceLogType.DeleteFile]: this.deleteFile.bind(this), @@ -103,6 +105,24 @@ export class WorkspacesLogsInterceptor implements NestInterceptor { await this.handleUserAction(platform, WorkspaceLogType.Login, req, res); } + async loginOpaqueStart(platform: WorkspaceLogPlatform, req: any, res: any) { + await this.handleUserAction( + platform, + WorkspaceLogType.LoginOpaqueStart, + req, + res, + ); + } + + async loginOpaqueFinish(platform: WorkspaceLogPlatform, req: any, res: any) { + await this.handleUserAction( + platform, + WorkspaceLogType.LoginOpaqueFinish, + req, + res, + ); + } + async changedPassword(platform: WorkspaceLogPlatform, req: any, res: any) { await this.handleUserAction( platform, diff --git a/yarn.lock b/yarn.lock index 37bf430c9..f028b3262 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2126,6 +2126,11 @@ "@sendgrid/client" "^8.1.5" "@sendgrid/helpers" "^8.0.0" +"@serenity-kit/opaque@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@serenity-kit/opaque/-/opaque-1.0.0.tgz#ef265667cd13977a53f22732f0d204452b5fe59c" + integrity sha512-UCu92RBFroWaUOdrIJzjXW8M/wqGGi7/2J8D5BPbCNAYU27iyzr5JDZ0N6wq31IhNhEIr7C/8V3+9aSgXi4F3A== + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" From 083c4080e9c029fa7b66858936081f20f3a3b4b1 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 17 Dec 2025 10:49:21 +0100 Subject: [PATCH 02/15] rename env variable --- src/config/configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 63a035e54..54eacef34 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -36,7 +36,7 @@ export default () => ({ redisConnectionString: process.env.REDIS_CONNECTION_STRING, }, secrets: { - serverSetup: process.env.OPAQUE_SERVER_SETUP, + serverSetup: process.env.OPAQUE_SECRET, magicIv: process.env.MAGIC_IV, magicSalt: process.env.MAGIC_SALT, cryptoSecret: process.env.CRYPTO_SECRET, From 1428dc06d70b68e5007fcbfb882483f2723c9865 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 17 Dec 2025 15:49:19 +0100 Subject: [PATCH 03/15] add tests --- jest.config.ts | 25 +++++++++++ jest.setup.js | 3 +- package.json | 22 ---------- src/modules/auth/auth.controller.spec.ts | 54 +++++++++++++++++++++++- src/modules/user/user.usecase.ts | 42 +++++++++++++++++- 5 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 jest.config.ts diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 000000000..762559c4e --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,25 @@ +import type { JestConfigWithTsJest } from 'ts-jest'; + +const config: JestConfigWithTsJest = { + preset: 'ts-jest/presets/default-esm', + + testEnvironment: 'node', + + extensionsToTreatAsEsm: ['.ts'], + + transform: { + '^.+\\.ts$': [ + 'ts-jest', + { + useESM: true, + tsconfig: 'tsconfig.json', + }, + ], + }, + + transformIgnorePatterns: ['/node_modules/(?!(?:@serenity-kit/opaque)/)'], + + setupFilesAfterEnv: ['/jest.setup.js'], +}; + +export default config; diff --git a/jest.setup.js b/jest.setup.js index 69357ad19..e847f79f4 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -8,7 +8,6 @@ if (typeof globalThis.BigInt === 'function') { } } -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { TextEncoder, TextDecoder } = require('util'); +import { TextEncoder, TextDecoder } from 'util'; global.TextEncoder = global.TextEncoder || TextEncoder; global.TextDecoder = global.TextDecoder || TextDecoder; diff --git a/package.json b/package.json index 69295da42..57b9527e9 100644 --- a/package.json +++ b/package.json @@ -132,28 +132,6 @@ "tsconfig-paths": "4.2.0", "typescript": "^5.8.3" }, - "jest": { - "moduleFileExtensions": [ - "js", - "json", - "ts" - ], - "roots": [ - "src", - "test" - ], - "testRegex": ".*\\.spec\\.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - }, - "collectCoverageFrom": [ - "**/*.(t|j)s" - ], - "testEnvironment": "node", - "setupFilesAfterEnv": [ - "/jest.setup.js" - ] - }, "lint-staged": { "./src/**/*.{js,ts}": "yarn run lint" } diff --git a/src/modules/auth/auth.controller.spec.ts b/src/modules/auth/auth.controller.spec.ts index c7e51adc9..3797fc737 100644 --- a/src/modules/auth/auth.controller.spec.ts +++ b/src/modules/auth/auth.controller.spec.ts @@ -4,7 +4,10 @@ import { UserUseCases } from '../user/user.usecase'; import { KeyServerUseCases } from '../keyserver/key-server.usecase'; import { CryptoService } from '../../externals/crypto/crypto.service'; import { LoginDto } from './dto/login-dto'; -import { LoginAccessDto } from './dto/login-access.dto'; +import { + LoginAccessDto, + LoginAccessOpaqueStartDto, +} from './dto/login-access.dto'; import { BadRequestException, ConflictException, @@ -23,6 +26,7 @@ import { Test } from '@nestjs/testing'; import { FeatureLimitService } from '../feature-limit/feature-limit.service'; import { PaymentRequiredException } from '../feature-limit/exceptions/payment-required.exception'; import { PlatformName } from '../../common/constants'; +import * as opaque from '@serenity-kit/opaque'; describe('AuthController', () => { let authController: AuthController; @@ -32,6 +36,9 @@ describe('AuthController', () => { let twoFactorAuthService: DeepMocked; let featureLimitService: DeepMocked; + beforeAll(async () => { + await opaque.ready; + }); beforeEach(async () => { const moduleRef = await Test.createTestingModule({ controllers: [AuthController], @@ -108,6 +115,51 @@ describe('AuthController', () => { }); }); + describe('POST /login-opaque', () => { + it('Should sucessfully finish 1st phase of the login', async () => { + const loginOpaqueDto = new LoginAccessOpaqueStartDto(); + loginOpaqueDto.email = 'USER_test@gmail.com'; + const password = v4(); + const { startLoginRequest } = opaque.client.startLogin({ + password, + }); + loginOpaqueDto.startLoginRequest = startLoginRequest; + const serverSetupMock = opaque.server.createSetup(); + jest + .spyOn(cryptoService['configService'], 'get') + .mockReturnValue(serverSetupMock); + const { clientRegistrationState, registrationRequest } = + opaque.client.startRegistration({ password }); + const { registrationResponse } = opaque.server.createRegistrationResponse( + { + serverSetup: serverSetupMock, + userIdentifier: loginOpaqueDto.email.toLowerCase(), + registrationRequest, + }, + ); + const { registrationRecord: registrationRecordMock } = + opaque.client.finishRegistration({ + clientRegistrationState, + registrationResponse, + password, + }); + jest.spyOn(userUseCases, 'findByEmail').mockResolvedValueOnce({ + registrationRecord: registrationRecordMock, + } as any); + + const result = await authController.loginOpaqueStart(loginOpaqueDto); + + expect(cryptoService.startLoginOpaque).toHaveBeenCalledTimes(1); + expect(cryptoService.startLoginOpaque).toHaveBeenCalledWith({ + userIdentifier: loginOpaqueDto.email.toLocaleLowerCase(), + registrationRecord: registrationRecordMock, + serverSetup: serverSetupMock, + startLoginRequest: loginOpaqueDto.startLoginRequest, + }); + expect(result).toEqual({ success: true }); + }); + }); + describe('POST /login/access', () => { const loginAccessDto = new LoginAccessDto(); loginAccessDto.email = 'user_test@gmail.com'; diff --git a/src/modules/user/user.usecase.ts b/src/modules/user/user.usecase.ts index c2cc15064..edea9c108 100644 --- a/src/modules/user/user.usecase.ts +++ b/src/modules/user/user.usecase.ts @@ -880,6 +880,44 @@ export class UserUseCases { return !hasBeenSubscribed; } + async getNewToken( + user: User, + tokenExpirationTime: string | number = '3d', + platform?: string, + customIat?: number, + ) { + const jti = v4(); + const availableWorkspaces = + await this.workspaceRepository.findUserAvailableWorkspaces(user.uuid); + + const owners = [ + ...new Set(availableWorkspaces.map(({ workspace }) => workspace.ownerId)), + ]; + const token = Sign( + { + jti, + sub: user.uuid, + payload: { + uuid: user.uuid, + email: user.email, + name: user.name, + lastname: user.lastname, + username: user.username, + sharedWorkspace: true, + networkCredentials: { + user: user.bridgeUser, + }, + workspaces: { owners }, + ...(platform && { platform }), + }, + ...(customIat ? { iat: customIat } : null), + }, + this.configService.get('secrets.jwt'), + tokenExpirationTime, + ); + return token; + } + async getAuthTokens( user: User, customIat?: number, @@ -1679,7 +1717,7 @@ export class UserUseCases { const userData = await this.getUserData(email); this.verify2FAcode(tfa, userData.secret_2FA); - const { newToken } = await this.getAuthTokens(userData, undefined, '3d'); + const token = await this.getNewToken(userData); await this.userRepository.loginFailed(userData, false); this.updateByUuid(userData.uuid, { updatedAt: new Date() }); @@ -1720,7 +1758,7 @@ export class UserUseCases { lastPasswordChangedAt: userData.lastPasswordChangedAt, }; - return { user, token: newToken }; + return { user, token }; } async getOrCreateUserRootFolderAndBucket(user: User) { From 1a3bd4f167874e16376faed0eac0dfac54a489e5 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 18 Dec 2025 09:58:10 +0100 Subject: [PATCH 04/15] add test --- .env.test | 1 + .eslintrc.js | 31 ------ jest.config.ts | 16 +-- jest.setup.js | 6 + package.json | 1 + .../auth/auth.contoller.opaque.spec.ts | 103 ++++++++++++++++++ src/modules/auth/auth.controller.spec.ts | 55 +--------- 7 files changed, 118 insertions(+), 95 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 src/modules/auth/auth.contoller.opaque.spec.ts diff --git a/.env.test b/.env.test index af51fa44e..7d08c8997 100644 --- a/.env.test +++ b/.env.test @@ -10,6 +10,7 @@ DRIVE_GATEWAY_PUBLIC_SECRET=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2tx CRYPTO_SECRET=6KYQBP847D4ATSFA CRYPTO_SECRET2=8Q8VMUE3BJZV87GT +OPAQUE_SECRET=awirun08Dxx3yBpGdd0W2-j4Tl5ip02M5Uu7EVRhtqUzEEdW5EhlP1QC1z3UX8hB7cavoCyem4Kl0iCymdTsbk_tbiJu8-zzrWF3S1nQ2cGY5TkDXIatNKh5riaw7xINwkTOycgxvsIENsPn2W19OgAw2_Zih_1f4Px6ncj7-iw NOTIFICATIONS_URL=http://localhost:3000 NOTIFICATIONS_API_KEY=secret diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index e532e21f0..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - tsconfigRootDir: __dirname, - sourceType: 'module', - }, - plugins: ['@typescript-eslint/eslint-plugin'], - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', - ], - root: true, - env: { - node: true, - jest: true, - }, - ignorePatterns: ['.eslintrc.js'], - rules: { - '@typescript-eslint/no-unused-vars': [ - 'warn', - { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, - ], - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/prefer-readonly': 'warn', - '@typescript-eslint/prefer-optional-chain': 'warn' - }, -}; diff --git a/jest.config.ts b/jest.config.ts index 762559c4e..3a252d364 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,24 +1,20 @@ import type { JestConfigWithTsJest } from 'ts-jest'; const config: JestConfigWithTsJest = { - preset: 'ts-jest/presets/default-esm', - + preset: 'ts-jest', testEnvironment: 'node', - - extensionsToTreatAsEsm: ['.ts'], - transform: { '^.+\\.ts$': [ 'ts-jest', { - useESM: true, - tsconfig: 'tsconfig.json', + tsconfig: { + esModuleInterop: true, + allowSyntheticDefaultImports: true, + }, }, ], }, - - transformIgnorePatterns: ['/node_modules/(?!(?:@serenity-kit/opaque)/)'], - + transformIgnorePatterns: ['node_modules/(?!@serenity-kit/opaque)'], setupFilesAfterEnv: ['/jest.setup.js'], }; diff --git a/jest.setup.js b/jest.setup.js index e847f79f4..6062a2c33 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -11,3 +11,9 @@ if (typeof globalThis.BigInt === 'function') { import { TextEncoder, TextDecoder } from 'util'; global.TextEncoder = global.TextEncoder || TextEncoder; global.TextDecoder = global.TextDecoder || TextDecoder; + +import * as opaque from '@serenity-kit/opaque'; + +beforeAll(async () => { + await opaque.ready; +}); diff --git a/package.json b/package.json index 57b9527e9..542317af3 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "", "author": "Internxt", "license": "MIT", + "type": "module", "scripts": { "prebuild": "rimraf dist", "build": "nest build", diff --git a/src/modules/auth/auth.contoller.opaque.spec.ts b/src/modules/auth/auth.contoller.opaque.spec.ts new file mode 100644 index 000000000..e37f14674 --- /dev/null +++ b/src/modules/auth/auth.contoller.opaque.spec.ts @@ -0,0 +1,103 @@ +import { AuthController } from './auth.controller'; +import { UserUseCases } from '../user/user.usecase'; +import { CryptoService } from '../../externals/crypto/crypto.service'; + +import { LoginAccessOpaqueStartDto } from './dto/login-access.dto'; +import { Logger } from '@nestjs/common'; +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { v4 } from 'uuid'; + +import { Test } from '@nestjs/testing'; +import * as opaque from '@serenity-kit/opaque'; +import { ConfigService } from '@nestjs/config'; + +describe('AuthController', () => { + let authController: AuthController; + let userUseCases: DeepMocked; + let cryptoService: DeepMocked; + + let serverSetupMock: string; + + beforeAll(async () => { + serverSetupMock = opaque.server.createSetup(); + }); + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + controllers: [AuthController], + providers: [ + CryptoService, + { + provide: ConfigService, + useValue: { + get: (key: string) => { + if (key === 'secrets.serverSetup') return serverSetupMock; + if (key === 'secrets.cryptoSecret2') return 'a'.repeat(64); // Valid hex key + if (key === 'secrets.cryptoSecret') return 'b'.repeat(64); // Valid hex key + return null; + }, + }, + }, + ], + }) + .setLogger(createMock()) + .useMocker((token) => { + if (token === CryptoService || token === ConfigService) { + return undefined; + } + return createMock(); + }) + .compile(); + + authController = moduleRef.get(AuthController); + userUseCases = moduleRef.get(UserUseCases); + cryptoService = moduleRef.get(CryptoService); + }); + + describe('POST /login-opaque', () => { + it('Should sucessfully finish 1st phase of the login', async () => { + jest + .spyOn(cryptoService['configService'], 'get') + .mockReturnValue(serverSetupMock); + const loginOpaqueDto = new LoginAccessOpaqueStartDto(); + loginOpaqueDto.email = 'USER_test@gmail.com'; + const password = v4(); + const { startLoginRequest } = opaque.client.startLogin({ + password, + }); + loginOpaqueDto.startLoginRequest = startLoginRequest; + const { clientRegistrationState, registrationRequest } = + opaque.client.startRegistration({ password }); + const { registrationResponse } = opaque.server.createRegistrationResponse( + { + serverSetup: serverSetupMock, + userIdentifier: loginOpaqueDto.email.toLowerCase(), + registrationRequest, + }, + ); + const { registrationRecord: registrationRecordMock } = + opaque.client.finishRegistration({ + clientRegistrationState, + registrationResponse, + password, + }); + jest.spyOn(userUseCases, 'findByEmail').mockResolvedValueOnce({ + registrationRecord: registrationRecordMock, + } as any); + const startLoginOpaqueSpy = jest.spyOn(cryptoService, 'startLoginOpaque'); + + const result = await authController.loginOpaqueStart(loginOpaqueDto); + + expect(startLoginOpaqueSpy).toHaveBeenCalledTimes(1); + expect(startLoginOpaqueSpy).toHaveBeenCalledWith( + loginOpaqueDto.email.toLowerCase(), + registrationRecordMock, + loginOpaqueDto.startLoginRequest, + ); + + expect(result.loginResponse).toBeDefined(); + expect(result).toEqual({ loginResponse: expect.any(String) }); + expect(typeof result.loginResponse).toBe('string'); + }); + }); +}); diff --git a/src/modules/auth/auth.controller.spec.ts b/src/modules/auth/auth.controller.spec.ts index 3797fc737..34e9966e5 100644 --- a/src/modules/auth/auth.controller.spec.ts +++ b/src/modules/auth/auth.controller.spec.ts @@ -4,10 +4,7 @@ import { UserUseCases } from '../user/user.usecase'; import { KeyServerUseCases } from '../keyserver/key-server.usecase'; import { CryptoService } from '../../externals/crypto/crypto.service'; import { LoginDto } from './dto/login-dto'; -import { - LoginAccessDto, - LoginAccessOpaqueStartDto, -} from './dto/login-access.dto'; +import { LoginAccessDto } from './dto/login-access.dto'; import { BadRequestException, ConflictException, @@ -26,7 +23,6 @@ import { Test } from '@nestjs/testing'; import { FeatureLimitService } from '../feature-limit/feature-limit.service'; import { PaymentRequiredException } from '../feature-limit/exceptions/payment-required.exception'; import { PlatformName } from '../../common/constants'; -import * as opaque from '@serenity-kit/opaque'; describe('AuthController', () => { let authController: AuthController; @@ -36,9 +32,6 @@ describe('AuthController', () => { let twoFactorAuthService: DeepMocked; let featureLimitService: DeepMocked; - beforeAll(async () => { - await opaque.ready; - }); beforeEach(async () => { const moduleRef = await Test.createTestingModule({ controllers: [AuthController], @@ -87,7 +80,6 @@ describe('AuthController', () => { hasEccKeys: true, sKey: 'encryptedText', tfa: true, - useOpaqueLogin: false, }); }); @@ -115,51 +107,6 @@ describe('AuthController', () => { }); }); - describe('POST /login-opaque', () => { - it('Should sucessfully finish 1st phase of the login', async () => { - const loginOpaqueDto = new LoginAccessOpaqueStartDto(); - loginOpaqueDto.email = 'USER_test@gmail.com'; - const password = v4(); - const { startLoginRequest } = opaque.client.startLogin({ - password, - }); - loginOpaqueDto.startLoginRequest = startLoginRequest; - const serverSetupMock = opaque.server.createSetup(); - jest - .spyOn(cryptoService['configService'], 'get') - .mockReturnValue(serverSetupMock); - const { clientRegistrationState, registrationRequest } = - opaque.client.startRegistration({ password }); - const { registrationResponse } = opaque.server.createRegistrationResponse( - { - serverSetup: serverSetupMock, - userIdentifier: loginOpaqueDto.email.toLowerCase(), - registrationRequest, - }, - ); - const { registrationRecord: registrationRecordMock } = - opaque.client.finishRegistration({ - clientRegistrationState, - registrationResponse, - password, - }); - jest.spyOn(userUseCases, 'findByEmail').mockResolvedValueOnce({ - registrationRecord: registrationRecordMock, - } as any); - - const result = await authController.loginOpaqueStart(loginOpaqueDto); - - expect(cryptoService.startLoginOpaque).toHaveBeenCalledTimes(1); - expect(cryptoService.startLoginOpaque).toHaveBeenCalledWith({ - userIdentifier: loginOpaqueDto.email.toLocaleLowerCase(), - registrationRecord: registrationRecordMock, - serverSetup: serverSetupMock, - startLoginRequest: loginOpaqueDto.startLoginRequest, - }); - expect(result).toEqual({ success: true }); - }); - }); - describe('POST /login/access', () => { const loginAccessDto = new LoginAccessDto(); loginAccessDto.email = 'user_test@gmail.com'; From 798df9df299af772aabe65d2c19673c9507803da Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 18 Dec 2025 10:00:36 +0100 Subject: [PATCH 05/15] mv eslintrc.js to .cjs --- .eslintrc.cjs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .eslintrc.cjs diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 000000000..8e9376f3a --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,31 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + root: true, + env: { + node: true, + jest: true, + }, + ignorePatterns: ['.eslintrc.cjs'], + rules: { + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/prefer-readonly': 'warn', + '@typescript-eslint/prefer-optional-chain': 'warn' + }, +}; From 9585695ea75dd5e489a05bcd4c111911d980c947 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 18 Dec 2025 10:25:59 +0100 Subject: [PATCH 06/15] fix unit tests --- src/config/{sequelize.js => sequelize.cjs} | 0 src/modules/auth/auth.controller.spec.ts | 1 + 2 files changed, 1 insertion(+) rename src/config/{sequelize.js => sequelize.cjs} (100%) diff --git a/src/config/sequelize.js b/src/config/sequelize.cjs similarity index 100% rename from src/config/sequelize.js rename to src/config/sequelize.cjs diff --git a/src/modules/auth/auth.controller.spec.ts b/src/modules/auth/auth.controller.spec.ts index 34e9966e5..c7e51adc9 100644 --- a/src/modules/auth/auth.controller.spec.ts +++ b/src/modules/auth/auth.controller.spec.ts @@ -80,6 +80,7 @@ describe('AuthController', () => { hasEccKeys: true, sKey: 'encryptedText', tfa: true, + useOpaqueLogin: false, }); }); From 20341a97ef22ed57531da89984eac35398379be6 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 18 Dec 2025 10:35:25 +0100 Subject: [PATCH 07/15] fix e2e tests, reduce code re-use --- .sequelizerc | 2 +- src/modules/user/user.usecase.ts | 33 ++++---------------------------- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/.sequelizerc b/.sequelizerc index 37072aa10..760b5f252 100644 --- a/.sequelizerc +++ b/.sequelizerc @@ -2,7 +2,7 @@ const path = require('path'); require('dotenv').config({ path: `./.env.${process.env.NODE_ENV}` }); module.exports = { - 'config': path.resolve('src/config', 'sequelize.js'), + 'config': path.resolve('src/config', 'sequelize.cjs'), 'models-path': '', 'seeders-path': '', } diff --git a/src/modules/user/user.usecase.ts b/src/modules/user/user.usecase.ts index aa5b4914d..cb5b1a5e8 100644 --- a/src/modules/user/user.usecase.ts +++ b/src/modules/user/user.usecase.ts @@ -918,42 +918,17 @@ export class UserUseCases { tokenExpirationTime: string | number = '3d', platform?: string, ): Promise<{ token: string; newToken: string }> { - const jti = v4(); - - const availableWorkspaces = - await this.workspaceRepository.findUserAvailableWorkspaces(user.uuid); - - const owners = [ - ...new Set(availableWorkspaces.map(({ workspace }) => workspace.ownerId)), - ]; - const token = SignEmail( user.email, this.configService.get('secrets.jwt'), tokenExpirationTime, customIat, ); - const newToken = Sign( - { - jti, - sub: user.uuid, - payload: { - uuid: user.uuid, - email: user.email, - name: user.name, - lastname: user.lastname, - username: user.username, - sharedWorkspace: true, - networkCredentials: { - user: user.bridgeUser, - }, - workspaces: { owners }, - ...(platform && { platform }), - }, - ...(customIat ? { iat: customIat } : null), - }, - this.configService.get('secrets.jwt'), + const newToken = await this.getNewToken( + user, tokenExpirationTime, + platform, + customIat, ); return { token, newToken }; From b55348f0a55796877085b23af7d17cb325e10862 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 18 Dec 2025 10:56:47 +0100 Subject: [PATCH 08/15] revert type module --- .eslintrc.cjs | 31 --------------------- .sequelizerc | 2 +- jest.setup.js | 6 +++-- package.json | 1 - src/config/sequelize.cjs | 58 ---------------------------------------- 5 files changed, 5 insertions(+), 93 deletions(-) delete mode 100644 .eslintrc.cjs delete mode 100644 src/config/sequelize.cjs diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 8e9376f3a..000000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - tsconfigRootDir: __dirname, - sourceType: 'module', - }, - plugins: ['@typescript-eslint/eslint-plugin'], - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', - ], - root: true, - env: { - node: true, - jest: true, - }, - ignorePatterns: ['.eslintrc.cjs'], - rules: { - '@typescript-eslint/no-unused-vars': [ - 'warn', - { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, - ], - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/prefer-readonly': 'warn', - '@typescript-eslint/prefer-optional-chain': 'warn' - }, -}; diff --git a/.sequelizerc b/.sequelizerc index 760b5f252..37072aa10 100644 --- a/.sequelizerc +++ b/.sequelizerc @@ -2,7 +2,7 @@ const path = require('path'); require('dotenv').config({ path: `./.env.${process.env.NODE_ENV}` }); module.exports = { - 'config': path.resolve('src/config', 'sequelize.cjs'), + 'config': path.resolve('src/config', 'sequelize.js'), 'models-path': '', 'seeders-path': '', } diff --git a/jest.setup.js b/jest.setup.js index 6062a2c33..de508b4de 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -8,11 +8,13 @@ if (typeof globalThis.BigInt === 'function') { } } -import { TextEncoder, TextDecoder } from 'util'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { TextEncoder, TextDecoder } = require('util'); global.TextEncoder = global.TextEncoder || TextEncoder; global.TextDecoder = global.TextDecoder || TextDecoder; -import * as opaque from '@serenity-kit/opaque'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const opaque = require('@serenity-kit/opaque'); beforeAll(async () => { await opaque.ready; diff --git a/package.json b/package.json index 542317af3..57b9527e9 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "description": "", "author": "Internxt", "license": "MIT", - "type": "module", "scripts": { "prebuild": "rimraf dist", "build": "nest build", diff --git a/src/config/sequelize.cjs b/src/config/sequelize.cjs deleted file mode 100644 index bf5cedc05..000000000 --- a/src/config/sequelize.cjs +++ /dev/null @@ -1,58 +0,0 @@ -module.exports = { - development: { - dialect: 'postgres', - host: process.env.RDS_HOSTNAME, - database: process.env.RDS_DBNAME, - username: process.env.RDS_USERNAME, - password: process.env.RDS_PASSWORD, - port: process.env.RDS_PORT, - logging: true, - pool: { - maxConnections: Number.MAX_SAFE_INTEGER, - maxIdleTime: 30000, - max: 20, - min: 0, - idle: 20000, - acquire: 20000, - }, - }, - test: { - dialect: 'postgres', - host: process.env.RDS_HOSTNAME, - database: process.env.RDS_DBNAME, - username: process.env.RDS_USERNAME, - password: process.env.RDS_PASSWORD, - port: process.env.RDS_PORT, - logging: true, - pool: { - maxConnections: Number.MAX_SAFE_INTEGER, - maxIdleTime: 30000, - max: 20, - min: 0, - idle: 20000, - acquire: 20000, - }, - }, - production: { - host: process.env.RDS_HOSTNAME, - database: process.env.RDS_DBNAME, - username: process.env.RDS_USERNAME, - password: process.env.RDS_PASSWORD, - port: process.env.RDS_PORT, - dialect: 'postgres', - pool: { - maxConnections: Number.MAX_SAFE_INTEGER, - maxIdleTime: 30000, - max: 20, - min: 0, - idle: 20000, - acquire: 20000, - }, - dialectOptions: { - ssl: { - require: true, - rejectUnauthorized: false, - }, - }, - }, -}; From 5a89baaad7c9d9d7cafb3f6da1759721de1bde10 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 18 Dec 2025 10:57:06 +0100 Subject: [PATCH 09/15] revert change to .cjs --- .eslintrc.js | 31 ++++++++++++++++++++++ src/config/sequelize.js | 58 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 .eslintrc.js create mode 100644 src/config/sequelize.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..e532e21f0 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,31 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + root: true, + env: { + node: true, + jest: true, + }, + ignorePatterns: ['.eslintrc.js'], + rules: { + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/prefer-readonly': 'warn', + '@typescript-eslint/prefer-optional-chain': 'warn' + }, +}; diff --git a/src/config/sequelize.js b/src/config/sequelize.js new file mode 100644 index 000000000..bf5cedc05 --- /dev/null +++ b/src/config/sequelize.js @@ -0,0 +1,58 @@ +module.exports = { + development: { + dialect: 'postgres', + host: process.env.RDS_HOSTNAME, + database: process.env.RDS_DBNAME, + username: process.env.RDS_USERNAME, + password: process.env.RDS_PASSWORD, + port: process.env.RDS_PORT, + logging: true, + pool: { + maxConnections: Number.MAX_SAFE_INTEGER, + maxIdleTime: 30000, + max: 20, + min: 0, + idle: 20000, + acquire: 20000, + }, + }, + test: { + dialect: 'postgres', + host: process.env.RDS_HOSTNAME, + database: process.env.RDS_DBNAME, + username: process.env.RDS_USERNAME, + password: process.env.RDS_PASSWORD, + port: process.env.RDS_PORT, + logging: true, + pool: { + maxConnections: Number.MAX_SAFE_INTEGER, + maxIdleTime: 30000, + max: 20, + min: 0, + idle: 20000, + acquire: 20000, + }, + }, + production: { + host: process.env.RDS_HOSTNAME, + database: process.env.RDS_DBNAME, + username: process.env.RDS_USERNAME, + password: process.env.RDS_PASSWORD, + port: process.env.RDS_PORT, + dialect: 'postgres', + pool: { + maxConnections: Number.MAX_SAFE_INTEGER, + maxIdleTime: 30000, + max: 20, + min: 0, + idle: 20000, + acquire: 20000, + }, + dialectOptions: { + ssl: { + require: true, + rejectUnauthorized: false, + }, + }, + }, +}; From e59bac37e3f175a993f881338e7fae18cc4627c6 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 18 Dec 2025 12:04:04 +0100 Subject: [PATCH 10/15] add more tests to fix coverage --- .../auth/auth.contoller.opaque.spec.ts | 70 ++++++++++++++----- src/modules/auth/auth.controller.ts | 2 +- .../cache-manager.service.spec.ts | 23 ++++++ 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/src/modules/auth/auth.contoller.opaque.spec.ts b/src/modules/auth/auth.contoller.opaque.spec.ts index e37f14674..d00c46196 100644 --- a/src/modules/auth/auth.contoller.opaque.spec.ts +++ b/src/modules/auth/auth.contoller.opaque.spec.ts @@ -2,7 +2,10 @@ import { AuthController } from './auth.controller'; import { UserUseCases } from '../user/user.usecase'; import { CryptoService } from '../../externals/crypto/crypto.service'; -import { LoginAccessOpaqueStartDto } from './dto/login-access.dto'; +import { + LoginAccessOpaqueFinishDto, + LoginAccessOpaqueStartDto, +} from './dto/login-access.dto'; import { Logger } from '@nestjs/common'; import { DeepMocked, createMock } from '@golevelup/ts-jest'; import { v4 } from 'uuid'; @@ -55,23 +58,18 @@ describe('AuthController', () => { }); describe('POST /login-opaque', () => { - it('Should sucessfully finish 1st phase of the login', async () => { - jest - .spyOn(cryptoService['configService'], 'get') - .mockReturnValue(serverSetupMock); - const loginOpaqueDto = new LoginAccessOpaqueStartDto(); - loginOpaqueDto.email = 'USER_test@gmail.com'; + it('Should sucessfully finish both phases of the login', async () => { + const email = 'USER_test@gmail.com'; const password = v4(); - const { startLoginRequest } = opaque.client.startLogin({ + const { startLoginRequest, clientLoginState } = opaque.client.startLogin({ password, }); - loginOpaqueDto.startLoginRequest = startLoginRequest; const { clientRegistrationState, registrationRequest } = opaque.client.startRegistration({ password }); const { registrationResponse } = opaque.server.createRegistrationResponse( { serverSetup: serverSetupMock, - userIdentifier: loginOpaqueDto.email.toLowerCase(), + userIdentifier: email.toLowerCase(), registrationRequest, }, ); @@ -81,12 +79,17 @@ describe('AuthController', () => { registrationResponse, password, }); - jest.spyOn(userUseCases, 'findByEmail').mockResolvedValueOnce({ + const loginOpaqueDto = new LoginAccessOpaqueStartDto(); + loginOpaqueDto.email = email; + loginOpaqueDto.startLoginRequest = startLoginRequest; + + jest.spyOn(userUseCases, 'findByEmail').mockResolvedValue({ registrationRecord: registrationRecordMock, } as any); const startLoginOpaqueSpy = jest.spyOn(cryptoService, 'startLoginOpaque'); - - const result = await authController.loginOpaqueStart(loginOpaqueDto); + const resultPhaseOne = + await authController.loginOpaqueStart(loginOpaqueDto); + const serverLoginStateValue = userUseCases.setLoginState.mock.calls[0][1]; expect(startLoginOpaqueSpy).toHaveBeenCalledTimes(1); expect(startLoginOpaqueSpy).toHaveBeenCalledWith( @@ -94,10 +97,45 @@ describe('AuthController', () => { registrationRecordMock, loginOpaqueDto.startLoginRequest, ); + expect(resultPhaseOne.loginResponse).toBeDefined(); + expect(resultPhaseOne).toEqual({ loginResponse: expect.any(String) }); + + const loginOpaqueFinishDto = new LoginAccessOpaqueFinishDto(); + loginOpaqueFinishDto.email = email; + + jest + .spyOn(userUseCases, 'getLoginState') + .mockResolvedValue(serverLoginStateValue); + + const { finishLoginRequest, sessionKey } = opaque.client.finishLogin({ + clientLoginState, + loginResponse: resultPhaseOne.loginResponse, + password, + }); + + loginOpaqueFinishDto.finishLoginRequest = finishLoginRequest; + + const finishLoginOpaqueSpy = jest.spyOn( + cryptoService, + 'finishLoginOpaque', + ); + + const resultPhaseTwo = + await authController.loginOpaqueFinish(loginOpaqueFinishDto); - expect(result.loginResponse).toBeDefined(); - expect(result).toEqual({ loginResponse: expect.any(String) }); - expect(typeof result.loginResponse).toBe('string'); + expect(finishLoginOpaqueSpy).toHaveBeenCalledTimes(1); + expect(finishLoginOpaqueSpy).toHaveBeenCalledWith( + finishLoginRequest, + serverLoginStateValue, + ); + + expect(resultPhaseTwo.user).toBeDefined(); + expect(resultPhaseTwo.token).toBeDefined(); + expect(resultPhaseTwo.sessionID).toBeDefined(); + expect(userUseCases.setSessionKey).toHaveBeenCalledWith( + resultPhaseTwo.sessionID, + sessionKey, + ); }); }); }); diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index f911923ca..0ce245eed 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -211,7 +211,7 @@ export class AuthController { type: LoginAccessResponseOpaqueFinishDto, }) @Public() - @WorkspaceLogAction(WorkspaceLogType.LoginOpaqueStart) + @WorkspaceLogAction(WorkspaceLogType.LoginOpaqueFinish) async loginOpaqueFinish( @Body() body: LoginAccessOpaqueFinishDto, ): Promise { diff --git a/src/modules/cache-manager/cache-manager.service.spec.ts b/src/modules/cache-manager/cache-manager.service.spec.ts index c2fc92df3..b457f215d 100644 --- a/src/modules/cache-manager/cache-manager.service.spec.ts +++ b/src/modules/cache-manager/cache-manager.service.spec.ts @@ -64,6 +64,29 @@ describe('CacheManagerService', () => { ); }); + it('Should sucessfully set and get session key and login state', async () => { + const id = v4(); + const value = 'test value'; + + await cacheManagerService.setSessionKey(id, value); + expect(cacheManager.set).toHaveBeenCalledWith( + `session:${id}`, + value, + 10000 * 60, + ); + await cacheManagerService.getSessionKey(id); + expect(cacheManager.get).toHaveBeenCalledWith(`session:${id}`); + + await cacheManagerService.setLoginState(id, value); + expect(cacheManager.set).toHaveBeenCalledWith( + `loginstate:${id}`, + value, + 10000 * 60, + ); + await cacheManagerService.getLoginState(id); + expect(cacheManager.get).toHaveBeenCalledWith(`loginstate:${id}`); + }); + it('When user usage is set, then it should return set value', async () => { const userUuid = v4(); const usage = 1024; From 67fc0570e564a0f4b58e7ce67de78d7cdc4d664b Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 18 Dec 2025 12:34:19 +0100 Subject: [PATCH 11/15] add usecase test for opaque --- src/modules/user/user.usecase.spec.ts | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/modules/user/user.usecase.spec.ts b/src/modules/user/user.usecase.spec.ts index 4e2fd4532..dec4ff911 100644 --- a/src/modules/user/user.usecase.spec.ts +++ b/src/modules/user/user.usecase.spec.ts @@ -1138,6 +1138,35 @@ describe('User use cases', () => { expect(result).toHaveProperty('newToken', 'newAuthToken'); }); + it('When opaque login is successful, then it should return user and token', async () => { + const email = 'test@example.com'; + + const user = newUser({ + attributes: { + email, + errorLoginCount: 0, + secret_2FA: null, + registrationRecord: 'test value', + }, + }); + const folder = newFolder({ owner: user, attributes: { bucket: v4() } }); + + jest.spyOn(userRepository, 'findByUsername').mockResolvedValue(user); + jest.spyOn(userUseCases, 'getNewToken').mockResolvedValueOnce('token'); + jest.spyOn(userUseCases, 'updateByUuid').mockResolvedValue(undefined); + jest + .spyOn(userUseCases, 'getOrCreateUserRootFolderAndBucket') + .mockResolvedValueOnce(folder); + jest.spyOn(keyServerRepository, 'findUserKeys').mockResolvedValue(null); + + const result = await userUseCases.loginAccessOpaque(email, ''); + + expect(result).toHaveProperty('user'); + expect(result.user).toHaveProperty('email', 'test@example.com'); + expect(result.user).toHaveProperty('bucket', folder.bucket); + expect(result).toHaveProperty('token', 'token'); + }); + it('When the 2FA code is wrong, then it should throw', async () => { const hashedPassword = v4(); const loginAccessDto = { From 41edba1e16dd0b98bf271bf46b5a29ab241e95e2 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 18 Dec 2025 17:27:45 +0100 Subject: [PATCH 12/15] return settings to jest, remove isOpaque from User --- jest.config.ts | 3 +++ src/modules/auth/auth.controller.ts | 5 ++++- src/modules/user/user.attributes.ts | 1 - src/modules/user/user.domain.ts | 4 ---- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index 3a252d364..0bb3618ff 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -14,6 +14,9 @@ const config: JestConfigWithTsJest = { }, ], }, + roots: ['src', 'test'], + collectCoverageFrom: ['**/*.(t|j)s'], + testRegex: '.*\\.spec\\.ts$', transformIgnorePatterns: ['node_modules/(?!@serenity-kit/opaque)'], setupFilesAfterEnv: ['/jest.setup.js'], }; diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 0ce245eed..cbaf2571a 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -93,6 +93,9 @@ export class AuthController { user.secret_2FA && user.secret_2FA.length > 0, ); const keys = await this.keyServerUseCases.findUserKeys(user.id); + const isOpaqueEnabled = Boolean( + user.registrationRecord && user.registrationRecord.length > 0, + ); return { hasKeys: !!keys.ecc, @@ -100,7 +103,7 @@ export class AuthController { tfa: required2FA, hasKyberKeys: !!keys.kyber, hasEccKeys: !!keys.ecc, - useOpaqueLogin: user.isOpaqueEnabled ?? false, + useOpaqueLogin: isOpaqueEnabled, }; } catch (err) { if (!(err instanceof HttpException)) { diff --git a/src/modules/user/user.attributes.ts b/src/modules/user/user.attributes.ts index fc039bbb9..832c73281 100644 --- a/src/modules/user/user.attributes.ts +++ b/src/modules/user/user.attributes.ts @@ -30,6 +30,5 @@ export interface UserAttributes { emailVerified: boolean; updatedAt?: Date; createdAt?: Date; - isOpaqueEnabled?: boolean; registrationRecord?: string; } diff --git a/src/modules/user/user.domain.ts b/src/modules/user/user.domain.ts index e7851bfa4..8907256b8 100644 --- a/src/modules/user/user.domain.ts +++ b/src/modules/user/user.domain.ts @@ -31,7 +31,6 @@ export class User implements UserAttributes { emailVerified: boolean; updatedAt: Date; createdAt: Date; - isOpaqueEnabled: boolean; registrationRecord?: string; constructor({ @@ -65,7 +64,6 @@ export class User implements UserAttributes { emailVerified, updatedAt, createdAt, - isOpaqueEnabled, registrationRecord, }: UserAttributes) { this.id = id; @@ -98,7 +96,6 @@ export class User implements UserAttributes { this.emailVerified = emailVerified; this.updatedAt = updatedAt; this.createdAt = createdAt; - this.isOpaqueEnabled = isOpaqueEnabled; this.registrationRecord = registrationRecord; } @@ -138,7 +135,6 @@ export class User implements UserAttributes { sharedWorkspace: this.sharedWorkspace, avatar: this.avatar, lastPasswordChangedAt: this.lastPasswordChangedAt, - isOpaqueEnabled: this.isOpaqueEnabled, }; } } From 849638524ed2bfecdb979ea9f583ddc3fc7b0335 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 18 Dec 2025 17:38:10 +0100 Subject: [PATCH 13/15] fix sonar complaint --- jest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.ts b/jest.config.ts index 0bb3618ff..601308a05 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -16,7 +16,7 @@ const config: JestConfigWithTsJest = { }, roots: ['src', 'test'], collectCoverageFrom: ['**/*.(t|j)s'], - testRegex: '.*\\.spec\\.ts$', + testRegex: String.raw`.*\.spec\.ts$`, transformIgnorePatterns: ['node_modules/(?!@serenity-kit/opaque)'], setupFilesAfterEnv: ['/jest.setup.js'], }; From 69a3e5a7c6f7fc5a989bcddda05372f6ac5d4763 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 8 Jan 2026 10:11:57 +0100 Subject: [PATCH 14/15] add bigger limit for registration record --- .env.template | 1 + migrations/20251216131653-add-registration-record.js | 4 ++-- src/externals/crypto/crypto.service.ts | 12 +++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.env.template b/.env.template index 38d306016..193c0fc3f 100644 --- a/.env.template +++ b/.env.template @@ -1,5 +1,6 @@ CRYPTO_SECRET=6KYQBP847D4ATSFA CRYPTO_SECRET2=8Q8VMUE3BJZV87GT +OPAQUE_SECRET=awirun08Dxx3yBpGdd0W2-j4Tl5ip02M5Uu7EVRhtqUzEEdW5EhlP1QC1z3UX8hB7cavoCyem4Kl0iCymdTsbk_tbiJu8-zzrWF3S1nQ2cGY5TkDXIatNKh5riaw7xINwkTOycgxvsIENsPn2W19OgAw2_Zih_1f4Px6ncj7-iw GATEWAY_SECRET=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT3dJQkFBSkJBTDlDTVRlZGEramdIcGJuTmtlSm51TlpnYzg5TGFvMGNQNkl6dlJrYTJ0MUVKbnh5ZTA1CndSWGZLMXFpbTFOMGU3cGhkd0RkRWYvNGJ1eFc5V2g1UWxzQ0F3RUFBUUpCQUpnRXljLzF2VDdGWFNyK3JpTWcKWFAxQ09LNTdaeCtCUFVyamZQTytHYSszWk1MRHhqaG44dGZmV1E4VUpKemJ5VkQ0Q0JqTmNra2xRN3phQ29BNwo1WWtDSVFEd0h2MXhVRkFVUkI2b3QwL0JMMWNxek5SNU80dFBMT0NjL2gyK0o4Y09WUUloQU12b0FrMm5IQWhSClpRNmhNZGFTdWtPVTE3MTYvRGxnNWNiSXNWYXh0bDN2QWlBTUdTT2YzL0lJODEyd0ZueFlPWEJrNGFrYTZwc2MKUkNDVkNHQ3JRZ25QZVFJZ2NTU2E2cFc0YzFFZTN5Qkl0RVNVZ0YxOTNKRDZsYWdUdDlxeXRHVkZ5UmNDSVFDYgp6dE85ampXcERmYTlnWTV2dVB4MFgyUkcxbjJQb0ZYVjVXT29RanNqbnc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= DRIVE_GATEWAY_PUBLIC_SECRET=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUE0bmVlL0Y5OC8wTjJhbFNKQ3JnMAp5bzJRRysydzR5SGk3ZXVDT3JYYUhENzFmN0NrWExWdHlxWTFRRWNFVnFuQm5QUnVpR1EvSklzWkJKVXhoQTlOCmdwdUpIbVY0aytnMEorRGcxeS9wd3k4L0lNM0FhTnNtWWp4c0NZQUZBSWhqUDZNZlBsVTNTYWdyekVRNklFU3MKeHBzT1JhRXd3WUZIWm42TU50b0FGbktMb3VlMVprUVpSSlVwNGZSSlMvcGNrdVNSNjZFcjFLMjhYckpieGE5egpCNG9SbFJMb0ExQ2cvTFN6ZEFQc2lVMzlSOWtlamxBKzFOMkxLVWFrNVJ3OWN5TkM0N3lHS3ZicSt2TTNYaUFmCk43Wk1teTdkY09aeXcyZW9idFFUVzVtTmR1WElWOHhWeWUzMkpyY3BuYWVqbDlUMG5TWGJEWE9OV2d6N0lWcmoKNFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t HOST_DRIVE_WEB=http://localhost:3000 diff --git a/migrations/20251216131653-add-registration-record.js b/migrations/20251216131653-add-registration-record.js index 1429a6914..11d2ebbdc 100644 --- a/migrations/20251216131653-add-registration-record.js +++ b/migrations/20251216131653-add-registration-record.js @@ -7,13 +7,13 @@ const newColumn = 'registration_record'; module.exports = { async up(queryInterface, Sequelize) { await queryInterface.addColumn(tableName, newColumn, { - type: Sequelize.STRING, + type: Sequelize.STRING(300), }); }, async down(queryInterface, Sequelize) { await queryInterface.removeColumn(tableName, newColumn, { - type: Sequelize.STRING, + type: Sequelize.STRING(300), }); }, }; diff --git a/src/externals/crypto/crypto.service.ts b/src/externals/crypto/crypto.service.ts index c51629525..5852f2d43 100644 --- a/src/externals/crypto/crypto.service.ts +++ b/src/externals/crypto/crypto.service.ts @@ -4,7 +4,7 @@ import { AesService } from './aes'; import CryptoJS from 'crypto-js'; import crypto from 'crypto'; import bcrypt from 'bcryptjs'; -import { server } from '@serenity-kit/opaque'; +import * as opaque from '@serenity-kit/opaque'; import { v4 as uuidv4 } from 'uuid'; export enum AsymmetricEncryptionAlgorithms { @@ -177,13 +177,19 @@ export class CryptoService { registrationRecord: string, startLoginRequest: string, ): { loginResponse: string; serverLoginState: string } { - const { loginResponse, serverLoginState } = server.startLogin({ + console.log('SERVER TMP TEST, serverSetup type:', typeof this.serverSetup); + console.log('SERVER TMP TEST, start login opaque:', this.serverSetup); + console.log('SERVER TMP TEST, login request:', startLoginRequest); + console.log('SERVER TMP TEST, registration record:', registrationRecord); + console.log('SERVER TMP TEST, email:', email); + const { loginResponse, serverLoginState } = opaque.server.startLogin({ userIdentifier: email, registrationRecord, serverSetup: this.serverSetup, startLoginRequest, }); + console.log('SERVER TMP TEST, done:', loginResponse); return { loginResponse, serverLoginState }; } @@ -191,7 +197,7 @@ export class CryptoService { finishLoginRequest: string, serverLoginState: string, ): { sessionKey: string } { - return server.finishLogin({ + return opaque.server.finishLogin({ finishLoginRequest, serverLoginState, }); From 16278253348a751bd5b875fd580d344a89d552a8 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 8 Jan 2026 10:13:08 +0100 Subject: [PATCH 15/15] remove logs --- src/externals/crypto/crypto.service.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/externals/crypto/crypto.service.ts b/src/externals/crypto/crypto.service.ts index 5852f2d43..b4d94e0b3 100644 --- a/src/externals/crypto/crypto.service.ts +++ b/src/externals/crypto/crypto.service.ts @@ -177,11 +177,6 @@ export class CryptoService { registrationRecord: string, startLoginRequest: string, ): { loginResponse: string; serverLoginState: string } { - console.log('SERVER TMP TEST, serverSetup type:', typeof this.serverSetup); - console.log('SERVER TMP TEST, start login opaque:', this.serverSetup); - console.log('SERVER TMP TEST, login request:', startLoginRequest); - console.log('SERVER TMP TEST, registration record:', registrationRecord); - console.log('SERVER TMP TEST, email:', email); const { loginResponse, serverLoginState } = opaque.server.startLogin({ userIdentifier: email, registrationRecord, @@ -189,7 +184,6 @@ export class CryptoService { startLoginRequest, }); - console.log('SERVER TMP TEST, done:', loginResponse); return { loginResponse, serverLoginState }; }