feat: authorize notes for listed (#1823)
This commit is contained in:
@@ -68,7 +68,7 @@ import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
|
||||
import { SnjsVersion } from './../Version'
|
||||
import { SNLog } from '../Log'
|
||||
import { Challenge, ChallengeResponse } from '../Services'
|
||||
import { Challenge, ChallengeResponse, ListedClientInterface } from '../Services'
|
||||
import { ApplicationConstructorOptions, FullyResolvedApplicationOptions } from './Options/ApplicationOptions'
|
||||
import { ApplicationOptionsDefaults } from './Options/Defaults'
|
||||
|
||||
@@ -86,9 +86,7 @@ type ApplicationObserver = {
|
||||
|
||||
type ObserverRemover = () => void
|
||||
|
||||
export class SNApplication
|
||||
implements ApplicationInterface, AppGroupManagedApplication, InternalServices.ListedClientInterface
|
||||
{
|
||||
export class SNApplication implements ApplicationInterface, AppGroupManagedApplication {
|
||||
onDeinit!: ExternalServices.DeinitCallback
|
||||
|
||||
/**
|
||||
@@ -273,6 +271,10 @@ export class SNApplication
|
||||
return this.componentManagerService
|
||||
}
|
||||
|
||||
public get listed(): ListedClientInterface {
|
||||
return this.listedService
|
||||
}
|
||||
|
||||
public computePrivateUsername(username: string): Promise<string | undefined> {
|
||||
return ComputePrivateUsername(this.options.crypto, username)
|
||||
}
|
||||
@@ -682,25 +684,6 @@ export class SNApplication
|
||||
return this.protectionService.authorizeSearchingProtectedNotesText()
|
||||
}
|
||||
|
||||
public canRegisterNewListedAccount(): boolean {
|
||||
return this.listedService.canRegisterNewListedAccount()
|
||||
}
|
||||
|
||||
public async requestNewListedAccount(): Promise<Responses.ListedAccount | undefined> {
|
||||
return this.listedService.requestNewListedAccount()
|
||||
}
|
||||
|
||||
public async getListedAccounts(): Promise<Responses.ListedAccount[]> {
|
||||
return this.listedService.getListedAccounts()
|
||||
}
|
||||
|
||||
public getListedAccountInfo(
|
||||
account: Responses.ListedAccount,
|
||||
inContextOfItem?: UuidString,
|
||||
): Promise<Responses.ListedAccountInfo | undefined> {
|
||||
return this.listedService.getListedAccountInfo(account, inContextOfItem)
|
||||
}
|
||||
|
||||
public async createEncryptedBackupFileForAutomatedDesktopBackups(): Promise<BackupFile | undefined> {
|
||||
return this.protocolService.createEncryptedBackupFile()
|
||||
}
|
||||
@@ -1096,11 +1079,11 @@ export class SNApplication
|
||||
this.createComponentManager()
|
||||
this.createMigrationService()
|
||||
this.createMfaService()
|
||||
this.createListedService()
|
||||
this.createActionsManager()
|
||||
this.createFileService()
|
||||
this.createIntegrityService()
|
||||
this.createMutatorService()
|
||||
this.createListedService()
|
||||
this.createActionsManager()
|
||||
this.createStatusService()
|
||||
|
||||
if (isDesktopDevice(this.deviceInterface)) {
|
||||
@@ -1175,6 +1158,8 @@ export class SNApplication
|
||||
this.itemManager,
|
||||
this.settingsService,
|
||||
this.deprecatedHttpService,
|
||||
this.protectionService,
|
||||
this.mutator,
|
||||
this.internalEventBus,
|
||||
)
|
||||
this.services.push(this.listedService)
|
||||
|
||||
@@ -167,6 +167,7 @@ export const ChallengeStrings = {
|
||||
SelectProtectedNote: 'Authentication is required to select a protected note',
|
||||
DisableMfa: 'Authentication is required to disable two-factor authentication',
|
||||
DeleteAccount: 'Authentication is required to delete your account',
|
||||
ListedAuthorization: 'Authentication is required to approve this note for Listed',
|
||||
}
|
||||
|
||||
export const ErrorAlertStrings = {
|
||||
|
||||
@@ -79,6 +79,8 @@ export class Challenge implements ChallengeInterface {
|
||||
return ChallengeStrings.DisableMfa
|
||||
case ChallengeReason.DeleteAccount:
|
||||
return ChallengeStrings.DeleteAccount
|
||||
case ChallengeReason.AuthorizeNoteForListed:
|
||||
return ChallengeStrings.ListedAuthorization
|
||||
case ChallengeReason.Custom:
|
||||
return ''
|
||||
default:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SNNote } from '@standardnotes/models'
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
import { ListedAccount, ListedAccountInfo } from '@standardnotes/responses'
|
||||
|
||||
@@ -6,4 +7,6 @@ export interface ListedClientInterface {
|
||||
requestNewListedAccount: () => Promise<ListedAccount | undefined>
|
||||
getListedAccounts(): Promise<ListedAccount[]>
|
||||
getListedAccountInfo(account: ListedAccount, inContextOfItem?: Uuid): Promise<ListedAccountInfo | undefined>
|
||||
isNoteAuthorizedForListed(note: SNNote): boolean
|
||||
authorizeNoteForListed(note: SNNote): Promise<boolean>
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ import { SNSettingsService } from '../Settings/SNSettingsService'
|
||||
import { ListedClientInterface } from './ListedClientInterface'
|
||||
import { SNApiService } from '../Api/ApiService'
|
||||
import { ListedAccount, ListedAccountInfo, ListedAccountInfoResponse } from '@standardnotes/responses'
|
||||
import { SNActionsExtension } from '@standardnotes/models'
|
||||
import { AbstractService, InternalEventBusInterface } from '@standardnotes/services'
|
||||
import { NoteMutator, SNActionsExtension, SNNote } from '@standardnotes/models'
|
||||
import { AbstractService, InternalEventBusInterface, MutatorClientInterface } from '@standardnotes/services'
|
||||
import { SNProtectionService } from '../Protection'
|
||||
|
||||
export class ListedService extends AbstractService implements ListedClientInterface {
|
||||
constructor(
|
||||
@@ -17,6 +18,8 @@ export class ListedService extends AbstractService implements ListedClientInterf
|
||||
private itemManager: ItemManager,
|
||||
private settingsService: SNSettingsService,
|
||||
private httpSerivce: SNHttpService,
|
||||
private protectionService: SNProtectionService,
|
||||
private mutatorService: MutatorClientInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
@@ -27,6 +30,8 @@ export class ListedService extends AbstractService implements ListedClientInterf
|
||||
;(this.settingsService as unknown) = undefined
|
||||
;(this.apiService as unknown) = undefined
|
||||
;(this.httpSerivce as unknown) = undefined
|
||||
;(this.protectionService as unknown) = undefined
|
||||
;(this.mutatorService as unknown) = undefined
|
||||
super.deinit()
|
||||
}
|
||||
|
||||
@@ -34,6 +39,23 @@ export class ListedService extends AbstractService implements ListedClientInterf
|
||||
return this.apiService.user != undefined
|
||||
}
|
||||
|
||||
public isNoteAuthorizedForListed(note: SNNote): boolean {
|
||||
return note.authorizedForListed
|
||||
}
|
||||
|
||||
public async authorizeNoteForListed(note: SNNote): Promise<boolean> {
|
||||
const result = await this.protectionService.authorizeListedPublishing()
|
||||
if (result === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
await this.mutatorService.changeAndSaveItem<NoteMutator>(note, (mutator) => {
|
||||
mutator.authorizedForListed = true
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Account creation is asyncronous on the backend due to message-based nature of architecture.
|
||||
* In order to get the newly created account, we poll the server to check for new accounts.
|
||||
@@ -73,8 +95,11 @@ export class ListedService extends AbstractService implements ListedClientInterf
|
||||
if (inContextOfItem) {
|
||||
url += `&item_uuid=${inContextOfItem}`
|
||||
}
|
||||
const response = (await this.httpSerivce.getAbsolute(url)) as ListedAccountInfoResponse
|
||||
if (response.error || !response.data || isString(response.data)) {
|
||||
|
||||
const response = (await this.httpSerivce.getAbsolute(url).catch((error) => {
|
||||
console.error(error)
|
||||
})) as ListedAccountInfoResponse
|
||||
if (!response || response.error || !response.data || isString(response.data)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -219,13 +219,18 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
|
||||
return this.authorizeAction(ChallengeReason.RevokeSession)
|
||||
}
|
||||
|
||||
async authorizeListedPublishing(): Promise<boolean> {
|
||||
return this.authorizeAction(ChallengeReason.AuthorizeNoteForListed, { forcePrompt: true })
|
||||
}
|
||||
|
||||
async authorizeAction(
|
||||
reason: ChallengeReason,
|
||||
{ fallBackToAccountPassword = true, requireAccountPassword = false } = {},
|
||||
{ fallBackToAccountPassword = true, requireAccountPassword = false, forcePrompt = false } = {},
|
||||
): Promise<boolean> {
|
||||
return this.validateOrRenewSession(reason, {
|
||||
requireAccountPassword,
|
||||
fallBackToAccountPassword,
|
||||
forcePrompt,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -295,9 +300,9 @@ export class SNProtectionService extends AbstractService<ProtectionEvent> implem
|
||||
|
||||
private async validateOrRenewSession(
|
||||
reason: ChallengeReason,
|
||||
{ fallBackToAccountPassword = true, requireAccountPassword = false } = {},
|
||||
{ fallBackToAccountPassword = true, requireAccountPassword = false, forcePrompt = false } = {},
|
||||
): Promise<boolean> {
|
||||
if (this.getSessionExpiryDate() > new Date()) {
|
||||
if (this.getSessionExpiryDate() > new Date() && !forcePrompt) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user