chore: generate mfa secret in backend (#2930) [skip e2e]
* chore: get mfa secret from backend * chore: remove unused code
This commit is contained in:
committed by
GitHub
parent
d6840ba41c
commit
2338449425
@@ -1,7 +1,6 @@
|
|||||||
export interface MfaServiceInterface {
|
export interface MfaServiceInterface {
|
||||||
isMfaActivated(): Promise<boolean>
|
isMfaActivated(): Promise<boolean>
|
||||||
generateMfaSecret(): Promise<string>
|
generateMfaSecret(): Promise<string>
|
||||||
getOtpToken(secret: string): Promise<string>
|
|
||||||
enableMfa(secret: string, otpToken: string): Promise<void>
|
enableMfa(secret: string, otpToken: string): Promise<void>
|
||||||
disableMfa(): Promise<void>
|
disableMfa(): Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1229,7 +1229,6 @@ export class Dependencies {
|
|||||||
this.factory.set(TYPES.MfaService, () => {
|
this.factory.set(TYPES.MfaService, () => {
|
||||||
return new MfaService(
|
return new MfaService(
|
||||||
this.get<SettingsService>(TYPES.SettingsService),
|
this.get<SettingsService>(TYPES.SettingsService),
|
||||||
this.get<PureCryptoInterface>(TYPES.Crypto),
|
|
||||||
this.get<FeaturesService>(TYPES.FeaturesService),
|
this.get<FeaturesService>(TYPES.FeaturesService),
|
||||||
this.get<ProtectionsClientInterface>(TYPES.ProtectionService),
|
this.get<ProtectionsClientInterface>(TYPES.ProtectionService),
|
||||||
this.get<EncryptionService>(TYPES.EncryptionService),
|
this.get<EncryptionService>(TYPES.EncryptionService),
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
|||||||
import { Paths } from './Paths'
|
import { Paths } from './Paths'
|
||||||
import { DiskStorageService } from '../Storage/DiskStorageService'
|
import { DiskStorageService } from '../Storage/DiskStorageService'
|
||||||
import { UuidString } from '../../Types/UuidString'
|
import { UuidString } from '../../Types/UuidString'
|
||||||
import { SettingsServerInterface } from '../Settings/SettingsServerInterface'
|
import { SettingsServerInterface, MfaSecretResponse } from '../Settings/SettingsServerInterface'
|
||||||
import { Strings } from '@Lib/Strings'
|
import { Strings } from '@Lib/Strings'
|
||||||
import { AnyFeatureDescription } from '@standardnotes/features'
|
import { AnyFeatureDescription } from '@standardnotes/features'
|
||||||
|
|
||||||
@@ -563,11 +563,13 @@ export class LegacyApiService
|
|||||||
settingName: string,
|
settingName: string,
|
||||||
settingValue: string | null,
|
settingValue: string | null,
|
||||||
sensitive: boolean,
|
sensitive: boolean,
|
||||||
|
totpToken?: string,
|
||||||
): Promise<HttpResponse<UpdateSettingResponse>> {
|
): Promise<HttpResponse<UpdateSettingResponse>> {
|
||||||
const params = {
|
const params = {
|
||||||
name: settingName,
|
name: settingName,
|
||||||
value: settingValue,
|
value: settingValue,
|
||||||
sensitive: sensitive,
|
sensitive: sensitive,
|
||||||
|
...(totpToken && { totpToken }),
|
||||||
}
|
}
|
||||||
return this.tokenRefreshableRequest<UpdateSettingResponse>({
|
return this.tokenRefreshableRequest<UpdateSettingResponse>({
|
||||||
verb: HttpVerb.Put,
|
verb: HttpVerb.Put,
|
||||||
@@ -637,6 +639,15 @@ export class LegacyApiService
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getMfaSecret(userUuid: UuidString): Promise<HttpResponse<MfaSecretResponse>> {
|
||||||
|
return this.tokenRefreshableRequest<MfaSecretResponse>({
|
||||||
|
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<HttpResponse> {
|
public downloadFeatureUrl(url: string): Promise<HttpResponse> {
|
||||||
return this.request({
|
return this.request({
|
||||||
verb: HttpVerb.Get,
|
verb: HttpVerb.Get,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const ItemsPaths = {
|
|||||||
const SettingsPaths = {
|
const SettingsPaths = {
|
||||||
settings: (userUuid: string) => `/v1/users/${userUuid}/settings`,
|
settings: (userUuid: string) => `/v1/users/${userUuid}/settings`,
|
||||||
setting: (userUuid: string, settingName: string) => `/v1/users/${userUuid}/settings/${settingName}`,
|
setting: (userUuid: string, settingName: string) => `/v1/users/${userUuid}/settings/${settingName}`,
|
||||||
|
mfaSecret: (userUuid: string) => `/v1/users/${userUuid}/mfa-secret`,
|
||||||
subscriptionSetting: (userUuid: string, settingName: string) =>
|
subscriptionSetting: (userUuid: string, settingName: string) =>
|
||||||
`/v1/users/${userUuid}/subscription-settings/${settingName}`,
|
`/v1/users/${userUuid}/subscription-settings/${settingName}`,
|
||||||
subscriptionSettings: (userUuid: string) => `/v1/users/${userUuid}/subscription-settings`,
|
subscriptionSettings: (userUuid: string) => `/v1/users/${userUuid}/subscription-settings`,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { SettingsService } from '../Settings'
|
import { SettingsService } from '../Settings'
|
||||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
|
||||||
import { FeaturesService } from '../Features/FeaturesService'
|
import { FeaturesService } from '../Features/FeaturesService'
|
||||||
import {
|
import {
|
||||||
AbstractService,
|
AbstractService,
|
||||||
@@ -7,7 +6,6 @@ import {
|
|||||||
MfaServiceInterface,
|
MfaServiceInterface,
|
||||||
ProtectionsClientInterface,
|
ProtectionsClientInterface,
|
||||||
EncryptionService,
|
EncryptionService,
|
||||||
SignInStrings,
|
|
||||||
ChallengeValidation,
|
ChallengeValidation,
|
||||||
} from '@standardnotes/services'
|
} from '@standardnotes/services'
|
||||||
import { SettingName } from '@standardnotes/domain-core'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
@@ -16,7 +14,6 @@ import { SNRootKeyParams } from '@standardnotes/encryption'
|
|||||||
export class MfaService extends AbstractService implements MfaServiceInterface {
|
export class MfaService extends AbstractService implements MfaServiceInterface {
|
||||||
constructor(
|
constructor(
|
||||||
private settingsService: SettingsService,
|
private settingsService: SettingsService,
|
||||||
private crypto: PureCryptoInterface,
|
|
||||||
private featuresService: FeaturesService,
|
private featuresService: FeaturesService,
|
||||||
private protections: ProtectionsClientInterface,
|
private protections: ProtectionsClientInterface,
|
||||||
private encryption: EncryptionService,
|
private encryption: EncryptionService,
|
||||||
@@ -25,14 +22,6 @@ export class MfaService extends AbstractService implements MfaServiceInterface {
|
|||||||
super(internalEventBus)
|
super(internalEventBus)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async saveMfaSetting(secret: string): Promise<void> {
|
|
||||||
return await this.settingsService.updateSetting(
|
|
||||||
SettingName.create(SettingName.NAMES.MfaSecret).getValue(),
|
|
||||||
secret,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async isMfaActivated(): Promise<boolean> {
|
async isMfaActivated(): Promise<boolean> {
|
||||||
const mfaSetting = await this.settingsService.getDoesSensitiveSettingExist(
|
const mfaSetting = await this.settingsService.getDoesSensitiveSettingExist(
|
||||||
SettingName.create(SettingName.NAMES.MfaSecret).getValue(),
|
SettingName.create(SettingName.NAMES.MfaSecret).getValue(),
|
||||||
@@ -41,21 +30,11 @@ export class MfaService extends AbstractService implements MfaServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async generateMfaSecret(): Promise<string> {
|
async generateMfaSecret(): Promise<string> {
|
||||||
return this.crypto.generateOtpSecret()
|
return this.settingsService.generateMfaSecret()
|
||||||
}
|
|
||||||
|
|
||||||
async getOtpToken(secret: string): Promise<string> {
|
|
||||||
return this.crypto.totpToken(secret, Date.now(), 6, 30)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async enableMfa(secret: string, otpToken: string): Promise<void> {
|
async enableMfa(secret: string, otpToken: string): Promise<void> {
|
||||||
const otpTokenValid = otpToken != undefined && otpToken === (await this.getOtpToken(secret))
|
return this.settingsService.updateMfaSetting(secret, otpToken)
|
||||||
|
|
||||||
if (!otpTokenValid) {
|
|
||||||
throw new Error(SignInStrings.IncorrectMfa)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.saveMfaSetting(secret)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async disableMfa(): Promise<void> {
|
async disableMfa(): Promise<void> {
|
||||||
@@ -80,7 +59,6 @@ export class MfaService extends AbstractService implements MfaServiceInterface {
|
|||||||
|
|
||||||
override deinit(): void {
|
override deinit(): void {
|
||||||
;(this.settingsService as unknown) = undefined
|
;(this.settingsService as unknown) = undefined
|
||||||
;(this.crypto as unknown) = undefined
|
|
||||||
;(this.featuresService as unknown) = undefined
|
;(this.featuresService as unknown) = undefined
|
||||||
super.deinit()
|
super.deinit()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ export class SettingsService extends AbstractService implements SettingsClientIn
|
|||||||
return this.provider.updateSubscriptionSetting(name, payload, sensitive)
|
return this.provider.updateSubscriptionSetting(name, payload, sensitive)
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSetting(name: SettingName, payload: string, sensitive = false) {
|
async updateSetting(name: SettingName, payload: string, sensitive = false, totpToken?: string) {
|
||||||
return this.provider.updateSetting(name, payload, sensitive)
|
return this.provider.updateSetting(name, payload, sensitive, totpToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDoesSensitiveSettingExist(name: SettingName) {
|
async getDoesSensitiveSettingExist(name: SettingName) {
|
||||||
@@ -54,6 +54,19 @@ export class SettingsService extends AbstractService implements SettingsClientIn
|
|||||||
return this.provider.deleteSetting(name, serverPassword)
|
return this.provider.deleteSetting(name, serverPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async generateMfaSecret(): Promise<string> {
|
||||||
|
return this.provider.getMfaSecret()
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateMfaSetting(secret: string, totpToken: string): Promise<void> {
|
||||||
|
return this.provider.updateSetting(
|
||||||
|
SettingName.create(SettingName.NAMES.MfaSecret).getValue(),
|
||||||
|
secret,
|
||||||
|
true,
|
||||||
|
totpToken,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
getEmailBackupFrequencyOptionLabel(frequency: EmailBackupFrequency): string {
|
getEmailBackupFrequencyOptionLabel(frequency: EmailBackupFrequency): string {
|
||||||
return this.frequencyOptionsLabels[frequency]
|
return this.frequencyOptionsLabels[frequency]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,13 @@ export interface SettingsClientInterface {
|
|||||||
|
|
||||||
getDoesSensitiveSettingExist(name: SettingName): Promise<boolean>
|
getDoesSensitiveSettingExist(name: SettingName): Promise<boolean>
|
||||||
|
|
||||||
updateSetting(name: SettingName, payload: string, sensitive?: boolean): Promise<void>
|
updateSetting(name: SettingName, payload: string, sensitive?: boolean, totpToken?: string): Promise<void>
|
||||||
|
|
||||||
deleteSetting(name: SettingName, serverPassword?: string): Promise<void>
|
deleteSetting(name: SettingName, serverPassword?: string): Promise<void>
|
||||||
|
|
||||||
|
generateMfaSecret(): Promise<string>
|
||||||
|
|
||||||
|
updateMfaSetting(secret: string, totpToken: string): Promise<void>
|
||||||
|
|
||||||
getEmailBackupFrequencyOptionLabel(frequency: EmailBackupFrequency): string
|
getEmailBackupFrequencyOptionLabel(frequency: EmailBackupFrequency): string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,8 +102,8 @@ export class SettingsGateway {
|
|||||||
return response.data?.success ?? false
|
return response.data?.success ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSetting(name: SettingName, payload: string, sensitive: boolean): Promise<void> {
|
async updateSetting(name: SettingName, payload: string, sensitive: boolean, totpToken?: string): Promise<void> {
|
||||||
const response = await this.settingsApi.updateSetting(this.userUuid, name.value, payload, sensitive)
|
const response = await this.settingsApi.updateSetting(this.userUuid, name.value, payload, sensitive, totpToken)
|
||||||
if (isErrorResponse(response)) {
|
if (isErrorResponse(response)) {
|
||||||
throw new Error(getErrorFromErrorResponse(response).message)
|
throw new Error(getErrorFromErrorResponse(response).message)
|
||||||
}
|
}
|
||||||
@@ -116,6 +116,14 @@ export class SettingsGateway {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getMfaSecret(): Promise<string> {
|
||||||
|
const response = await this.settingsApi.getMfaSecret(this.userUuid)
|
||||||
|
if (isErrorResponse(response)) {
|
||||||
|
throw new Error(getErrorFromErrorResponse(response).message)
|
||||||
|
}
|
||||||
|
return response.data.secret
|
||||||
|
}
|
||||||
|
|
||||||
deinit() {
|
deinit() {
|
||||||
;(this.settingsApi as unknown) = undefined
|
;(this.settingsApi as unknown) = undefined
|
||||||
;(this.userProvider as unknown) = undefined
|
;(this.userProvider as unknown) = undefined
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import {
|
|||||||
} from '@standardnotes/responses'
|
} from '@standardnotes/responses'
|
||||||
import { UuidString } from '@Lib/Types/UuidString'
|
import { UuidString } from '@Lib/Types/UuidString'
|
||||||
|
|
||||||
|
export interface MfaSecretResponse {
|
||||||
|
secret: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface SettingsServerInterface {
|
export interface SettingsServerInterface {
|
||||||
listSettings(userUuid: UuidString): Promise<HttpResponse<ListSettingsResponse>>
|
listSettings(userUuid: UuidString): Promise<HttpResponse<ListSettingsResponse>>
|
||||||
|
|
||||||
@@ -15,6 +19,7 @@ export interface SettingsServerInterface {
|
|||||||
settingName: string,
|
settingName: string,
|
||||||
settingValue: string,
|
settingValue: string,
|
||||||
sensitive: boolean,
|
sensitive: boolean,
|
||||||
|
totpToken?: string,
|
||||||
): Promise<HttpResponse<UpdateSettingResponse>>
|
): Promise<HttpResponse<UpdateSettingResponse>>
|
||||||
|
|
||||||
getSetting(
|
getSetting(
|
||||||
@@ -32,6 +37,8 @@ export interface SettingsServerInterface {
|
|||||||
sensitive: boolean,
|
sensitive: boolean,
|
||||||
): Promise<HttpResponse<UpdateSettingResponse>>
|
): Promise<HttpResponse<UpdateSettingResponse>>
|
||||||
|
|
||||||
|
getMfaSecret(userUuid: UuidString): Promise<HttpResponse<MfaSecretResponse>>
|
||||||
|
|
||||||
deleteSetting(
|
deleteSetting(
|
||||||
userUuid: UuidString,
|
userUuid: UuidString,
|
||||||
settingName: string,
|
settingName: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user