mirror of
https://github.com/standardnotes/server
synced 2026-09-11 21:45:28 -04:00
feat(auth): feature entitlement check for u2f endpoints
This commit is contained in:
+43
@@ -4,11 +4,16 @@ import { Authenticator } from '../../Authenticator/Authenticator'
|
||||
import { AuthenticatorChallenge } from '../../Authenticator/AuthenticatorChallenge'
|
||||
import { AuthenticatorChallengeRepositoryInterface } from '../../Authenticator/AuthenticatorChallengeRepositoryInterface'
|
||||
import { AuthenticatorRepositoryInterface } from '../../Authenticator/AuthenticatorRepositoryInterface'
|
||||
import { FeatureServiceInterface } from '../../Feature/FeatureServiceInterface'
|
||||
import { User } from '../../User/User'
|
||||
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||
import { GenerateAuthenticatorRegistrationOptions } from './GenerateAuthenticatorRegistrationOptions'
|
||||
|
||||
describe('GenerateAuthenticatorRegistrationOptions', () => {
|
||||
let authenticatorRepository: AuthenticatorRepositoryInterface
|
||||
let authenticatorChallengeRepository: AuthenticatorChallengeRepositoryInterface
|
||||
let userRepository: UserRepositoryInterface
|
||||
let featureService: FeatureServiceInterface
|
||||
|
||||
const createUseCase = () =>
|
||||
new GenerateAuthenticatorRegistrationOptions(
|
||||
@@ -16,6 +21,8 @@ describe('GenerateAuthenticatorRegistrationOptions', () => {
|
||||
authenticatorChallengeRepository,
|
||||
'Standard Notes',
|
||||
'standardnotes.com',
|
||||
userRepository,
|
||||
featureService,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -35,6 +42,12 @@ describe('GenerateAuthenticatorRegistrationOptions', () => {
|
||||
|
||||
authenticatorChallengeRepository = {} as jest.Mocked<AuthenticatorChallengeRepositoryInterface>
|
||||
authenticatorChallengeRepository.save = jest.fn()
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue({} as jest.Mocked<User>)
|
||||
|
||||
featureService = {} as jest.Mocked<FeatureServiceInterface>
|
||||
featureService.userIsEntitledToFeature = jest.fn().mockReturnValue(true)
|
||||
})
|
||||
|
||||
it('should return error if userUuid is invalid', async () => {
|
||||
@@ -63,6 +76,36 @@ describe('GenerateAuthenticatorRegistrationOptions', () => {
|
||||
expect(result.getError()).toBe('Could not generate authenticator registration options: Username cannot be empty')
|
||||
})
|
||||
|
||||
it('should return error if user is not entitled to u2f feature', async () => {
|
||||
featureService.userIsEntitledToFeature = jest.fn().mockReturnValue(false)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
username: 'username',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe(
|
||||
'Could not generate authenticator registration options: user is not entitled to U2F.',
|
||||
)
|
||||
})
|
||||
|
||||
it('should return error if user is not found', async () => {
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
username: 'username',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Could not generate authenticator registration options: user not found.')
|
||||
})
|
||||
|
||||
it('should return error if authenticator challenge is invalid', async () => {
|
||||
const mock = jest.spyOn(AuthenticatorChallenge, 'create')
|
||||
mock.mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
+19
@@ -5,6 +5,9 @@ import { GenerateAuthenticatorRegistrationOptionsDTO } from './GenerateAuthentic
|
||||
import { AuthenticatorRepositoryInterface } from '../../Authenticator/AuthenticatorRepositoryInterface'
|
||||
import { AuthenticatorChallengeRepositoryInterface } from '../../Authenticator/AuthenticatorChallengeRepositoryInterface'
|
||||
import { AuthenticatorChallenge } from '../../Authenticator/AuthenticatorChallenge'
|
||||
import { FeatureIdentifier } from '@standardnotes/features'
|
||||
import { FeatureServiceInterface } from '../../Feature/FeatureServiceInterface'
|
||||
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||
|
||||
export class GenerateAuthenticatorRegistrationOptions implements UseCaseInterface<Record<string, unknown>> {
|
||||
constructor(
|
||||
@@ -12,6 +15,8 @@ export class GenerateAuthenticatorRegistrationOptions implements UseCaseInterfac
|
||||
private authenticatorChallengeRepository: AuthenticatorChallengeRepositoryInterface,
|
||||
private relyingPartyName: string,
|
||||
private relyingPartyId: string,
|
||||
private userRepository: UserRepositoryInterface,
|
||||
private featureService: FeatureServiceInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: GenerateAuthenticatorRegistrationOptionsDTO): Promise<Result<Record<string, unknown>>> {
|
||||
@@ -27,6 +32,20 @@ export class GenerateAuthenticatorRegistrationOptions implements UseCaseInterfac
|
||||
}
|
||||
const username = usernameOrError.getValue()
|
||||
|
||||
const user = await this.userRepository.findOneByUuid(userUuid.value)
|
||||
if (user === null) {
|
||||
return Result.fail('Could not generate authenticator registration options: user not found.')
|
||||
}
|
||||
|
||||
const userIsEntitledToU2F = await this.featureService.userIsEntitledToFeature(
|
||||
user,
|
||||
FeatureIdentifier.UniversalSecondFactor,
|
||||
)
|
||||
|
||||
if (!userIsEntitledToU2F) {
|
||||
return Result.fail('Could not generate authenticator registration options: user is not entitled to U2F.')
|
||||
}
|
||||
|
||||
const authenticators = await this.authenticatorRepository.findByUserUuid(userUuid)
|
||||
const options = generateRegistrationOptions({
|
||||
rpID: this.relyingPartyId,
|
||||
|
||||
Reference in New Issue
Block a user