diff --git a/packages/services/src/Domain/Mfa/MfaServiceInterface.ts b/packages/services/src/Domain/Mfa/MfaServiceInterface.ts index a6822237686..b7887ac4805 100644 --- a/packages/services/src/Domain/Mfa/MfaServiceInterface.ts +++ b/packages/services/src/Domain/Mfa/MfaServiceInterface.ts @@ -1,7 +1,6 @@ export interface MfaServiceInterface { isMfaActivated(): Promise generateMfaSecret(): Promise - getOtpToken(secret: string): Promise enableMfa(secret: string, otpToken: string): Promise disableMfa(): Promise } diff --git a/packages/snjs/lib/Application/Dependencies/Dependencies.ts b/packages/snjs/lib/Application/Dependencies/Dependencies.ts index 69197f564e6..c07c3707120 100644 --- a/packages/snjs/lib/Application/Dependencies/Dependencies.ts +++ b/packages/snjs/lib/Application/Dependencies/Dependencies.ts @@ -1229,7 +1229,6 @@ export class Dependencies { this.factory.set(TYPES.MfaService, () => { return new MfaService( this.get(TYPES.SettingsService), - this.get(TYPES.Crypto), this.get(TYPES.FeaturesService), this.get(TYPES.ProtectionService), this.get(TYPES.EncryptionService), diff --git a/packages/snjs/lib/Services/Api/ApiService.ts b/packages/snjs/lib/Services/Api/ApiService.ts index 9cccbec7f0e..92347c6b041 100644 --- a/packages/snjs/lib/Services/Api/ApiService.ts +++ b/packages/snjs/lib/Services/Api/ApiService.ts @@ -77,7 +77,7 @@ import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { Paths } from './Paths' import { DiskStorageService } from '../Storage/DiskStorageService' import { UuidString } from '../../Types/UuidString' -import { SettingsServerInterface } from '../Settings/SettingsServerInterface' +import { SettingsServerInterface, MfaSecretResponse } from '../Settings/SettingsServerInterface' import { Strings } from '@Lib/Strings' import { AnyFeatureDescription } from '@standardnotes/features' @@ -563,11 +563,13 @@ export class LegacyApiService settingName: string, settingValue: string | null, sensitive: boolean, + totpToken?: string, ): Promise> { const params = { name: settingName, value: settingValue, sensitive: sensitive, + ...(totpToken && { totpToken }), } return this.tokenRefreshableRequest({ verb: HttpVerb.Put, @@ -637,6 +639,15 @@ export class LegacyApiService }) } + async getMfaSecret(userUuid: UuidString): Promise> { + return this.tokenRefreshableRequest({ + verb: HttpVerb.Get, + url: joinPaths(this.host, Paths.v1.mfaSecret(userUuid)), + authentication: this.getSessionAccessToken(), + fallbackErrorMessage: 'Failed to get MFA secret.', + }) + } + public downloadFeatureUrl(url: string): Promise { return this.request({ verb: HttpVerb.Get, diff --git a/packages/snjs/lib/Services/Api/Paths.ts b/packages/snjs/lib/Services/Api/Paths.ts index 6639ed8dcb8..5a337c81383 100644 --- a/packages/snjs/lib/Services/Api/Paths.ts +++ b/packages/snjs/lib/Services/Api/Paths.ts @@ -40,6 +40,7 @@ const ItemsPaths = { const SettingsPaths = { settings: (userUuid: string) => `/v1/users/${userUuid}/settings`, setting: (userUuid: string, settingName: string) => `/v1/users/${userUuid}/settings/${settingName}`, + mfaSecret: (userUuid: string) => `/v1/users/${userUuid}/mfa-secret`, subscriptionSetting: (userUuid: string, settingName: string) => `/v1/users/${userUuid}/subscription-settings/${settingName}`, subscriptionSettings: (userUuid: string) => `/v1/users/${userUuid}/subscription-settings`, diff --git a/packages/snjs/lib/Services/Mfa/MfaService.ts b/packages/snjs/lib/Services/Mfa/MfaService.ts index 5929aa60c13..78ee3b7fd2d 100644 --- a/packages/snjs/lib/Services/Mfa/MfaService.ts +++ b/packages/snjs/lib/Services/Mfa/MfaService.ts @@ -1,5 +1,4 @@ import { SettingsService } from '../Settings' -import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { FeaturesService } from '../Features/FeaturesService' import { AbstractService, @@ -7,7 +6,6 @@ import { MfaServiceInterface, ProtectionsClientInterface, EncryptionService, - SignInStrings, ChallengeValidation, } from '@standardnotes/services' import { SettingName } from '@standardnotes/domain-core' @@ -16,7 +14,6 @@ import { SNRootKeyParams } from '@standardnotes/encryption' export class MfaService extends AbstractService implements MfaServiceInterface { constructor( private settingsService: SettingsService, - private crypto: PureCryptoInterface, private featuresService: FeaturesService, private protections: ProtectionsClientInterface, private encryption: EncryptionService, @@ -25,14 +22,6 @@ export class MfaService extends AbstractService implements MfaServiceInterface { super(internalEventBus) } - private async saveMfaSetting(secret: string): Promise { - return await this.settingsService.updateSetting( - SettingName.create(SettingName.NAMES.MfaSecret).getValue(), - secret, - true, - ) - } - async isMfaActivated(): Promise { const mfaSetting = await this.settingsService.getDoesSensitiveSettingExist( SettingName.create(SettingName.NAMES.MfaSecret).getValue(), @@ -41,21 +30,11 @@ export class MfaService extends AbstractService implements MfaServiceInterface { } async generateMfaSecret(): Promise { - return this.crypto.generateOtpSecret() - } - - async getOtpToken(secret: string): Promise { - return this.crypto.totpToken(secret, Date.now(), 6, 30) + return this.settingsService.generateMfaSecret() } async enableMfa(secret: string, otpToken: string): Promise { - const otpTokenValid = otpToken != undefined && otpToken === (await this.getOtpToken(secret)) - - if (!otpTokenValid) { - throw new Error(SignInStrings.IncorrectMfa) - } - - return this.saveMfaSetting(secret) + return this.settingsService.updateMfaSetting(secret, otpToken) } async disableMfa(): Promise { @@ -80,7 +59,6 @@ export class MfaService extends AbstractService implements MfaServiceInterface { override deinit(): void { ;(this.settingsService as unknown) = undefined - ;(this.crypto as unknown) = undefined ;(this.featuresService as unknown) = undefined super.deinit() } diff --git a/packages/snjs/lib/Services/Settings/SNSettingsService.ts b/packages/snjs/lib/Services/Settings/SNSettingsService.ts index 4b403a832be..57cba1e5834 100644 --- a/packages/snjs/lib/Services/Settings/SNSettingsService.ts +++ b/packages/snjs/lib/Services/Settings/SNSettingsService.ts @@ -42,8 +42,8 @@ export class SettingsService extends AbstractService implements SettingsClientIn return this.provider.updateSubscriptionSetting(name, payload, sensitive) } - async updateSetting(name: SettingName, payload: string, sensitive = false) { - return this.provider.updateSetting(name, payload, sensitive) + async updateSetting(name: SettingName, payload: string, sensitive = false, totpToken?: string) { + return this.provider.updateSetting(name, payload, sensitive, totpToken) } async getDoesSensitiveSettingExist(name: SettingName) { @@ -54,6 +54,19 @@ export class SettingsService extends AbstractService implements SettingsClientIn return this.provider.deleteSetting(name, serverPassword) } + async generateMfaSecret(): Promise { + return this.provider.getMfaSecret() + } + + async updateMfaSetting(secret: string, totpToken: string): Promise { + return this.provider.updateSetting( + SettingName.create(SettingName.NAMES.MfaSecret).getValue(), + secret, + true, + totpToken, + ) + } + getEmailBackupFrequencyOptionLabel(frequency: EmailBackupFrequency): string { return this.frequencyOptionsLabels[frequency] } diff --git a/packages/snjs/lib/Services/Settings/SettingsClientInterface.ts b/packages/snjs/lib/Services/Settings/SettingsClientInterface.ts index 0e1373f366a..5a0c95d7bca 100644 --- a/packages/snjs/lib/Services/Settings/SettingsClientInterface.ts +++ b/packages/snjs/lib/Services/Settings/SettingsClientInterface.ts @@ -9,9 +9,13 @@ export interface SettingsClientInterface { getDoesSensitiveSettingExist(name: SettingName): Promise - updateSetting(name: SettingName, payload: string, sensitive?: boolean): Promise + updateSetting(name: SettingName, payload: string, sensitive?: boolean, totpToken?: string): Promise deleteSetting(name: SettingName, serverPassword?: string): Promise + generateMfaSecret(): Promise + + updateMfaSetting(secret: string, totpToken: string): Promise + getEmailBackupFrequencyOptionLabel(frequency: EmailBackupFrequency): string } diff --git a/packages/snjs/lib/Services/Settings/SettingsGateway.ts b/packages/snjs/lib/Services/Settings/SettingsGateway.ts index 8a7c5c57710..d45af926440 100644 --- a/packages/snjs/lib/Services/Settings/SettingsGateway.ts +++ b/packages/snjs/lib/Services/Settings/SettingsGateway.ts @@ -102,8 +102,8 @@ export class SettingsGateway { return response.data?.success ?? false } - async updateSetting(name: SettingName, payload: string, sensitive: boolean): Promise { - const response = await this.settingsApi.updateSetting(this.userUuid, name.value, payload, sensitive) + async updateSetting(name: SettingName, payload: string, sensitive: boolean, totpToken?: string): Promise { + const response = await this.settingsApi.updateSetting(this.userUuid, name.value, payload, sensitive, totpToken) if (isErrorResponse(response)) { throw new Error(getErrorFromErrorResponse(response).message) } @@ -116,6 +116,14 @@ export class SettingsGateway { } } + async getMfaSecret(): Promise { + const response = await this.settingsApi.getMfaSecret(this.userUuid) + if (isErrorResponse(response)) { + throw new Error(getErrorFromErrorResponse(response).message) + } + return response.data.secret + } + deinit() { ;(this.settingsApi as unknown) = undefined ;(this.userProvider as unknown) = undefined diff --git a/packages/snjs/lib/Services/Settings/SettingsServerInterface.ts b/packages/snjs/lib/Services/Settings/SettingsServerInterface.ts index 7e7af662ffe..f31d7ed3c75 100644 --- a/packages/snjs/lib/Services/Settings/SettingsServerInterface.ts +++ b/packages/snjs/lib/Services/Settings/SettingsServerInterface.ts @@ -7,6 +7,10 @@ import { } from '@standardnotes/responses' import { UuidString } from '@Lib/Types/UuidString' +export interface MfaSecretResponse { + secret: string +} + export interface SettingsServerInterface { listSettings(userUuid: UuidString): Promise> @@ -15,6 +19,7 @@ export interface SettingsServerInterface { settingName: string, settingValue: string, sensitive: boolean, + totpToken?: string, ): Promise> getSetting( @@ -32,6 +37,8 @@ export interface SettingsServerInterface { sensitive: boolean, ): Promise> + getMfaSecret(userUuid: UuidString): Promise> + deleteSetting( userUuid: UuidString, settingName: string,