chore: Add serverPassword param to endpoints (#2919) [skip e2e]
* chore: send server password param to delete account endpoint * chore: send server password param to disable mfa endpoint * chore: modify tests * chore: force challenge prompt for mfa disable * chore: fix eslint errors * chore: add server passsword to get recovery codes * chore: fix tests * chore: pass server password as header
This commit is contained in:
committed by
GitHub
parent
cf4d2196de
commit
54af28aa04
@@ -578,12 +578,18 @@ export class LegacyApiService
|
||||
})
|
||||
}
|
||||
|
||||
async getSetting(userUuid: UuidString, settingName: string): Promise<HttpResponse<GetSettingResponse>> {
|
||||
async getSetting(
|
||||
userUuid: UuidString,
|
||||
settingName: string,
|
||||
serverPassword?: string,
|
||||
): Promise<HttpResponse<GetSettingResponse>> {
|
||||
const customHeaders = serverPassword ? [{ key: 'x-server-password', value: serverPassword }] : undefined
|
||||
return await this.tokenRefreshableRequest<GetSettingResponse>({
|
||||
verb: HttpVerb.Get,
|
||||
url: joinPaths(this.host, Paths.v1.setting(userUuid, settingName.toLowerCase())),
|
||||
authentication: this.getSessionAccessToken(),
|
||||
fallbackErrorMessage: API_MESSAGE_FAILED_GET_SETTINGS,
|
||||
customHeaders,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -616,12 +622,18 @@ export class LegacyApiService
|
||||
})
|
||||
}
|
||||
|
||||
async deleteSetting(userUuid: UuidString, settingName: string): Promise<HttpResponse<DeleteSettingResponse>> {
|
||||
async deleteSetting(
|
||||
userUuid: UuidString,
|
||||
settingName: string,
|
||||
serverPassword?: string,
|
||||
): Promise<HttpResponse<DeleteSettingResponse>> {
|
||||
const customHeaders = serverPassword ? [{ key: 'x-server-password', value: serverPassword }] : undefined
|
||||
return this.tokenRefreshableRequest<DeleteSettingResponse>({
|
||||
verb: HttpVerb.Delete,
|
||||
url: joinPaths(this.host, Paths.v1.setting(userUuid, settingName)),
|
||||
authentication: this.getSessionAccessToken(),
|
||||
fallbackErrorMessage: API_MESSAGE_FAILED_UPDATE_SETTINGS,
|
||||
customHeaders,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,12 @@ import {
|
||||
InternalEventBusInterface,
|
||||
MfaServiceInterface,
|
||||
ProtectionsClientInterface,
|
||||
EncryptionService,
|
||||
SignInStrings,
|
||||
ChallengeValidation,
|
||||
} from '@standardnotes/services'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { SNRootKeyParams } from '@standardnotes/encryption'
|
||||
|
||||
export class MfaService extends AbstractService implements MfaServiceInterface {
|
||||
constructor(
|
||||
@@ -16,6 +19,7 @@ export class MfaService extends AbstractService implements MfaServiceInterface {
|
||||
private crypto: PureCryptoInterface,
|
||||
private featuresService: FeaturesService,
|
||||
private protections: ProtectionsClientInterface,
|
||||
private encryption: EncryptionService,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
@@ -55,11 +59,23 @@ export class MfaService extends AbstractService implements MfaServiceInterface {
|
||||
}
|
||||
|
||||
async disableMfa(): Promise<void> {
|
||||
if (!(await this.protections.authorizeMfaDisable())) {
|
||||
const { success, challengeResponse } = await this.protections.authorizeMfaDisable()
|
||||
|
||||
if (!success) {
|
||||
return
|
||||
}
|
||||
|
||||
return await this.settingsService.deleteSetting(SettingName.create(SettingName.NAMES.MfaSecret).getValue())
|
||||
const password = challengeResponse?.getValueForType(ChallengeValidation.AccountPassword).value as string
|
||||
const currentRootKey = await this.encryption.computeRootKey(
|
||||
password,
|
||||
this.encryption.getRootKeyParams() as SNRootKeyParams,
|
||||
)
|
||||
const serverPassword = currentRootKey.serverPassword
|
||||
|
||||
return await this.settingsService.deleteSetting(
|
||||
SettingName.create(SettingName.NAMES.MfaSecret).getValue(),
|
||||
serverPassword,
|
||||
)
|
||||
}
|
||||
|
||||
override deinit(): void {
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { isValidProtectionSessionLength } from './isValidProtectionSessionLength'
|
||||
import { UnprotectedAccessSecondsDuration } from './UnprotectedAccessSecondsDuration'
|
||||
import { ChallengeResponse } from '../Challenge'
|
||||
|
||||
/**
|
||||
* Enforces certain actions to require extra authentication,
|
||||
@@ -246,11 +247,11 @@ export class ProtectionService
|
||||
})
|
||||
}
|
||||
|
||||
async authorizeMfaDisable(): Promise<boolean> {
|
||||
return this.authorizeAction(ChallengeReason.DisableMfa, {
|
||||
async authorizeMfaDisable(): Promise<{ success: boolean; challengeResponse?: ChallengeResponse }> {
|
||||
return this.authorizeActionWithChallengeResponse(ChallengeReason.DisableMfa, {
|
||||
fallBackToAccountPassword: true,
|
||||
requireAccountPassword: true,
|
||||
forcePrompt: false,
|
||||
forcePrompt: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -278,6 +279,14 @@ export class ProtectionService
|
||||
})
|
||||
}
|
||||
|
||||
async authorizeAccountDeletion(): Promise<{ success: boolean; challengeResponse?: ChallengeResponse }> {
|
||||
return this.authorizeActionWithChallengeResponse(ChallengeReason.DeleteAccount, {
|
||||
fallBackToAccountPassword: true,
|
||||
requireAccountPassword: true,
|
||||
forcePrompt: true,
|
||||
})
|
||||
}
|
||||
|
||||
async authorizeAction(
|
||||
reason: ChallengeReason,
|
||||
dto: { fallBackToAccountPassword: boolean; requireAccountPassword: boolean; forcePrompt: boolean },
|
||||
@@ -285,6 +294,13 @@ export class ProtectionService
|
||||
return this.validateOrRenewSession(reason, dto)
|
||||
}
|
||||
|
||||
async authorizeActionWithChallengeResponse(
|
||||
reason: ChallengeReason,
|
||||
dto: { fallBackToAccountPassword: boolean; requireAccountPassword: boolean; forcePrompt: boolean },
|
||||
): Promise<{ success: boolean; challengeResponse?: ChallengeResponse }> {
|
||||
return this.validateOrRenewSessionWithChallengeResponse(reason, dto)
|
||||
}
|
||||
|
||||
getMobilePasscodeTimingOptions(): TimingDisplayOption[] {
|
||||
return [
|
||||
{
|
||||
@@ -353,8 +369,20 @@ export class ProtectionService
|
||||
reason: ChallengeReason,
|
||||
{ fallBackToAccountPassword = true, requireAccountPassword = false, forcePrompt = false } = {},
|
||||
): Promise<boolean> {
|
||||
const response = await this.validateOrRenewSessionWithChallengeResponse(reason, {
|
||||
fallBackToAccountPassword,
|
||||
requireAccountPassword,
|
||||
forcePrompt,
|
||||
})
|
||||
return response.success
|
||||
}
|
||||
|
||||
private async validateOrRenewSessionWithChallengeResponse(
|
||||
reason: ChallengeReason,
|
||||
{ fallBackToAccountPassword = true, requireAccountPassword = false, forcePrompt = false } = {},
|
||||
): Promise<{ success: boolean; challengeResponse?: ChallengeResponse }> {
|
||||
if (this.getSessionExpiryDate() > new Date() && !forcePrompt) {
|
||||
return true
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
const prompts: ChallengePrompt[] = []
|
||||
@@ -378,9 +406,10 @@ export class ProtectionService
|
||||
if (fallBackToAccountPassword && this.encryption.hasAccount()) {
|
||||
prompts.push(new ChallengePrompt(ChallengeValidation.AccountPassword))
|
||||
} else {
|
||||
return true
|
||||
return { success: true }
|
||||
}
|
||||
}
|
||||
|
||||
const lastSessionLength = this.getLastSessionLength()
|
||||
const chosenSessionLength = isValidProtectionSessionLength(lastSessionLength)
|
||||
? lastSessionLength
|
||||
@@ -407,9 +436,9 @@ export class ProtectionService
|
||||
} else {
|
||||
this.setSessionLength(length as UnprotectedAccessSecondsDuration)
|
||||
}
|
||||
return true
|
||||
return { success: true, challengeResponse: response }
|
||||
} else {
|
||||
return false
|
||||
return { success: false }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ export class SettingsService extends AbstractService implements SettingsClientIn
|
||||
return this.provider.listSettings()
|
||||
}
|
||||
|
||||
async getSetting(name: SettingName) {
|
||||
return this.provider.getSetting(name)
|
||||
async getSetting(name: SettingName, serverPassword?: string) {
|
||||
return this.provider.getSetting(name, serverPassword)
|
||||
}
|
||||
|
||||
async getSubscriptionSetting(name: SettingName) {
|
||||
@@ -50,8 +50,8 @@ export class SettingsService extends AbstractService implements SettingsClientIn
|
||||
return this.provider.getDoesSensitiveSettingExist(name)
|
||||
}
|
||||
|
||||
async deleteSetting(name: SettingName) {
|
||||
return this.provider.deleteSetting(name)
|
||||
async deleteSetting(name: SettingName, serverPassword?: string) {
|
||||
return this.provider.deleteSetting(name, serverPassword)
|
||||
}
|
||||
|
||||
getEmailBackupFrequencyOptionLabel(frequency: EmailBackupFrequency): string {
|
||||
|
||||
@@ -5,13 +5,13 @@ import { SettingName } from '@standardnotes/domain-core'
|
||||
export interface SettingsClientInterface {
|
||||
listSettings(): Promise<SettingsList>
|
||||
|
||||
getSetting(name: SettingName): Promise<string | undefined>
|
||||
getSetting(name: SettingName, serverPassword?: string): Promise<string | undefined>
|
||||
|
||||
getDoesSensitiveSettingExist(name: SettingName): Promise<boolean>
|
||||
|
||||
updateSetting(name: SettingName, payload: string, sensitive?: boolean): Promise<void>
|
||||
|
||||
deleteSetting(name: SettingName): Promise<void>
|
||||
deleteSetting(name: SettingName, serverPassword?: string): Promise<void>
|
||||
|
||||
getEmailBackupFrequencyOptionLabel(frequency: EmailBackupFrequency): string
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ export class SettingsGateway {
|
||||
return settings
|
||||
}
|
||||
|
||||
async getSetting(name: SettingName): Promise<string | undefined> {
|
||||
const response = await this.settingsApi.getSetting(this.userUuid, name.value)
|
||||
async getSetting(name: SettingName, serverPassword?: string): Promise<string | undefined> {
|
||||
const response = await this.settingsApi.getSetting(this.userUuid, name.value, serverPassword)
|
||||
|
||||
if (response.status === HttpStatusCode.BadRequest) {
|
||||
return undefined
|
||||
@@ -109,8 +109,8 @@ export class SettingsGateway {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSetting(name: SettingName): Promise<void> {
|
||||
const response = await this.settingsApi.deleteSetting(this.userUuid, name.value)
|
||||
async deleteSetting(name: SettingName, serverPassword?: string): Promise<void> {
|
||||
const response = await this.settingsApi.deleteSetting(this.userUuid, name.value, serverPassword)
|
||||
if (isErrorResponse(response)) {
|
||||
throw new Error(getErrorFromErrorResponse(response).message)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ export interface SettingsServerInterface {
|
||||
sensitive: boolean,
|
||||
): Promise<HttpResponse<UpdateSettingResponse>>
|
||||
|
||||
getSetting(userUuid: UuidString, settingName: string): Promise<HttpResponse<GetSettingResponse>>
|
||||
getSetting(
|
||||
userUuid: UuidString,
|
||||
settingName: string,
|
||||
serverPassword?: string,
|
||||
): Promise<HttpResponse<GetSettingResponse>>
|
||||
|
||||
getSubscriptionSetting(userUuid: UuidString, settingName: string): Promise<HttpResponse<GetSettingResponse>>
|
||||
|
||||
@@ -28,5 +32,9 @@ export interface SettingsServerInterface {
|
||||
sensitive: boolean,
|
||||
): Promise<HttpResponse<UpdateSettingResponse>>
|
||||
|
||||
deleteSetting(userUuid: UuidString, settingName: string): Promise<HttpResponse<DeleteSettingResponse>>
|
||||
deleteSetting(
|
||||
userUuid: UuidString,
|
||||
settingName: string,
|
||||
serverPassword?: string,
|
||||
): Promise<HttpResponse<DeleteSettingResponse>>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user