-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Create recaptcha libraries * Recaptcha implementation and verification for generation route * Protect authentication actions with recaptcha * Deactivate recaptcha security locally * Fix lint and test issues * Add unit tests for google recaptcha generator
- Loading branch information
Showing
67 changed files
with
1,326 additions
and
85 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,3 @@ Thumbs.db | |
|
||
.nx/cache | ||
.nx/workspace-data | ||
|
||
# .env file | ||
apps/client/.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
RECAPTCHA_SITE_KEY= | ||
FIREBASE_PROJECT_ID= | ||
GOOGLE_APPLICATION_CREDENTIALS= | ||
FRONTEND_URL= | ||
ENVIRONMENT=local |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.env | ||
gcp-key.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
declare let grecaptcha: ReCaptchaV2.ReCaptcha & { | ||
enterprise: ReCaptchaV2.ReCaptcha; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
|
||
import { mock } from 'jest-mock-extended'; | ||
|
||
import { ChallengeResultData, IRecaptchaChecker, IRecaptchaCheckerSymbol } from '@pps-easy/recaptcha/contracts'; | ||
|
||
import { RecaptchaGuard } from './recaptcha.guard'; | ||
import { BadRequestException, ExecutionContext, ForbiddenException } from '@nestjs/common'; | ||
|
||
describe('The RecaptchaGuard', () => { | ||
let app: TestingModule; | ||
const recaptchaCheckerMock = mock<IRecaptchaChecker>() | ||
const executionContextMock = mock<ExecutionContext>(); | ||
const getRequest = jest.fn(); | ||
|
||
beforeAll(() => { | ||
executionContextMock.switchToHttp.mockReturnValue({ getRequest, getNext: jest.fn(), getResponse: jest.fn() }); | ||
}); | ||
|
||
beforeAll(async () => { | ||
app = await Test.createTestingModule({ | ||
providers: [ | ||
RecaptchaGuard, | ||
{ provide: IRecaptchaCheckerSymbol, useValue: recaptchaCheckerMock } | ||
], | ||
}) | ||
.compile() | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(app.get(RecaptchaGuard)).toBeDefined(); | ||
}); | ||
|
||
describe('The canActivate method', () => { | ||
describe('When context http request does not contain body', () => { | ||
beforeEach(() => { | ||
getRequest.mockReturnValueOnce({ body: undefined }); | ||
}); | ||
|
||
it('should return false', () => { | ||
const guard = app.get(RecaptchaGuard); | ||
|
||
expect(guard.canActivate(executionContextMock)).toBe(false); | ||
}); | ||
}); | ||
|
||
describe('When context http request body does not contain recaptchaToken', () => { | ||
beforeEach(() => { | ||
getRequest.mockReturnValueOnce({ body: {} }); | ||
}); | ||
|
||
it('should throw BadRequestException', () => { | ||
const guard = app.get(RecaptchaGuard); | ||
|
||
expect(() => guard.canActivate(executionContextMock)).toThrow(expect.any(BadRequestException)); | ||
}); | ||
}); | ||
|
||
describe('When context http request body contains a not valid recaptchaToken', () => { | ||
beforeEach(() => { | ||
const challengeResult: ChallengeResultData = { | ||
reasons: [], | ||
score: 0, | ||
isValid: false, | ||
} | ||
recaptchaCheckerMock.check.mockResolvedValue(challengeResult); | ||
getRequest.mockReturnValueOnce({ body: { recaptchaToken: 'notValidRecaptchaToken' } }); | ||
}); | ||
|
||
it('should throw forbidden exception', () => { | ||
const guard = app.get(RecaptchaGuard); | ||
|
||
expect(guard.canActivate(executionContextMock)).rejects.toEqual(expect.any(ForbiddenException)); | ||
}); | ||
}); | ||
|
||
describe('When context http request body contains a bad score check result', () => { | ||
beforeEach(() => { | ||
const challengeResult: ChallengeResultData = { | ||
reasons: [], | ||
score: 0.4, | ||
isValid: true, | ||
} | ||
recaptchaCheckerMock.check.mockResolvedValue(challengeResult); | ||
getRequest.mockReturnValueOnce({ body: { recaptchaToken: 'badScoreRecaptchaToken' } }); | ||
}); | ||
|
||
it('should throw forbidden exception', () => { | ||
const guard = app.get(RecaptchaGuard); | ||
|
||
expect(guard.canActivate(executionContextMock)).rejects.toEqual(expect.any(ForbiddenException)); | ||
}); | ||
}); | ||
|
||
describe('When context http request body contains a good score check result', () => { | ||
beforeEach(() => { | ||
const challengeResult: ChallengeResultData = { | ||
reasons: [], | ||
score: 0.6, | ||
isValid: true, | ||
} | ||
recaptchaCheckerMock.check.mockResolvedValue(challengeResult); | ||
getRequest.mockReturnValueOnce({ body: { recaptchaToken: 'goodScoreRecaptchaToken' } }); | ||
}); | ||
|
||
it('should resolve true', () => { | ||
const guard = app.get(RecaptchaGuard); | ||
|
||
expect(guard.canActivate(executionContextMock)).resolves.toBe(true); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { | ||
BadRequestException, | ||
CanActivate, | ||
ExecutionContext, | ||
ForbiddenException, | ||
Inject, | ||
Injectable | ||
} from '@nestjs/common'; | ||
import { Request } from 'express'; | ||
|
||
import { Observable } from 'rxjs'; | ||
|
||
import { IRecaptchaChecker, IRecaptchaCheckerSymbol } from '@pps-easy/recaptcha/contracts'; | ||
import { ChallengeResult } from '@pps-easy/recaptcha/domain'; | ||
|
||
@Injectable() | ||
export class RecaptchaGuard implements CanActivate { | ||
public constructor(@Inject(IRecaptchaCheckerSymbol) private readonly recaptchaChecker: IRecaptchaChecker) { | ||
} | ||
|
||
public canActivate( | ||
context: ExecutionContext, | ||
): boolean | Promise<boolean> | Observable<boolean> { | ||
const request: Request = context.switchToHttp().getRequest(); | ||
|
||
if (!request.body) { | ||
return false; | ||
} | ||
|
||
if (!request.body.recaptchaToken) { | ||
throw new BadRequestException('Body must contain recaptchaToken'); | ||
} | ||
|
||
return new Promise<boolean>((resolve, reject) => { | ||
this.recaptchaChecker.check(request.body.recaptchaToken).then((result) => { | ||
const challengeResult = new ChallengeResult(result); | ||
const isValidChallenge = challengeResult.checkIsValid(); | ||
|
||
if (!isValidChallenge) { | ||
reject(new ForbiddenException()); | ||
|
||
return; | ||
} | ||
|
||
resolve(isValidChallenge); | ||
}); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Body, Controller, Inject, Post } from '@nestjs/common'; | ||
|
||
import { ChallengeResultData, IRecaptchaChecker, IRecaptchaCheckerSymbol } from '@pps-easy/recaptcha/contracts'; | ||
|
||
import { RecaptchaDto } from './recaptcha.dto'; | ||
|
||
@Controller('/recaptcha') | ||
export class RecaptchaController { | ||
constructor(@Inject(IRecaptchaCheckerSymbol) private readonly recaptchaChecker: IRecaptchaChecker) {} | ||
|
||
@Post('/check') | ||
public check(@Body() recaptchaDto: RecaptchaDto): Promise<ChallengeResultData> { | ||
return this.recaptchaChecker.check(recaptchaDto.token); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { IsNotEmpty, IsString } from 'class-validator'; | ||
|
||
export class RecaptchaDto { | ||
@IsString() | ||
@IsNotEmpty() | ||
token: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
declare let grecaptcha: ReCaptchaV2.ReCaptcha & { | ||
enterprise: ReCaptchaV2.ReCaptcha; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { AxiosInstance } from 'axios'; | ||
|
||
import { IRecaptchaGenerator } from '@pps-easy/recaptcha/contracts'; | ||
|
||
import { GeneratePPSPayload, IPPSCertificateApiService } from './pps-certificate-service.requirements'; | ||
|
||
export class PPSCertificateApiService implements IPPSCertificateApiService { | ||
public constructor(private readonly httpService: AxiosInstance, private readonly recaptchaGenerator: IRecaptchaGenerator) {} | ||
|
||
public async generate(payload: GeneratePPSPayload) { | ||
try { | ||
const recaptchaToken = await this.recaptchaGenerator.generate(); | ||
const response = await this.httpService.post('/api/pps/generate', { ...payload, recaptchaToken }); | ||
|
||
return response.data; | ||
} catch (error) { | ||
PPSCertificateApiService.handleApiError(error); | ||
throw error; | ||
} | ||
} | ||
|
||
private static handleApiError(error: unknown): void { | ||
console.error('Error generating PPS:', error); | ||
|
||
if (error instanceof Error && error.message.includes('CORS')) { | ||
throw new Error('CORS error: Communication problem with the server.'); | ||
} else { | ||
throw new Error('An error has occurred during certificate generation.'); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.