Files
aliasvault/apps/mobile-app/utils/SrpUtility.ts
2026-01-13 15:51:42 +01:00

145 lines
4.7 KiB
TypeScript

import type { LoginResponse, ValidateLoginRequest2Fa, ValidateLoginResponse, BadRequestResponse } from '@/utils/dist/core/models/webapi';
import { ApiAuthError } from '@/utils/types/errors/ApiAuthError';
import { WebApiService } from '@/utils/WebApiService';
import NativeVaultManager from '@/specs/NativeVaultManager';
/**
* Utility class for SRP login and validation operations.
* Uses native Rust SRP implementation via NativeVaultManager.
*/
export class SrpUtility {
private webApiService: WebApiService;
/**
* Constructor
*/
public constructor(webApiService: WebApiService) {
this.webApiService = webApiService;
}
/**
* Initiates the login process with the server
*/
public async initiateLogin(username: string): Promise<LoginResponse> {
const response = await this.webApiService.rawFetch('Auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username: username.toLowerCase().trim() }),
});
// Check if response is a bad request (400)
if (response.status === 400) {
const badRequestResponse = await response.json() as BadRequestResponse;
throw new ApiAuthError(badRequestResponse.title);
}
// For other responses, try to parse as LoginResponse
const loginResponse = await response.json() as LoginResponse;
return loginResponse;
}
/**
* Validates the login with the server using SRP protocol
*/
public async validateLogin(
username: string,
passwordHash: string,
rememberMe: boolean,
loginResponse: LoginResponse
): Promise<ValidateLoginResponse> {
/**
* Use srpIdentity from server response if available, otherwise fall back to username.
* Note: the fallback can be removed in the future after 0.26.0+ is deployed.
*/
const srpIdentity = loginResponse.srpIdentity ?? username;
const clientEphemeral = await NativeVaultManager.srpGenerateEphemeral();
const privateKey = await NativeVaultManager.srpDerivePrivateKey(loginResponse.salt, srpIdentity, passwordHash);
const sessionProof = await NativeVaultManager.srpDeriveSession(
clientEphemeral.secret,
loginResponse.serverEphemeral,
loginResponse.salt,
srpIdentity,
privateKey
);
const response = await this.webApiService.rawFetch('Auth/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: username.toLowerCase().trim(),
rememberMe,
clientPublicEphemeral: clientEphemeral.public,
clientSessionProof: sessionProof.proof,
}),
});
// Check if response is a bad request (400)
if (response.status === 400) {
const badRequestResponse = await response.json() as BadRequestResponse;
throw new ApiAuthError(badRequestResponse.title);
}
// For other responses, try to parse as ValidateLoginResponse
const validateResponse = await response.json() as ValidateLoginResponse;
return validateResponse;
}
/**
* Validates 2FA code with the server
*/
public async validateLogin2Fa(
username: string,
passwordHash: string,
rememberMe: boolean,
loginResponse: LoginResponse,
twoFactorCode: number
): Promise<ValidateLoginResponse> {
/**
* Use srpIdentity from server response if available, otherwise fall back to username.
* Note: the fallback can be removed in the future after 0.26.0+ is deployed.
*/
const srpIdentity = loginResponse.srpIdentity ?? username;
const clientEphemeral = await NativeVaultManager.srpGenerateEphemeral();
const privateKey = await NativeVaultManager.srpDerivePrivateKey(loginResponse.salt, srpIdentity, passwordHash);
const sessionProof = await NativeVaultManager.srpDeriveSession(
clientEphemeral.secret,
loginResponse.serverEphemeral,
loginResponse.salt,
srpIdentity,
privateKey
);
const model: ValidateLoginRequest2Fa = {
username: username.toLowerCase().trim(),
rememberMe,
clientPublicEphemeral: clientEphemeral.public,
clientSessionProof: sessionProof.proof,
code2Fa: twoFactorCode,
};
const response = await this.webApiService.rawFetch('Auth/validate-2fa', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(model),
});
// Check if response is a bad request (400)
if (response.status === 400) {
const badRequestResponse = await response.json() as BadRequestResponse;
throw new ApiAuthError(badRequestResponse.title);
}
// For other responses, try to parse as ValidateLoginResponse
const validateResponse = await response.json() as ValidateLoginResponse;
return validateResponse;
}
}