mirror of
https://github.com/standardnotes/server
synced 2026-01-16 20:04:32 -05:00
feat(auth): add http endpoints for authenticators
This commit is contained in:
@@ -21,6 +21,7 @@ import '../src/Controller/v1/FilesController'
|
||||
import '../src/Controller/v1/SubscriptionInvitesController'
|
||||
import '../src/Controller/v1/WorkspacesController'
|
||||
import '../src/Controller/v1/InvitesController'
|
||||
import '../src/Controller/v1/AuthenticatorsController'
|
||||
|
||||
import '../src/Controller/v2/PaymentsControllerV2'
|
||||
import '../src/Controller/v2/ActionsControllerV2'
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { inject } from 'inversify'
|
||||
import { Request, Response } from 'express'
|
||||
import { controller, BaseHttpController, httpPost, httpGet } from 'inversify-express-utils'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
|
||||
|
||||
@controller('/v1/authenticators', TYPES.AuthMiddleware)
|
||||
export class AuthenticatorsController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpGet('/generate-registration-options')
|
||||
async generateRegistrationOptions(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(
|
||||
request,
|
||||
response,
|
||||
'authenticators/generate-registration-options',
|
||||
request.body,
|
||||
)
|
||||
}
|
||||
|
||||
@httpGet('/generate-authentication-options')
|
||||
async generateAuthenticationOptions(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(
|
||||
request,
|
||||
response,
|
||||
'authenticators/generate-authentication-options',
|
||||
request.body,
|
||||
)
|
||||
}
|
||||
|
||||
@httpPost('/verify-registration')
|
||||
async verifyRegistration(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(request, response, 'authenticators/verify-registration', request.body)
|
||||
}
|
||||
|
||||
@httpPost('/verify-authentication')
|
||||
async verifyAuthentication(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(request, response, 'authenticators/verify-authentication', request.body)
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import '../src/Controller/ListedController'
|
||||
import '../src/Controller/SubscriptionSettingsController'
|
||||
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressAuthController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressAuthenticatorsController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressUserRequestsController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressWebSocketsController'
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class removeCompoundIndex1672307975117 implements MigrationInterface {
|
||||
name = 'removeCompoundIndex1672307975117'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `user_uuid_and_challenge` ON `authenticator_challenges`')
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE INDEX `user_uuid_and_challenge` ON `authenticator_challenges` (`user_uuid`, `challenge`)',
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -217,6 +217,7 @@ import { GenerateAuthenticatorRegistrationOptions } from '../Domain/UseCase/Gene
|
||||
import { VerifyAuthenticatorRegistrationResponse } from '../Domain/UseCase/VerifyAuthenticatorRegistrationResponse/VerifyAuthenticatorRegistrationResponse'
|
||||
import { GenerateAuthenticatorAuthenticationOptions } from '../Domain/UseCase/GenerateAuthenticatorAuthenticationOptions/GenerateAuthenticatorAuthenticationOptions'
|
||||
import { VerifyAuthenticatorAuthenticationResponse } from '../Domain/UseCase/VerifyAuthenticatorAuthenticationResponse/VerifyAuthenticatorAuthenticationResponse'
|
||||
import { AuthenticatorsController } from '../Controller/AuthenticatorsController'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||
@@ -303,11 +304,6 @@ export class ContainerConfigLoader {
|
||||
)
|
||||
.toConstantValue(new AuthenticatorChallengePersistenceMapper())
|
||||
|
||||
// Controller
|
||||
container.bind<AuthController>(TYPES.AuthController).to(AuthController)
|
||||
container.bind<SubscriptionInvitesController>(TYPES.SubscriptionInvitesController).to(SubscriptionInvitesController)
|
||||
container.bind<UserRequestsController>(TYPES.UserRequestsController).to(UserRequestsController)
|
||||
|
||||
// ORM
|
||||
container
|
||||
.bind<Repository<OfflineSetting>>(TYPES.ORMOfflineSettingRepository)
|
||||
@@ -641,6 +637,21 @@ export class ContainerConfigLoader {
|
||||
container.bind<CreateCrossServiceToken>(TYPES.CreateCrossServiceToken).to(CreateCrossServiceToken)
|
||||
container.bind<ProcessUserRequest>(TYPES.ProcessUserRequest).to(ProcessUserRequest)
|
||||
|
||||
// Controller
|
||||
container.bind<AuthController>(TYPES.AuthController).to(AuthController)
|
||||
container
|
||||
.bind<AuthenticatorsController>(TYPES.AuthenticatorsController)
|
||||
.toConstantValue(
|
||||
new AuthenticatorsController(
|
||||
container.get(TYPES.GenerateAuthenticatorRegistrationOptions),
|
||||
container.get(TYPES.VerifyAuthenticatorRegistrationResponse),
|
||||
container.get(TYPES.GenerateAuthenticatorAuthenticationOptions),
|
||||
container.get(TYPES.VerifyAuthenticatorAuthenticationResponse),
|
||||
),
|
||||
)
|
||||
container.bind<SubscriptionInvitesController>(TYPES.SubscriptionInvitesController).to(SubscriptionInvitesController)
|
||||
container.bind<UserRequestsController>(TYPES.UserRequestsController).to(UserRequestsController)
|
||||
|
||||
// Handlers
|
||||
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
||||
container
|
||||
|
||||
@@ -9,6 +9,7 @@ const TYPES = {
|
||||
AuthenticatorPersistenceMapper: Symbol.for('AuthenticatorPersistenceMapper'),
|
||||
// Controller
|
||||
AuthController: Symbol.for('AuthController'),
|
||||
AuthenticatorsController: Symbol.for('AuthenticatorsController'),
|
||||
SubscriptionInvitesController: Symbol.for('SubscriptionInvitesController'),
|
||||
UserRequestsController: Symbol.for('UserRequestsController'),
|
||||
// Repositories
|
||||
|
||||
122
packages/auth/src/Controller/AuthenticatorsController.ts
Normal file
122
packages/auth/src/Controller/AuthenticatorsController.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { HttpStatusCode } from '@standardnotes/api'
|
||||
|
||||
import { GenerateAuthenticatorAuthenticationOptions } from '../Domain/UseCase/GenerateAuthenticatorAuthenticationOptions/GenerateAuthenticatorAuthenticationOptions'
|
||||
import { GenerateAuthenticatorRegistrationOptions } from '../Domain/UseCase/GenerateAuthenticatorRegistrationOptions/GenerateAuthenticatorRegistrationOptions'
|
||||
import { VerifyAuthenticatorAuthenticationResponse } from '../Domain/UseCase/VerifyAuthenticatorAuthenticationResponse/VerifyAuthenticatorAuthenticationResponse'
|
||||
import { VerifyAuthenticatorRegistrationResponse } from '../Domain/UseCase/VerifyAuthenticatorRegistrationResponse/VerifyAuthenticatorRegistrationResponse'
|
||||
import { GenerateAuthenticatorAuthenticationOptionsRequestParams } from '../Infra/Http/Request/GenerateAuthenticatorAuthenticationOptionsRequestParams'
|
||||
import { GenerateAuthenticatorRegistrationOptionsRequestParams } from '../Infra/Http/Request/GenerateAuthenticatorRegistrationOptionsRequestParams'
|
||||
import { VerifyAuthenticatorAuthenticationResponseRequestParams } from '../Infra/Http/Request/VerifyAuthenticatorAuthenticationResponseRequestParams'
|
||||
import { VerifyAuthenticatorRegistrationResponseRequestParams } from '../Infra/Http/Request/VerifyAuthenticatorRegistrationResponseRequestParams'
|
||||
import { GenerateAuthenticatorAuthenticationOptionsResponse } from '../Infra/Http/Response/GenerateAuthenticatorAuthenticationOptionsResponse'
|
||||
import { GenerateAuthenticatorRegistrationOptionsResponse } from '../Infra/Http/Response/GenerateAuthenticatorRegistrationOptionsResponse'
|
||||
import { VerifyAuthenticatorAuthenticationResponseResponse } from '../Infra/Http/Response/VerifyAuthenticatorAuthenticationResponseResponse'
|
||||
import { VerifyAuthenticatorRegistrationResponseResponse } from '../Infra/Http/Response/VerifyAuthenticatorRegistrationResponseResponse'
|
||||
|
||||
export class AuthenticatorsController {
|
||||
constructor(
|
||||
private generateAuthenticatorRegistrationOptions: GenerateAuthenticatorRegistrationOptions,
|
||||
private verifyAuthenticatorRegistrationResponse: VerifyAuthenticatorRegistrationResponse,
|
||||
private generateAuthenticatorAuthenticationOptions: GenerateAuthenticatorAuthenticationOptions,
|
||||
private verifyAuthenticatorAuthenticationResponse: VerifyAuthenticatorAuthenticationResponse,
|
||||
) {}
|
||||
|
||||
async generateRegistrationOptions(
|
||||
params: GenerateAuthenticatorRegistrationOptionsRequestParams,
|
||||
): Promise<GenerateAuthenticatorRegistrationOptionsResponse> {
|
||||
const result = await this.generateAuthenticatorRegistrationOptions.execute({
|
||||
userUuid: params.userUuid,
|
||||
username: params.username,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
data: {
|
||||
error: {
|
||||
message: result.getError(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: { options: result.getValue() },
|
||||
}
|
||||
}
|
||||
|
||||
async verifyRegistrationResponse(
|
||||
params: VerifyAuthenticatorRegistrationResponseRequestParams,
|
||||
): Promise<VerifyAuthenticatorRegistrationResponseResponse> {
|
||||
const result = await this.verifyAuthenticatorRegistrationResponse.execute({
|
||||
userUuid: params.userUuid,
|
||||
registrationCredential: params.registrationCredential,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return {
|
||||
status: HttpStatusCode.Unauthorized,
|
||||
data: {
|
||||
error: {
|
||||
message: result.getError(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: { success: result.getValue() },
|
||||
}
|
||||
}
|
||||
|
||||
async generateAuthenticationOptions(
|
||||
params: GenerateAuthenticatorAuthenticationOptionsRequestParams,
|
||||
): Promise<GenerateAuthenticatorAuthenticationOptionsResponse> {
|
||||
const result = await this.generateAuthenticatorAuthenticationOptions.execute({
|
||||
userUuid: params.userUuid,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
data: {
|
||||
error: {
|
||||
message: result.getError(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: { options: result.getValue() },
|
||||
}
|
||||
}
|
||||
|
||||
async verifyAuthenticationResponse(
|
||||
params: VerifyAuthenticatorAuthenticationResponseRequestParams,
|
||||
): Promise<VerifyAuthenticatorAuthenticationResponseResponse> {
|
||||
const result = await this.verifyAuthenticatorAuthenticationResponse.execute({
|
||||
userUuid: params.userUuid,
|
||||
authenticationCredential: params.authenticationCredential,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return {
|
||||
status: HttpStatusCode.Unauthorized,
|
||||
data: {
|
||||
error: {
|
||||
message: result.getError(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: { success: result.getValue() },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import { Uuid } from '@standardnotes/domain-core'
|
||||
import { AuthenticatorChallenge } from './AuthenticatorChallenge'
|
||||
|
||||
export interface AuthenticatorChallengeRepositoryInterface {
|
||||
findByUserUuidAndChallenge(userUuid: Uuid, challenge: Buffer): Promise<AuthenticatorChallenge | null>
|
||||
findByUserUuid(userUuid: Uuid): Promise<AuthenticatorChallenge | null>
|
||||
save(authenticatorChallenge: AuthenticatorChallenge): Promise<void>
|
||||
}
|
||||
|
||||
@@ -27,18 +27,18 @@ export class VerifyAuthenticatorAuthenticationResponse implements UseCaseInterfa
|
||||
|
||||
const authenticator = await this.authenticatorRepository.findByUserUuidAndCredentialId(
|
||||
userUuid,
|
||||
Buffer.from(dto.registrationCredential.id as string),
|
||||
Buffer.from(dto.authenticationCredential.id as string),
|
||||
)
|
||||
if (!authenticator) {
|
||||
return Result.fail(
|
||||
`Could not verify authenticator authentication response: authenticator ${dto.registrationCredential.id} not found`,
|
||||
`Could not verify authenticator authentication response: authenticator ${dto.authenticationCredential.id} not found`,
|
||||
)
|
||||
}
|
||||
|
||||
let verification: VerifiedAuthenticationResponse
|
||||
try {
|
||||
verification = await verifyAuthenticationResponse({
|
||||
credential: dto.registrationCredential,
|
||||
credential: dto.authenticationCredential,
|
||||
expectedChallenge: authenticatorChallenge.props.challenge.toString(),
|
||||
expectedOrigin: `https://${RelyingParty.RP_ID}`,
|
||||
expectedRPID: RelyingParty.RP_ID,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface VerifyAuthenticatorAuthenticationResponseDTO {
|
||||
userUuid: string
|
||||
registrationCredential: Record<string, unknown>
|
||||
authenticationCredential: Record<string, unknown>
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
authenticatorRepository.save = jest.fn()
|
||||
|
||||
authenticatorChallengeRepository = {} as jest.Mocked<AuthenticatorChallengeRepositoryInterface>
|
||||
authenticatorChallengeRepository.findByUserUuidAndChallenge = jest.fn().mockReturnValue({
|
||||
authenticatorChallengeRepository.findByUserUuid = jest.fn().mockReturnValue({
|
||||
props: {
|
||||
challenge: Buffer.from('challenge'),
|
||||
},
|
||||
@@ -32,7 +32,6 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
challenge: Buffer.from('challenge'),
|
||||
registrationCredential: {
|
||||
id: Buffer.from('id'),
|
||||
rawId: Buffer.from('rawId'),
|
||||
@@ -51,13 +50,12 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
})
|
||||
|
||||
it('should return error if challenge is not found', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuidAndChallenge = jest.fn().mockReturnValue(null)
|
||||
authenticatorChallengeRepository.findByUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
challenge: Buffer.from('challenge'),
|
||||
registrationCredential: {
|
||||
id: Buffer.from('id'),
|
||||
rawId: Buffer.from('rawId'),
|
||||
@@ -74,7 +72,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
})
|
||||
|
||||
it('should return error if verification could not verify', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuidAndChallenge = jest.fn().mockReturnValue({
|
||||
authenticatorChallengeRepository.findByUserUuid = jest.fn().mockReturnValue({
|
||||
props: {
|
||||
challenge: Buffer.from('challenge'),
|
||||
},
|
||||
@@ -98,7 +96,6 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
challenge: Buffer.from('invalid'),
|
||||
registrationCredential: {
|
||||
id: Buffer.from('id'),
|
||||
rawId: Buffer.from('rawId'),
|
||||
@@ -117,7 +114,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
})
|
||||
|
||||
it('should return error if verification throws error', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuidAndChallenge = jest.fn().mockReturnValue({
|
||||
authenticatorChallengeRepository.findByUserUuid = jest.fn().mockReturnValue({
|
||||
props: {
|
||||
challenge: Buffer.from('challenge'),
|
||||
},
|
||||
@@ -132,7 +129,6 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
challenge: Buffer.from('invalid'),
|
||||
registrationCredential: {
|
||||
id: Buffer.from('id'),
|
||||
rawId: Buffer.from('rawId'),
|
||||
@@ -151,7 +147,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
})
|
||||
|
||||
it('should return error if verification is missing registration info', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuidAndChallenge = jest.fn().mockReturnValue({
|
||||
authenticatorChallengeRepository.findByUserUuid = jest.fn().mockReturnValue({
|
||||
props: {
|
||||
challenge: Buffer.from('challenge'),
|
||||
},
|
||||
@@ -168,7 +164,6 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
challenge: Buffer.from('invalid'),
|
||||
registrationCredential: {
|
||||
id: Buffer.from('id'),
|
||||
rawId: Buffer.from('rawId'),
|
||||
@@ -189,7 +184,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
})
|
||||
|
||||
it('should return error if authenticator could not be created', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuidAndChallenge = jest.fn().mockReturnValue({
|
||||
authenticatorChallengeRepository.findByUserUuid = jest.fn().mockReturnValue({
|
||||
props: {
|
||||
challenge: Buffer.from('challenge'),
|
||||
},
|
||||
@@ -218,7 +213,6 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
challenge: Buffer.from('invalid'),
|
||||
registrationCredential: {
|
||||
id: Buffer.from('id'),
|
||||
rawId: Buffer.from('rawId'),
|
||||
@@ -238,7 +232,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
})
|
||||
|
||||
it('should verify authenticator registration response', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuidAndChallenge = jest.fn().mockReturnValue({
|
||||
authenticatorChallengeRepository.findByUserUuid = jest.fn().mockReturnValue({
|
||||
props: {
|
||||
challenge: Buffer.from('challenge'),
|
||||
},
|
||||
@@ -262,7 +256,6 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
challenge: Buffer.from('invalid'),
|
||||
registrationCredential: {
|
||||
id: Buffer.from('id'),
|
||||
rawId: Buffer.from('rawId'),
|
||||
|
||||
@@ -20,10 +20,7 @@ export class VerifyAuthenticatorRegistrationResponse implements UseCaseInterface
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const authenticatorChallenge = await this.authenticatorChallengeRepository.findByUserUuidAndChallenge(
|
||||
userUuid,
|
||||
dto.challenge,
|
||||
)
|
||||
const authenticatorChallenge = await this.authenticatorChallengeRepository.findByUserUuid(userUuid)
|
||||
if (!authenticatorChallenge) {
|
||||
return Result.fail('Could not verify authenticator registration response: challenge not found')
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export interface VerifyAuthenticatorRegistrationResponseDTO {
|
||||
userUuid: string
|
||||
challenge: Buffer
|
||||
registrationCredential: Record<string, unknown>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GenerateAuthenticatorAuthenticationOptionsRequestParams {
|
||||
userUuid: string
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface GenerateAuthenticatorRegistrationOptionsRequestParams {
|
||||
userUuid: string
|
||||
username: string
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface VerifyAuthenticatorAuthenticationResponseRequestParams {
|
||||
userUuid: string
|
||||
authenticationCredential: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface VerifyAuthenticatorRegistrationResponseRequestParams {
|
||||
userUuid: string
|
||||
registrationCredential: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { HttpErrorResponseBody, HttpResponse } from '@standardnotes/api'
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { GenerateAuthenticatorAuthenticationOptionsResponseBody } from './GenerateAuthenticatorAuthenticationOptionsResponseBody'
|
||||
|
||||
export interface GenerateAuthenticatorAuthenticationOptionsResponse extends HttpResponse {
|
||||
data: Either<GenerateAuthenticatorAuthenticationOptionsResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GenerateAuthenticatorAuthenticationOptionsResponseBody {
|
||||
options: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { HttpErrorResponseBody, HttpResponse } from '@standardnotes/api'
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { GenerateAuthenticatorRegistrationOptionsResponseBody } from './GenerateAuthenticatorRegistrationOptionsResponseBody'
|
||||
|
||||
export interface GenerateAuthenticatorRegistrationOptionsResponse extends HttpResponse {
|
||||
data: Either<GenerateAuthenticatorRegistrationOptionsResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GenerateAuthenticatorRegistrationOptionsResponseBody {
|
||||
options: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { HttpErrorResponseBody, HttpResponse } from '@standardnotes/api'
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { VerifyAuthenticatorAuthenticationResponseResponseBody } from './VerifyAuthenticatorAuthenticationResponseResponseBody'
|
||||
|
||||
export interface VerifyAuthenticatorAuthenticationResponseResponse extends HttpResponse {
|
||||
data: Either<VerifyAuthenticatorAuthenticationResponseResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface VerifyAuthenticatorAuthenticationResponseResponseBody {
|
||||
success: boolean
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { HttpErrorResponseBody, HttpResponse } from '@standardnotes/api'
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { VerifyAuthenticatorRegistrationResponseResponseBody } from './VerifyAuthenticatorRegistrationResponseResponseBody'
|
||||
|
||||
export interface VerifyAuthenticatorRegistrationResponseResponse extends HttpResponse {
|
||||
data: Either<VerifyAuthenticatorRegistrationResponseResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface VerifyAuthenticatorRegistrationResponseResponseBody {
|
||||
success: boolean
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
BaseHttpController,
|
||||
controller,
|
||||
httpGet,
|
||||
httpPost,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
results,
|
||||
} from 'inversify-express-utils'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AuthenticatorsController } from '../../Controller/AuthenticatorsController'
|
||||
|
||||
@controller('/authenticators', TYPES.ApiGatewayAuthMiddleware)
|
||||
export class InversifyExpressAuthenticatorsController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.AuthenticatorsController) private authenticatorsController: AuthenticatorsController) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpGet('/generate-registration-options')
|
||||
async generateRegistrationOptions(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.authenticatorsController.generateRegistrationOptions({
|
||||
username: response.locals.user.email,
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
@httpPost('/verify-registration')
|
||||
async verifyRegistration(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.authenticatorsController.verifyRegistrationResponse({
|
||||
userUuid: response.locals.user.uuid,
|
||||
registrationCredential: request.body,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
@httpGet('/generate-authentication-options')
|
||||
async generateAuthenticationOptions(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.authenticatorsController.generateAuthenticationOptions({
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
@httpPost('/verify-authentication')
|
||||
async verifyAuthentication(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.authenticatorsController.verifyAuthenticationResponse({
|
||||
userUuid: response.locals.user.uuid,
|
||||
authenticationCredential: request.body,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
}
|
||||
@@ -12,22 +12,6 @@ export class MySQLAuthenticatorChallengeRepository implements AuthenticatorChall
|
||||
private mapper: MapperInterface<AuthenticatorChallenge, TypeORMAuthenticatorChallenge>,
|
||||
) {}
|
||||
|
||||
async findByUserUuidAndChallenge(userUuid: Uuid, challenge: Buffer): Promise<AuthenticatorChallenge | null> {
|
||||
const typeOrm = await this.ormRepository
|
||||
.createQueryBuilder('challenge')
|
||||
.where('challenge.user_uuid = :userUuid and challenge.challenge = :challenge', {
|
||||
userUuid: userUuid.value,
|
||||
challenge,
|
||||
})
|
||||
.getOne()
|
||||
|
||||
if (typeOrm === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.mapper.toDomain(typeOrm)
|
||||
}
|
||||
|
||||
async save(authenticatorChallenge: AuthenticatorChallenge): Promise<void> {
|
||||
let persistence = this.mapper.toProjection(authenticatorChallenge)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
|
||||
|
||||
@Entity({ name: 'authenticator_challenges' })
|
||||
@Index('user_uuid_and_challenge', ['userUuid', 'challenge'])
|
||||
export class TypeORMAuthenticatorChallenge {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
Reference in New Issue
Block a user