-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
handle multiple tokenization strategies
- Loading branch information
Showing
9 changed files
with
219 additions
and
43 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
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
49 changes: 29 additions & 20 deletions
49
api/src/modules/authentication/strategies/jwt.strategy.ts
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 |
---|---|---|
@@ -1,39 +1,48 @@ | ||
import { Injectable, UnauthorizedException } from '@nestjs/common'; | ||
import { PassportStrategy } from '@nestjs/passport'; | ||
import * as config from 'config'; | ||
import { | ||
AuthenticationService, | ||
JwtDataPayload, | ||
} from 'modules/authentication/authentication.service'; | ||
|
||
import { ExtractJwt, Strategy } from 'passport-jwt'; | ||
import { JwtDataPayload } from 'modules/authentication/authentication.service'; | ||
import { getSecretByTokenType } from 'modules/authentication/utils/authentication.utils'; | ||
import { User } from 'modules/users/user.entity'; | ||
import { UserRepository } from 'modules/users/user.repository'; | ||
import * as jwt from 'jsonwebtoken'; | ||
|
||
/** | ||
* @todo: We are handling different token strategies by using secretOrKeyProvider, and the static | ||
* getSecretFromToken method. This is not ideal, explore how to override global at route handler level | ||
* other global or controller level guards | ||
*/ | ||
|
||
@Injectable() | ||
export class JwtStrategy extends PassportStrategy(Strategy) { | ||
constructor( | ||
private readonly authenticationService: AuthenticationService, | ||
|
||
private readonly userRepository: UserRepository, | ||
) { | ||
constructor(private readonly userRepo: UserRepository) { | ||
super({ | ||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), | ||
secretOrKey: config.get('auth.jwt.secret'), | ||
secretOrKeyProvider: JwtStrategy.getSecretFromToken, | ||
}); | ||
} | ||
|
||
/** | ||
* Validate that the email in the JWT payload's `sub` property matches that of | ||
* an existing user. | ||
*/ | ||
public async validate({ sub: email }: JwtDataPayload): Promise<User> { | ||
const user: User | null = await this.userRepository.findByEmail(email); | ||
|
||
async validate(payload: JwtDataPayload): Promise<any> { | ||
const { sub: email } = payload; | ||
const user: User | null = await this.userRepo.findByEmail(email); | ||
if (!user) { | ||
throw new UnauthorizedException(); | ||
} | ||
|
||
return user; | ||
} | ||
|
||
static getSecretFromToken( | ||
req: Request, | ||
rawJwtToken: string, | ||
done: any, | ||
): void { | ||
try { | ||
const { tokenType } = jwt.decode(rawJwtToken) as JwtDataPayload; | ||
const secret: string = getSecretByTokenType(tokenType); | ||
|
||
done(null, secret); | ||
} catch (e) { | ||
throw new UnauthorizedException(); | ||
} | ||
} | ||
} |
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
113 changes: 113 additions & 0 deletions
113
api/test/e2e/tokenization-strategies/tokenization-strategies.spec.ts
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 ApplicationManager, { | ||
TestApplication, | ||
} from '../../utils/application-manager'; | ||
import { | ||
AuthenticationService, | ||
TOKEN_TYPE, | ||
} from '../../../src/modules/authentication/authentication.service'; | ||
import * as request from 'supertest'; | ||
import { HttpStatus } from '@nestjs/common'; | ||
|
||
describe('Password recovery tests (e2e)', () => { | ||
let testApplication: TestApplication; | ||
let authenticationService: AuthenticationService; | ||
|
||
beforeAll(async () => { | ||
testApplication = await ApplicationManager.init(); | ||
authenticationService = testApplication.get<AuthenticationService>( | ||
AuthenticationService, | ||
); | ||
}); | ||
|
||
afterAll(async () => { | ||
await testApplication.close(); | ||
}); | ||
|
||
describe('Tokenization strategies tests (e2e)', () => { | ||
test( | ||
'Given I have a account activation token,' + | ||
'But I use it to reset the password,' + | ||
' Then I should not be authorized', | ||
async () => { | ||
const accountActivationToken: string = authenticationService.signToken( | ||
'[email protected]', | ||
{ tokenType: TOKEN_TYPE.ACCOUNT_ACTIVATION }, | ||
); | ||
|
||
await request(testApplication.getHttpServer()) | ||
.post('/api/v1/users/me/password/reset') | ||
.set('Authorization', `Bearer ${accountActivationToken}`) | ||
.send({ newPassword: `[email protected]` }) | ||
.expect(HttpStatus.UNAUTHORIZED); | ||
}, | ||
); | ||
test( | ||
'Given I have a password reset token,' + | ||
'But I use it to activate my account,' + | ||
' Then I should not be authorized', | ||
async () => { | ||
const passwordResetToken: string = authenticationService.signToken( | ||
'[email protected]', | ||
{ tokenType: TOKEN_TYPE.PASSWORD_RESET }, | ||
); | ||
|
||
await request(testApplication.getHttpServer()) | ||
.post('/auth/validate-account') | ||
.set('Authorization', `Bearer ${passwordResetToken}`) | ||
.send({ newPassword: `[email protected]` }) | ||
.expect(HttpStatus.UNAUTHORIZED); | ||
}, | ||
); | ||
test( | ||
'Given I have a login token,' + | ||
'But I use it to activate my account, And to reset my password' + | ||
' Then I should not be authorized', | ||
async () => { | ||
const loginToken: string = authenticationService.signToken( | ||
'[email protected]', | ||
{ tokenType: TOKEN_TYPE.GENERAL }, | ||
); | ||
|
||
await request(testApplication.getHttpServer()) | ||
.post('/auth/validate-account') | ||
.set('Authorization', `Bearer ${loginToken}`) | ||
.send({ newPassword: `[email protected]` }) | ||
.expect(HttpStatus.UNAUTHORIZED); | ||
|
||
await request(testApplication.getHttpServer()) | ||
.post('/api/v1/users/me/password/reset') | ||
.set('Authorization', `Bearer ${loginToken}`) | ||
.send({ newPassword: `[email protected]` }) | ||
.expect(HttpStatus.UNAUTHORIZED); | ||
}, | ||
); | ||
test( | ||
'Given I have a account activation token, or a password reset token' + | ||
'When I use it to get data from the API' + | ||
' Then I should not be authorized', | ||
async () => { | ||
const accountActivationToken: string = authenticationService.signToken( | ||
'[email protected]', | ||
{ tokenType: TOKEN_TYPE.ACCOUNT_ACTIVATION }, | ||
); | ||
|
||
const passwordResetToken: string = authenticationService.signToken( | ||
'[email protected]', | ||
{ tokenType: TOKEN_TYPE.PASSWORD_RESET }, | ||
); | ||
|
||
await request(testApplication.getHttpServer()) | ||
.get('/api/v1/materials') | ||
.set('Authorization', `Bearer ${accountActivationToken}`) | ||
.send({ newPassword: `[email protected]` }) | ||
.expect(HttpStatus.UNAUTHORIZED); | ||
|
||
await request(testApplication.getHttpServer()) | ||
.get('/api/v1/materials') | ||
.set('Authorization', `Bearer ${passwordResetToken}`) | ||
.send({ newPassword: `[email protected]` }) | ||
.expect(HttpStatus.UNAUTHORIZED); | ||
}, | ||
); | ||
}); | ||
}); |
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