feat(snjs): add sign in with recovery codes use case (#2130)
* feat(snjs): add sign in with recovery codes use case * fix(snjs): code review adjustments * fix(snjs): remove unnecessary exposed getter * fix(services): waiting for event handling * fix: preferences test Co-authored-by: Mo <mo@standardnotes.com>
This commit is contained in:
5
packages/api/src/Domain/Client/Auth/AuthApiOperations.ts
Normal file
5
packages/api/src/Domain/Client/Auth/AuthApiOperations.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum AuthApiOperations {
|
||||
GenerateRecoveryCodes,
|
||||
GetRecoveryKeyParams,
|
||||
SignInWithRecoveryCodes,
|
||||
}
|
||||
89
packages/api/src/Domain/Client/Auth/AuthApiService.ts
Normal file
89
packages/api/src/Domain/Client/Auth/AuthApiService.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { ApiVersion } from '../../Api'
|
||||
import { ApiCallError } from '../../Error/ApiCallError'
|
||||
import { ErrorMessage } from '../../Error/ErrorMessage'
|
||||
import {
|
||||
GenerateRecoveryCodesResponse,
|
||||
RecoveryKeyParamsResponse,
|
||||
SignInWithRecoveryCodesResponse,
|
||||
} from '../../Response'
|
||||
import { AuthServerInterface } from '../../Server'
|
||||
|
||||
import { AuthApiOperations } from './AuthApiOperations'
|
||||
import { AuthApiServiceInterface } from './AuthApiServiceInterface'
|
||||
|
||||
export class AuthApiService implements AuthApiServiceInterface {
|
||||
private operationsInProgress: Map<AuthApiOperations, boolean>
|
||||
|
||||
constructor(private authServer: AuthServerInterface) {
|
||||
this.operationsInProgress = new Map()
|
||||
}
|
||||
|
||||
async generateRecoveryCodes(): Promise<GenerateRecoveryCodesResponse> {
|
||||
if (this.operationsInProgress.get(AuthApiOperations.GenerateRecoveryCodes)) {
|
||||
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||
}
|
||||
|
||||
this.operationsInProgress.set(AuthApiOperations.GenerateRecoveryCodes, true)
|
||||
|
||||
try {
|
||||
const response = await this.authServer.generateRecoveryCodes()
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
} finally {
|
||||
this.operationsInProgress.set(AuthApiOperations.GenerateRecoveryCodes, false)
|
||||
}
|
||||
}
|
||||
|
||||
async recoveryKeyParams(dto: {
|
||||
username: string
|
||||
codeChallenge: string
|
||||
recoveryCodes: string
|
||||
}): Promise<RecoveryKeyParamsResponse> {
|
||||
if (this.operationsInProgress.get(AuthApiOperations.GetRecoveryKeyParams)) {
|
||||
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||
}
|
||||
|
||||
this.operationsInProgress.set(AuthApiOperations.GetRecoveryKeyParams, true)
|
||||
|
||||
try {
|
||||
const response = await this.authServer.recoveryKeyParams({
|
||||
apiVersion: ApiVersion.v0,
|
||||
...dto,
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
} finally {
|
||||
this.operationsInProgress.set(AuthApiOperations.GetRecoveryKeyParams, false)
|
||||
}
|
||||
}
|
||||
|
||||
async signInWithRecoveryCodes(dto: {
|
||||
username: string
|
||||
password: string
|
||||
codeVerifier: string
|
||||
recoveryCodes: string
|
||||
}): Promise<SignInWithRecoveryCodesResponse> {
|
||||
if (this.operationsInProgress.get(AuthApiOperations.SignInWithRecoveryCodes)) {
|
||||
throw new ApiCallError(ErrorMessage.GenericInProgress)
|
||||
}
|
||||
|
||||
this.operationsInProgress.set(AuthApiOperations.SignInWithRecoveryCodes, true)
|
||||
|
||||
try {
|
||||
const response = await this.authServer.signInWithRecoveryCodes({
|
||||
apiVersion: ApiVersion.v0,
|
||||
...dto,
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
throw new ApiCallError(ErrorMessage.GenericFail)
|
||||
} finally {
|
||||
this.operationsInProgress.set(AuthApiOperations.SignInWithRecoveryCodes, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
GenerateRecoveryCodesResponse,
|
||||
RecoveryKeyParamsResponse,
|
||||
SignInWithRecoveryCodesResponse,
|
||||
} from '../../Response'
|
||||
|
||||
export interface AuthApiServiceInterface {
|
||||
generateRecoveryCodes(): Promise<GenerateRecoveryCodesResponse>
|
||||
recoveryKeyParams(dto: {
|
||||
username: string
|
||||
codeChallenge: string
|
||||
recoveryCodes: string
|
||||
}): Promise<RecoveryKeyParamsResponse>
|
||||
signInWithRecoveryCodes(dto: {
|
||||
username: string
|
||||
password: string
|
||||
codeVerifier: string
|
||||
recoveryCodes: string
|
||||
}): Promise<SignInWithRecoveryCodesResponse>
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
export * from './Auth/AuthApiOperations'
|
||||
export * from './Auth/AuthApiService'
|
||||
export * from './Auth/AuthApiServiceInterface'
|
||||
export * from './Authenticator/AuthenticatorApiOperations'
|
||||
export * from './Authenticator/AuthenticatorApiService'
|
||||
export * from './Authenticator/AuthenticatorApiServiceInterface'
|
||||
|
||||
@@ -1 +1 @@
|
||||
export type HttpRequestParams = Record<string, unknown>
|
||||
export type HttpRequestParams = unknown
|
||||
|
||||
@@ -276,9 +276,9 @@ export class HttpService implements HttpServiceInterface {
|
||||
}
|
||||
|
||||
private urlForUrlAndParams(url: string, params: HttpRequestParams) {
|
||||
const keyValueString = Object.keys(params)
|
||||
const keyValueString = Object.keys(params as Record<string, unknown>)
|
||||
.map((key) => {
|
||||
return key + '=' + encodeURIComponent(params[key] as string)
|
||||
return key + '=' + encodeURIComponent((params as Record<string, unknown>)[key] as string)
|
||||
})
|
||||
.join('&')
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface RecoveryKeyParamsRequestParams {
|
||||
apiVersion: string
|
||||
username: string
|
||||
codeChallenge: string
|
||||
recoveryCodes: string
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface SignInWithRecoveryCodesRequestParams {
|
||||
apiVersion: string
|
||||
username: string
|
||||
password: string
|
||||
codeVerifier: string
|
||||
recoveryCodes: string
|
||||
}
|
||||
@@ -5,6 +5,8 @@ export * from './Authenticator/GenerateAuthenticatorRegistrationOptionsRequestPa
|
||||
export * from './Authenticator/ListAuthenticatorsRequestParams'
|
||||
export * from './Authenticator/VerifyAuthenticatorAuthenticationResponseRequestParams'
|
||||
export * from './Authenticator/VerifyAuthenticatorRegistrationResponseRequestParams'
|
||||
export * from './Recovery/RecoveryKeyParamsRequestParams'
|
||||
export * from './Recovery/SignInWithRecoveryCodesRequestParams'
|
||||
export * from './Subscription/AppleIAPConfirmRequestParams'
|
||||
export * from './Subscription/SubscriptionInviteAcceptRequestParams'
|
||||
export * from './Subscription/SubscriptionInviteCancelRequestParams'
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
|
||||
import { GenerateRecoveryCodesResponseBody } from './GenerateRecoveryCodesResponseBody'
|
||||
|
||||
export interface GenerateRecoveryCodesResponse extends HttpResponse {
|
||||
data: Either<GenerateRecoveryCodesResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GenerateRecoveryCodesResponseBody {
|
||||
recoveryCodes: string
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
|
||||
import { RecoveryKeyParamsResponseBody } from './RecoveryKeyParamsResponseBody'
|
||||
|
||||
export interface RecoveryKeyParamsResponse extends HttpResponse {
|
||||
data: Either<RecoveryKeyParamsResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { KeyParamsData } from '@standardnotes/responses'
|
||||
|
||||
export interface RecoveryKeyParamsResponseBody {
|
||||
keyParams: KeyParamsData
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
|
||||
import { HttpResponse } from '../../Http/HttpResponse'
|
||||
|
||||
import { SignInWithRecoveryCodesResponseBody } from './SignInWithRecoveryCodesResponseBody'
|
||||
|
||||
export interface SignInWithRecoveryCodesResponse extends HttpResponse {
|
||||
data: Either<SignInWithRecoveryCodesResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { KeyParamsData, SessionBody } from '@standardnotes/responses'
|
||||
|
||||
export interface SignInWithRecoveryCodesResponseBody {
|
||||
session: SessionBody
|
||||
key_params: KeyParamsData
|
||||
user: {
|
||||
uuid: string
|
||||
email: string
|
||||
protocolVersion: string
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,12 @@ export * from './Authenticator/VerifyAuthenticatorAuthenticationResponseResponse
|
||||
export * from './Authenticator/VerifyAuthenticatorAuthenticationResponseResponseBody'
|
||||
export * from './Authenticator/VerifyAuthenticatorRegistrationResponseResponse'
|
||||
export * from './Authenticator/VerifyAuthenticatorRegistrationResponseResponseBody'
|
||||
export * from './Recovery/GenerateRecoveryCodesResponse'
|
||||
export * from './Recovery/GenerateRecoveryCodesResponseBody'
|
||||
export * from './Recovery/RecoveryKeyParamsResponse'
|
||||
export * from './Recovery/RecoveryKeyParamsResponseBody'
|
||||
export * from './Recovery/SignInWithRecoveryCodesResponse'
|
||||
export * from './Recovery/SignInWithRecoveryCodesResponseBody'
|
||||
export * from './Subscription/AppleIAPConfirmResponse'
|
||||
export * from './Subscription/AppleIAPConfirmResponseBody'
|
||||
export * from './Subscription/SubscriptionInviteAcceptResponse'
|
||||
|
||||
33
packages/api/src/Domain/Server/Auth/AuthServer.ts
Normal file
33
packages/api/src/Domain/Server/Auth/AuthServer.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { HttpServiceInterface } from '../../Http/HttpServiceInterface'
|
||||
import { RecoveryKeyParamsRequestParams, SignInWithRecoveryCodesRequestParams } from '../../Request'
|
||||
import {
|
||||
GenerateRecoveryCodesResponse,
|
||||
RecoveryKeyParamsResponse,
|
||||
SignInWithRecoveryCodesResponse,
|
||||
} from '../../Response'
|
||||
import { AuthServerInterface } from './AuthServerInterface'
|
||||
import { Paths } from './Paths'
|
||||
|
||||
export class AuthServer implements AuthServerInterface {
|
||||
constructor(private httpService: HttpServiceInterface) {}
|
||||
|
||||
async generateRecoveryCodes(): Promise<GenerateRecoveryCodesResponse> {
|
||||
const response = await this.httpService.post(Paths.v1.generateRecoveryCodes)
|
||||
|
||||
return response as GenerateRecoveryCodesResponse
|
||||
}
|
||||
|
||||
async recoveryKeyParams(params: RecoveryKeyParamsRequestParams): Promise<RecoveryKeyParamsResponse> {
|
||||
const response = await this.httpService.post(Paths.v1.recoveryKeyParams, params)
|
||||
|
||||
return response as RecoveryKeyParamsResponse
|
||||
}
|
||||
|
||||
async signInWithRecoveryCodes(
|
||||
params: SignInWithRecoveryCodesRequestParams,
|
||||
): Promise<SignInWithRecoveryCodesResponse> {
|
||||
const response = await this.httpService.post(Paths.v1.signInWithRecoveryCodes, params)
|
||||
|
||||
return response as SignInWithRecoveryCodesResponse
|
||||
}
|
||||
}
|
||||
12
packages/api/src/Domain/Server/Auth/AuthServerInterface.ts
Normal file
12
packages/api/src/Domain/Server/Auth/AuthServerInterface.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { RecoveryKeyParamsRequestParams, SignInWithRecoveryCodesRequestParams } from '../../Request'
|
||||
import {
|
||||
GenerateRecoveryCodesResponse,
|
||||
RecoveryKeyParamsResponse,
|
||||
SignInWithRecoveryCodesResponse,
|
||||
} from '../../Response'
|
||||
|
||||
export interface AuthServerInterface {
|
||||
generateRecoveryCodes(): Promise<GenerateRecoveryCodesResponse>
|
||||
recoveryKeyParams(params: RecoveryKeyParamsRequestParams): Promise<RecoveryKeyParamsResponse>
|
||||
signInWithRecoveryCodes(params: SignInWithRecoveryCodesRequestParams): Promise<SignInWithRecoveryCodesResponse>
|
||||
}
|
||||
@@ -2,8 +2,15 @@ const SessionPaths = {
|
||||
refreshSession: '/v1/sessions/refresh',
|
||||
}
|
||||
|
||||
const RecoveryPaths = {
|
||||
generateRecoveryCodes: '/v1/auth/recovery/codes',
|
||||
recoveryKeyParams: '/v1/auth/recovery/login-params',
|
||||
signInWithRecoveryCodes: '/v1/auth/recovery/login',
|
||||
}
|
||||
|
||||
export const Paths = {
|
||||
v1: {
|
||||
...SessionPaths,
|
||||
...RecoveryPaths,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from './Auth/AuthServer'
|
||||
export * from './Auth/AuthServerInterface'
|
||||
export * from './Authenticator/AuthenticatorServer'
|
||||
export * from './Authenticator/AuthenticatorServerInterface'
|
||||
export * from './Subscription/SubscriptionServer'
|
||||
|
||||
Reference in New Issue
Block a user