diff --git a/packages/api/src/Domain/Client/Authenticator/AuthenticatorApiOperations.ts b/packages/api/src/Domain/Client/Authenticator/AuthenticatorApiOperations.ts new file mode 100644 index 000000000..d4ee9fe94 --- /dev/null +++ b/packages/api/src/Domain/Client/Authenticator/AuthenticatorApiOperations.ts @@ -0,0 +1,8 @@ +export enum AuthenticatorApiOperations { + List, + Delete, + GenerateRegistrationOptions, + GenerateAuthenticationOptions, + VerifyRegistrationResponse, + VerifyAuthenticationResponse, +} diff --git a/packages/api/src/Domain/Client/Authenticator/AuthenticatorApiService.ts b/packages/api/src/Domain/Client/Authenticator/AuthenticatorApiService.ts new file mode 100644 index 000000000..b6de0da15 --- /dev/null +++ b/packages/api/src/Domain/Client/Authenticator/AuthenticatorApiService.ts @@ -0,0 +1,152 @@ +import { ErrorMessage } from '../../Error/ErrorMessage' +import { ApiCallError } from '../../Error/ApiCallError' + +import { AuthenticatorApiServiceInterface } from './AuthenticatorApiServiceInterface' +import { AuthenticatorApiOperations } from './AuthenticatorApiOperations' +import { + ListAuthenticatorsResponse, + DeleteAuthenticatorResponse, + GenerateAuthenticatorRegistrationOptionsResponse, + VerifyAuthenticatorRegistrationResponseResponse, + GenerateAuthenticatorAuthenticationOptionsResponse, + VerifyAuthenticatorAuthenticationResponseResponse, +} from '../../Response' +import { AuthenticatorServerInterface } from '../../Server/Authenticator/AuthenticatorServerInterface' + +export class AuthenticatorApiService implements AuthenticatorApiServiceInterface { + private operationsInProgress: Map + + constructor(private authenticatorServer: AuthenticatorServerInterface) { + this.operationsInProgress = new Map() + } + + async list(): Promise { + if (this.operationsInProgress.get(AuthenticatorApiOperations.List)) { + throw new ApiCallError(ErrorMessage.GenericInProgress) + } + + this.operationsInProgress.set(AuthenticatorApiOperations.List, true) + + try { + const response = await this.authenticatorServer.list({}) + + return response + } catch (error) { + throw new ApiCallError(ErrorMessage.GenericFail) + } finally { + this.operationsInProgress.set(AuthenticatorApiOperations.List, false) + } + } + + async delete(authenticatorId: string): Promise { + if (this.operationsInProgress.get(AuthenticatorApiOperations.Delete)) { + throw new ApiCallError(ErrorMessage.GenericInProgress) + } + + this.operationsInProgress.set(AuthenticatorApiOperations.Delete, true) + + try { + const response = await this.authenticatorServer.delete({ + authenticatorId, + }) + + return response + } catch (error) { + throw new ApiCallError(ErrorMessage.GenericFail) + } finally { + this.operationsInProgress.set(AuthenticatorApiOperations.Delete, false) + } + } + + async generateRegistrationOptions( + userUuid: string, + username: string, + ): Promise { + if (this.operationsInProgress.get(AuthenticatorApiOperations.GenerateRegistrationOptions)) { + throw new ApiCallError(ErrorMessage.GenericInProgress) + } + + this.operationsInProgress.set(AuthenticatorApiOperations.GenerateRegistrationOptions, true) + + try { + const response = await this.authenticatorServer.generateRegistrationOptions({ + username, + userUuid, + }) + + return response + } catch (error) { + throw new ApiCallError(ErrorMessage.GenericFail) + } finally { + this.operationsInProgress.set(AuthenticatorApiOperations.GenerateRegistrationOptions, false) + } + } + + async verifyRegistrationResponse( + userUuid: string, + name: string, + registrationCredential: Record, + ): Promise { + if (this.operationsInProgress.get(AuthenticatorApiOperations.VerifyRegistrationResponse)) { + throw new ApiCallError(ErrorMessage.GenericInProgress) + } + + this.operationsInProgress.set(AuthenticatorApiOperations.VerifyRegistrationResponse, true) + + try { + const response = await this.authenticatorServer.verifyRegistrationResponse({ + userUuid, + name, + registrationCredential, + }) + + return response + } catch (error) { + throw new ApiCallError(ErrorMessage.GenericFail) + } finally { + this.operationsInProgress.set(AuthenticatorApiOperations.VerifyRegistrationResponse, false) + } + } + + async generateAuthenticationOptions(): Promise { + if (this.operationsInProgress.get(AuthenticatorApiOperations.GenerateAuthenticationOptions)) { + throw new ApiCallError(ErrorMessage.GenericInProgress) + } + + this.operationsInProgress.set(AuthenticatorApiOperations.GenerateAuthenticationOptions, true) + + try { + const response = await this.authenticatorServer.generateAuthenticationOptions({}) + + return response + } catch (error) { + throw new ApiCallError(ErrorMessage.GenericFail) + } finally { + this.operationsInProgress.set(AuthenticatorApiOperations.GenerateAuthenticationOptions, false) + } + } + + async verifyAuthenticationResponse( + userUuid: string, + authenticationCredential: Record, + ): Promise { + if (this.operationsInProgress.get(AuthenticatorApiOperations.VerifyAuthenticationResponse)) { + throw new ApiCallError(ErrorMessage.GenericInProgress) + } + + this.operationsInProgress.set(AuthenticatorApiOperations.VerifyAuthenticationResponse, true) + + try { + const response = await this.authenticatorServer.verifyAuthenticationResponse({ + authenticationCredential, + userUuid, + }) + + return response + } catch (error) { + throw new ApiCallError(ErrorMessage.GenericFail) + } finally { + this.operationsInProgress.set(AuthenticatorApiOperations.VerifyAuthenticationResponse, false) + } + } +} diff --git a/packages/api/src/Domain/Client/Authenticator/AuthenticatorApiServiceInterface.ts b/packages/api/src/Domain/Client/Authenticator/AuthenticatorApiServiceInterface.ts new file mode 100644 index 000000000..5619a8d42 --- /dev/null +++ b/packages/api/src/Domain/Client/Authenticator/AuthenticatorApiServiceInterface.ts @@ -0,0 +1,27 @@ +import { + ListAuthenticatorsResponse, + DeleteAuthenticatorResponse, + GenerateAuthenticatorRegistrationOptionsResponse, + VerifyAuthenticatorRegistrationResponseResponse, + GenerateAuthenticatorAuthenticationOptionsResponse, + VerifyAuthenticatorAuthenticationResponseResponse, +} from '../../Response' + +export interface AuthenticatorApiServiceInterface { + list(): Promise + delete(authenticatorId: string): Promise + generateRegistrationOptions( + userUuid: string, + username: string, + ): Promise + verifyRegistrationResponse( + userUuid: string, + name: string, + registrationCredential: Record, + ): Promise + generateAuthenticationOptions(): Promise + verifyAuthenticationResponse( + userUuid: string, + authenticationCredential: Record, + ): Promise +} diff --git a/packages/api/src/Domain/Client/index.ts b/packages/api/src/Domain/Client/index.ts index f31a0bf5b..8c97ba6c5 100644 --- a/packages/api/src/Domain/Client/index.ts +++ b/packages/api/src/Domain/Client/index.ts @@ -1,3 +1,6 @@ +export * from './Authenticator/AuthenticatorApiOperations' +export * from './Authenticator/AuthenticatorApiService' +export * from './Authenticator/AuthenticatorApiServiceInterface' export * from './Subscription/SubscriptionApiOperations' export * from './Subscription/SubscriptionApiService' export * from './Subscription/SubscriptionApiServiceInterface' diff --git a/packages/api/src/Domain/Request/Authenticator/DeleteAuthenticatorRequestParams.ts b/packages/api/src/Domain/Request/Authenticator/DeleteAuthenticatorRequestParams.ts new file mode 100644 index 000000000..e95e4df77 --- /dev/null +++ b/packages/api/src/Domain/Request/Authenticator/DeleteAuthenticatorRequestParams.ts @@ -0,0 +1,4 @@ +export interface DeleteAuthenticatorRequestParams { + authenticatorId: string + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/Authenticator/GenerateAuthenticatorAuthenticationOptionsRequestParams.ts b/packages/api/src/Domain/Request/Authenticator/GenerateAuthenticatorAuthenticationOptionsRequestParams.ts new file mode 100644 index 000000000..f5d5f4f26 --- /dev/null +++ b/packages/api/src/Domain/Request/Authenticator/GenerateAuthenticatorAuthenticationOptionsRequestParams.ts @@ -0,0 +1,3 @@ +export interface GenerateAuthenticatorAuthenticationOptionsRequestParams { + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/Authenticator/GenerateAuthenticatorRegistrationOptionsRequestParams.ts b/packages/api/src/Domain/Request/Authenticator/GenerateAuthenticatorRegistrationOptionsRequestParams.ts new file mode 100644 index 000000000..80ecd9465 --- /dev/null +++ b/packages/api/src/Domain/Request/Authenticator/GenerateAuthenticatorRegistrationOptionsRequestParams.ts @@ -0,0 +1,5 @@ +export interface GenerateAuthenticatorRegistrationOptionsRequestParams { + userUuid: string + username: string + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/Authenticator/ListAuthenticatorsRequestParams.ts b/packages/api/src/Domain/Request/Authenticator/ListAuthenticatorsRequestParams.ts new file mode 100644 index 000000000..90f6cb162 --- /dev/null +++ b/packages/api/src/Domain/Request/Authenticator/ListAuthenticatorsRequestParams.ts @@ -0,0 +1,3 @@ +export interface ListAuthenticatorsRequestParams { + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/Authenticator/VerifyAuthenticatorAuthenticationResponseRequestParams.ts b/packages/api/src/Domain/Request/Authenticator/VerifyAuthenticatorAuthenticationResponseRequestParams.ts new file mode 100644 index 000000000..e335b6680 --- /dev/null +++ b/packages/api/src/Domain/Request/Authenticator/VerifyAuthenticatorAuthenticationResponseRequestParams.ts @@ -0,0 +1,5 @@ +export interface VerifyAuthenticatorAuthenticationResponseRequestParams { + userUuid: string + authenticationCredential: Record + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/Authenticator/VerifyAuthenticatorRegistrationResponseRequestParams.ts b/packages/api/src/Domain/Request/Authenticator/VerifyAuthenticatorRegistrationResponseRequestParams.ts new file mode 100644 index 000000000..2448a8228 --- /dev/null +++ b/packages/api/src/Domain/Request/Authenticator/VerifyAuthenticatorRegistrationResponseRequestParams.ts @@ -0,0 +1,6 @@ +export interface VerifyAuthenticatorRegistrationResponseRequestParams { + userUuid: string + name: string + registrationCredential: Record + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/index.ts b/packages/api/src/Domain/Request/index.ts index cc5c47220..4875e1c19 100644 --- a/packages/api/src/Domain/Request/index.ts +++ b/packages/api/src/Domain/Request/index.ts @@ -1,4 +1,10 @@ export * from './ApiEndpointParam' +export * from './Authenticator/DeleteAuthenticatorRequestParams' +export * from './Authenticator/GenerateAuthenticatorAuthenticationOptionsRequestParams' +export * from './Authenticator/GenerateAuthenticatorRegistrationOptionsRequestParams' +export * from './Authenticator/ListAuthenticatorsRequestParams' +export * from './Authenticator/VerifyAuthenticatorAuthenticationResponseRequestParams' +export * from './Authenticator/VerifyAuthenticatorRegistrationResponseRequestParams' export * from './Subscription/AppleIAPConfirmRequestParams' export * from './Subscription/SubscriptionInviteAcceptRequestParams' export * from './Subscription/SubscriptionInviteCancelRequestParams' diff --git a/packages/api/src/Domain/Response/Authenticator/DeleteAuthenticatorResponse.ts b/packages/api/src/Domain/Response/Authenticator/DeleteAuthenticatorResponse.ts new file mode 100644 index 000000000..1e0b0591d --- /dev/null +++ b/packages/api/src/Domain/Response/Authenticator/DeleteAuthenticatorResponse.ts @@ -0,0 +1,10 @@ +import { Either } from '@standardnotes/common' + +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' + +import { DeleteAuthenticatorResponseBody } from './DeleteAuthenticatorResponseBody' + +export interface DeleteAuthenticatorResponse extends HttpResponse { + data: Either +} diff --git a/packages/api/src/Domain/Response/Authenticator/DeleteAuthenticatorResponseBody.ts b/packages/api/src/Domain/Response/Authenticator/DeleteAuthenticatorResponseBody.ts new file mode 100644 index 000000000..148baf369 --- /dev/null +++ b/packages/api/src/Domain/Response/Authenticator/DeleteAuthenticatorResponseBody.ts @@ -0,0 +1,3 @@ +export interface DeleteAuthenticatorResponseBody { + message: string +} diff --git a/packages/api/src/Domain/Response/Authenticator/GenerateAuthenticatorAuthenticationOptionsResponse.ts b/packages/api/src/Domain/Response/Authenticator/GenerateAuthenticatorAuthenticationOptionsResponse.ts new file mode 100644 index 000000000..6f55af926 --- /dev/null +++ b/packages/api/src/Domain/Response/Authenticator/GenerateAuthenticatorAuthenticationOptionsResponse.ts @@ -0,0 +1,10 @@ +import { Either } from '@standardnotes/common' + +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' + +import { GenerateAuthenticatorAuthenticationOptionsResponseBody } from './GenerateAuthenticatorAuthenticationOptionsResponseBody' + +export interface GenerateAuthenticatorAuthenticationOptionsResponse extends HttpResponse { + data: Either +} diff --git a/packages/api/src/Domain/Response/Authenticator/GenerateAuthenticatorAuthenticationOptionsResponseBody.ts b/packages/api/src/Domain/Response/Authenticator/GenerateAuthenticatorAuthenticationOptionsResponseBody.ts new file mode 100644 index 000000000..a1d18d1a2 --- /dev/null +++ b/packages/api/src/Domain/Response/Authenticator/GenerateAuthenticatorAuthenticationOptionsResponseBody.ts @@ -0,0 +1,3 @@ +export interface GenerateAuthenticatorAuthenticationOptionsResponseBody { + options: Record +} diff --git a/packages/api/src/Domain/Response/Authenticator/GenerateAuthenticatorRegistrationOptionsResponse.ts b/packages/api/src/Domain/Response/Authenticator/GenerateAuthenticatorRegistrationOptionsResponse.ts new file mode 100644 index 000000000..1b63c6609 --- /dev/null +++ b/packages/api/src/Domain/Response/Authenticator/GenerateAuthenticatorRegistrationOptionsResponse.ts @@ -0,0 +1,10 @@ +import { Either } from '@standardnotes/common' + +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' + +import { GenerateAuthenticatorRegistrationOptionsResponseBody } from './GenerateAuthenticatorRegistrationOptionsResponseBody' + +export interface GenerateAuthenticatorRegistrationOptionsResponse extends HttpResponse { + data: Either +} diff --git a/packages/api/src/Domain/Response/Authenticator/GenerateAuthenticatorRegistrationOptionsResponseBody.ts b/packages/api/src/Domain/Response/Authenticator/GenerateAuthenticatorRegistrationOptionsResponseBody.ts new file mode 100644 index 000000000..ecec6dce0 --- /dev/null +++ b/packages/api/src/Domain/Response/Authenticator/GenerateAuthenticatorRegistrationOptionsResponseBody.ts @@ -0,0 +1,3 @@ +export interface GenerateAuthenticatorRegistrationOptionsResponseBody { + options: Record +} diff --git a/packages/api/src/Domain/Response/Authenticator/ListAuthenticatorsResponse.ts b/packages/api/src/Domain/Response/Authenticator/ListAuthenticatorsResponse.ts new file mode 100644 index 000000000..3b7264777 --- /dev/null +++ b/packages/api/src/Domain/Response/Authenticator/ListAuthenticatorsResponse.ts @@ -0,0 +1,10 @@ +import { Either } from '@standardnotes/common' + +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' + +import { ListAuthenticatorsResponseBody } from './ListAuthenticatorsResponseBody' + +export interface ListAuthenticatorsResponse extends HttpResponse { + data: Either +} diff --git a/packages/api/src/Domain/Response/Authenticator/ListAuthenticatorsResponseBody.ts b/packages/api/src/Domain/Response/Authenticator/ListAuthenticatorsResponseBody.ts new file mode 100644 index 000000000..a6c4f1451 --- /dev/null +++ b/packages/api/src/Domain/Response/Authenticator/ListAuthenticatorsResponseBody.ts @@ -0,0 +1,6 @@ +export interface ListAuthenticatorsResponseBody { + authenticators: Array<{ + id: string + name: string + }> +} diff --git a/packages/api/src/Domain/Response/Authenticator/VerifyAuthenticatorAuthenticationResponseResponse.ts b/packages/api/src/Domain/Response/Authenticator/VerifyAuthenticatorAuthenticationResponseResponse.ts new file mode 100644 index 000000000..ada9af679 --- /dev/null +++ b/packages/api/src/Domain/Response/Authenticator/VerifyAuthenticatorAuthenticationResponseResponse.ts @@ -0,0 +1,10 @@ +import { Either } from '@standardnotes/common' + +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' + +import { VerifyAuthenticatorAuthenticationResponseResponseBody } from './VerifyAuthenticatorAuthenticationResponseResponseBody' + +export interface VerifyAuthenticatorAuthenticationResponseResponse extends HttpResponse { + data: Either +} diff --git a/packages/api/src/Domain/Response/Authenticator/VerifyAuthenticatorAuthenticationResponseResponseBody.ts b/packages/api/src/Domain/Response/Authenticator/VerifyAuthenticatorAuthenticationResponseResponseBody.ts new file mode 100644 index 000000000..382d196f4 --- /dev/null +++ b/packages/api/src/Domain/Response/Authenticator/VerifyAuthenticatorAuthenticationResponseResponseBody.ts @@ -0,0 +1,3 @@ +export interface VerifyAuthenticatorAuthenticationResponseResponseBody { + success: boolean +} diff --git a/packages/api/src/Domain/Response/Authenticator/VerifyAuthenticatorRegistrationResponseResponse.ts b/packages/api/src/Domain/Response/Authenticator/VerifyAuthenticatorRegistrationResponseResponse.ts new file mode 100644 index 000000000..1f7033e25 --- /dev/null +++ b/packages/api/src/Domain/Response/Authenticator/VerifyAuthenticatorRegistrationResponseResponse.ts @@ -0,0 +1,10 @@ +import { Either } from '@standardnotes/common' + +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' + +import { VerifyAuthenticatorRegistrationResponseResponseBody } from './VerifyAuthenticatorRegistrationResponseResponseBody' + +export interface VerifyAuthenticatorRegistrationResponseResponse extends HttpResponse { + data: Either +} diff --git a/packages/api/src/Domain/Response/Authenticator/VerifyAuthenticatorRegistrationResponseResponseBody.ts b/packages/api/src/Domain/Response/Authenticator/VerifyAuthenticatorRegistrationResponseResponseBody.ts new file mode 100644 index 000000000..faf06a8b5 --- /dev/null +++ b/packages/api/src/Domain/Response/Authenticator/VerifyAuthenticatorRegistrationResponseResponseBody.ts @@ -0,0 +1,3 @@ +export interface VerifyAuthenticatorRegistrationResponseResponseBody { + success: boolean +} diff --git a/packages/api/src/Domain/Response/index.ts b/packages/api/src/Domain/Response/index.ts index 96d61dc6f..5d8effcb7 100644 --- a/packages/api/src/Domain/Response/index.ts +++ b/packages/api/src/Domain/Response/index.ts @@ -1,3 +1,17 @@ +export * from './Auth/SessionRefreshResponse' +export * from './Auth/SessionRefreshResponseBody' +export * from './Authenticator/DeleteAuthenticatorResponse' +export * from './Authenticator/DeleteAuthenticatorResponseBody' +export * from './Authenticator/GenerateAuthenticatorAuthenticationOptionsResponse' +export * from './Authenticator/GenerateAuthenticatorAuthenticationOptionsResponseBody' +export * from './Authenticator/GenerateAuthenticatorRegistrationOptionsResponse' +export * from './Authenticator/GenerateAuthenticatorRegistrationOptionsResponseBody' +export * from './Authenticator/ListAuthenticatorsResponse' +export * from './Authenticator/ListAuthenticatorsResponseBody' +export * from './Authenticator/VerifyAuthenticatorAuthenticationResponseResponse' +export * from './Authenticator/VerifyAuthenticatorAuthenticationResponseResponseBody' +export * from './Authenticator/VerifyAuthenticatorRegistrationResponseResponse' +export * from './Authenticator/VerifyAuthenticatorRegistrationResponseResponseBody' export * from './Subscription/AppleIAPConfirmResponse' export * from './Subscription/AppleIAPConfirmResponseBody' export * from './Subscription/SubscriptionInviteAcceptResponse' diff --git a/packages/api/src/Domain/Server/Authenticator/AuthenticatorServer.ts b/packages/api/src/Domain/Server/Authenticator/AuthenticatorServer.ts new file mode 100644 index 000000000..f2511ba14 --- /dev/null +++ b/packages/api/src/Domain/Server/Authenticator/AuthenticatorServer.ts @@ -0,0 +1,67 @@ +import { HttpServiceInterface } from '../../Http/HttpServiceInterface' +import { + ListAuthenticatorsRequestParams, + DeleteAuthenticatorRequestParams, + GenerateAuthenticatorRegistrationOptionsRequestParams, + VerifyAuthenticatorRegistrationResponseRequestParams, + GenerateAuthenticatorAuthenticationOptionsRequestParams, + VerifyAuthenticatorAuthenticationResponseRequestParams, +} from '../../Request' +import { + ListAuthenticatorsResponse, + DeleteAuthenticatorResponse, + GenerateAuthenticatorRegistrationOptionsResponse, + VerifyAuthenticatorRegistrationResponseResponse, + GenerateAuthenticatorAuthenticationOptionsResponse, + VerifyAuthenticatorAuthenticationResponseResponse, +} from '../../Response' +import { AuthenticatorServerInterface } from './AuthenticatorServerInterface' +import { Paths } from './Paths' + +export class AuthenticatorServer implements AuthenticatorServerInterface { + constructor(private httpService: HttpServiceInterface) {} + + async list(params: ListAuthenticatorsRequestParams): Promise { + const response = await this.httpService.get(Paths.v1.listAuthenticators, params) + + return response as ListAuthenticatorsResponse + } + + async delete(params: DeleteAuthenticatorRequestParams): Promise { + const response = await this.httpService.delete(Paths.v1.deleteAuthenticator(params.authenticatorId), params) + + return response as DeleteAuthenticatorResponse + } + + async generateRegistrationOptions( + params: GenerateAuthenticatorRegistrationOptionsRequestParams, + ): Promise { + const response = await this.httpService.get(Paths.v1.generateRegistrationOptions, params) + + return response as GenerateAuthenticatorRegistrationOptionsResponse + } + + async verifyRegistrationResponse( + params: VerifyAuthenticatorRegistrationResponseRequestParams, + ): Promise { + const response = await this.httpService.post(Paths.v1.verifyRegistrationResponse, params) + + return response as VerifyAuthenticatorRegistrationResponseResponse + } + + async generateAuthenticationOptions( + params: GenerateAuthenticatorAuthenticationOptionsRequestParams, + ): Promise { + const response = await this.httpService.get(Paths.v1.generateAuthenticationOptions, params) + + return response as GenerateAuthenticatorAuthenticationOptionsResponse + } + + async verifyAuthenticationResponse( + params: VerifyAuthenticatorAuthenticationResponseRequestParams, + ): Promise { + const response = await this.httpService.post(Paths.v1.verifyAuthenticationResponse, params) + + return response as VerifyAuthenticatorAuthenticationResponseResponse + } +} diff --git a/packages/api/src/Domain/Server/Authenticator/AuthenticatorServerInterface.ts b/packages/api/src/Domain/Server/Authenticator/AuthenticatorServerInterface.ts new file mode 100644 index 000000000..c43704241 --- /dev/null +++ b/packages/api/src/Domain/Server/Authenticator/AuthenticatorServerInterface.ts @@ -0,0 +1,33 @@ +import { + ListAuthenticatorsRequestParams, + DeleteAuthenticatorRequestParams, + GenerateAuthenticatorRegistrationOptionsRequestParams, + VerifyAuthenticatorRegistrationResponseRequestParams, + GenerateAuthenticatorAuthenticationOptionsRequestParams, + VerifyAuthenticatorAuthenticationResponseRequestParams, +} from '../../Request' +import { + ListAuthenticatorsResponse, + DeleteAuthenticatorResponse, + GenerateAuthenticatorRegistrationOptionsResponse, + VerifyAuthenticatorRegistrationResponseResponse, + GenerateAuthenticatorAuthenticationOptionsResponse, + VerifyAuthenticatorAuthenticationResponseResponse, +} from '../../Response' + +export interface AuthenticatorServerInterface { + list(params: ListAuthenticatorsRequestParams): Promise + delete(params: DeleteAuthenticatorRequestParams): Promise + generateRegistrationOptions( + params: GenerateAuthenticatorRegistrationOptionsRequestParams, + ): Promise + verifyRegistrationResponse( + params: VerifyAuthenticatorRegistrationResponseRequestParams, + ): Promise + generateAuthenticationOptions( + params: GenerateAuthenticatorAuthenticationOptionsRequestParams, + ): Promise + verifyAuthenticationResponse( + params: VerifyAuthenticatorAuthenticationResponseRequestParams, + ): Promise +} diff --git a/packages/api/src/Domain/Server/Authenticator/Paths.ts b/packages/api/src/Domain/Server/Authenticator/Paths.ts new file mode 100644 index 000000000..9ec186b99 --- /dev/null +++ b/packages/api/src/Domain/Server/Authenticator/Paths.ts @@ -0,0 +1,14 @@ +const AuthenticatorPaths = { + listAuthenticators: '/v1/authenticators', + deleteAuthenticator: (authenticatorId: string) => `/v1/authenticators/${authenticatorId}`, + generateRegistrationOptions: '/v1/authenticators/generate-registration-options', + verifyRegistrationResponse: '/v1/authenticators/verify-registration', + generateAuthenticationOptions: '/v1/authenticators/generate-authentication-options', + verifyAuthenticationResponse: '/v1/authenticators/verify-authentication', +} + +export const Paths = { + v1: { + ...AuthenticatorPaths, + }, +} diff --git a/packages/api/src/Domain/Server/index.ts b/packages/api/src/Domain/Server/index.ts index 390d4805e..4572136b0 100644 --- a/packages/api/src/Domain/Server/index.ts +++ b/packages/api/src/Domain/Server/index.ts @@ -1,3 +1,5 @@ +export * from './Authenticator/AuthenticatorServer' +export * from './Authenticator/AuthenticatorServerInterface' export * from './Subscription/SubscriptionServer' export * from './Subscription/SubscriptionServerInterface' export * from './User/UserServer' diff --git a/packages/services/src/Domain/Authenticator/AuthenticatorClientInterface.ts b/packages/services/src/Domain/Authenticator/AuthenticatorClientInterface.ts new file mode 100644 index 000000000..aa16f7b6a --- /dev/null +++ b/packages/services/src/Domain/Authenticator/AuthenticatorClientInterface.ts @@ -0,0 +1,12 @@ +export interface AuthenticatorClientInterface { + list(): Promise> + delete(authenticatorId: string): Promise + generateRegistrationOptions(userUuid: string, username: string): Promise | null> + verifyRegistrationResponse( + userUuid: string, + name: string, + registrationCredential: Record, + ): Promise + generateAuthenticationOptions(): Promise | null> + verifyAuthenticationResponse(userUuid: string, authenticationCredential: Record): Promise +} diff --git a/packages/services/src/Domain/Authenticator/AuthenticatorManager.ts b/packages/services/src/Domain/Authenticator/AuthenticatorManager.ts new file mode 100644 index 000000000..201a3c4a2 --- /dev/null +++ b/packages/services/src/Domain/Authenticator/AuthenticatorManager.ts @@ -0,0 +1,111 @@ +/* istanbul ignore file */ + +import { AuthenticatorApiServiceInterface } from '@standardnotes/api' + +import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' +import { AbstractService } from '../Service/AbstractService' +import { AuthenticatorClientInterface } from './AuthenticatorClientInterface' + +export class AuthenticatorManager extends AbstractService implements AuthenticatorClientInterface { + constructor( + private authenticatorApiService: AuthenticatorApiServiceInterface, + protected override internalEventBus: InternalEventBusInterface, + ) { + super(internalEventBus) + } + + async list(): Promise<{ id: string; name: string }[]> { + try { + const result = await this.authenticatorApiService.list() + + if (result.data.error) { + return [] + } + + return result.data.authenticators + } catch (error) { + return [] + } + } + + async delete(authenticatorId: string): Promise { + try { + const result = await this.authenticatorApiService.delete(authenticatorId) + + if (result.data.error) { + return false + } + + return true + } catch (error) { + return false + } + } + + async generateRegistrationOptions(userUuid: string, username: string): Promise | null> { + try { + const result = await this.authenticatorApiService.generateRegistrationOptions(userUuid, username) + + if (result.data.error) { + return null + } + + return result.data.options + } catch (error) { + return null + } + } + + async verifyRegistrationResponse( + userUuid: string, + name: string, + registrationCredential: Record, + ): Promise { + try { + const result = await this.authenticatorApiService.verifyRegistrationResponse( + userUuid, + name, + registrationCredential, + ) + + if (result.data.error) { + return false + } + + return result.data.success + } catch (error) { + return false + } + } + + async generateAuthenticationOptions(): Promise | null> { + try { + const result = await this.authenticatorApiService.generateAuthenticationOptions() + + if (result.data.error) { + return null + } + + return result.data.options + } catch (error) { + return null + } + } + + async verifyAuthenticationResponse( + userUuid: string, + authenticationCredential: Record, + ): Promise { + try { + const result = await this.authenticatorApiService.verifyAuthenticationResponse(userUuid, authenticationCredential) + + if (result.data.error) { + return false + } + + return result.data.success + } catch (error) { + return false + } + } +} diff --git a/packages/services/src/Domain/index.ts b/packages/services/src/Domain/index.ts index baefb8d05..cb1ffdda7 100644 --- a/packages/services/src/Domain/index.ts +++ b/packages/services/src/Domain/index.ts @@ -6,6 +6,8 @@ export * from './Application/ApplicationStage' export * from './Application/DeinitCallback' export * from './Application/DeinitSource' export * from './Application/DeinitMode' +export * from './Authenticator/AuthenticatorClientInterface' +export * from './Authenticator/AuthenticatorManager' export * from './User/UserClientInterface' export * from './Application/WebApplicationInterface' export * from './Backups/BackupService' diff --git a/packages/snjs/lib/Application/Application.ts b/packages/snjs/lib/Application/Application.ts index 1a602cd72..61e377c5c 100644 --- a/packages/snjs/lib/Application/Application.ts +++ b/packages/snjs/lib/Application/Application.ts @@ -1,4 +1,8 @@ import { + AuthenticatorApiService, + AuthenticatorApiServiceInterface, + AuthenticatorServer, + AuthenticatorServerInterface, HttpService, HttpServiceInterface, SubscriptionApiService, @@ -63,6 +67,8 @@ import { CredentialsChangeFunctionResponse, SessionStrings, AccountEvent, + AuthenticatorClientInterface, + AuthenticatorManager, } from '@standardnotes/services' import { BackupServiceInterface, FilesClientInterface } from '@standardnotes/files' import { ComputePrivateUsername } from '@standardnotes/encryption' @@ -159,6 +165,9 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli private filesBackupService?: FilesBackupService private declare sessionStorageMapper: MapperInterface> private declare legacySessionStorageMapper: MapperInterface> + private declare authenticatorApiService: AuthenticatorApiServiceInterface + private declare authenticatorServer: AuthenticatorServerInterface + private declare authenticatorManager: AuthenticatorClientInterface private internalEventBus!: ExternalServices.InternalEventBusInterface @@ -1134,6 +1143,9 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli this.createMutatorService() this.createListedService() this.createActionsManager() + this.createAuthenticatorServer() + this.createAuthenticatorApiService() + this.createAuthenticatorManager() } private clearServices() { @@ -1181,6 +1193,9 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli ;(this.statusService as unknown) = undefined ;(this.sessionStorageMapper as unknown) = undefined ;(this.legacySessionStorageMapper as unknown) = undefined + ;(this.authenticatorApiService as unknown) = undefined + ;(this.authenticatorServer as unknown) = undefined + ;(this.authenticatorManager as unknown) = undefined this.services = [] } @@ -1705,4 +1720,16 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli this.statusService = new ExternalServices.StatusService(this.internalEventBus) this.services.push(this.statusService) } + + private createAuthenticatorServer() { + this.authenticatorServer = new AuthenticatorServer(this.httpService) + } + + private createAuthenticatorApiService() { + this.authenticatorApiService = new AuthenticatorApiService(this.authenticatorServer) + } + + private createAuthenticatorManager() { + this.authenticatorManager = new AuthenticatorManager(this.authenticatorApiService, this.internalEventBus) + } }