Skip to content

Commit

Permalink
fire event after new access token generated from refresh token. Updat…
Browse files Browse the repository at this point in the history
…e docs
  • Loading branch information
duysolo committed Nov 19, 2022
1 parent ecc996f commit a4ea357
Show file tree
Hide file tree
Showing 29 changed files with 374 additions and 173 deletions.
69 changes: 68 additions & 1 deletion README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,28 @@ export class SampleAuthRepository implements AuthRepository<string, AuthDto> {
mergeMap(() => this.getAuthUserByUsername(username))
)
}

public getAuthUserByAccessToken(
accessToken: string,
jwtPayload: IJwtPayload
): Observable<IAuthUserEntity | undefined> {
/**
* You can check the token if it's stored in database.
*/

return this.getAuthUserByUsername(jwtPayload.username)
}

public getAuthUserByRefreshToken(
refreshToken: string,
jwtPayload: IJwtPayload
): Observable<IAuthUserEntity | undefined> {
/**
* You can check the token if it's stored in database.
*/

return this.getAuthUserByUsername(jwtPayload.username)
}
}
```

Expand Down Expand Up @@ -360,6 +382,28 @@ export class SampleAuthRepository implements AuthRepository<IPbkdf2Hash, AuthDto
mergeMap(() => this.getAuthUserByUsername(username))
)
}

public getAuthUserByAccessToken(
accessToken: string,
jwtPayload: IJwtPayload
): Observable<IAuthUserEntity<IPbkdf2Hash> | undefined> {
/**
* You can check the token if it's stored in database.
*/

return this.getAuthUserByUsername(jwtPayload.username)
}

public getAuthUserByRefreshToken(
refreshToken: string,
jwtPayload: IJwtPayload
): Observable<IAuthUserEntity<IPbkdf2Hash> | undefined> {
/**
* You can check the token if it's stored in database.
*/

return this.getAuthUserByUsername(jwtPayload.username)
}
}
```

Expand Down Expand Up @@ -522,7 +566,9 @@ export class AppController {

Triggered when user logged in successfully.

Sample usages
You can store the relevant access/refresh tokens in database using this event.

#### Sample usages

```typescript
import { UserLoggedInEvent } from '@mercury-labs/nest-auth'
Expand All @@ -544,6 +590,27 @@ export class UserLoggedInEventHandler implements IEventHandler<UserLoggedInEvent
}
```

### `AccessTokenGeneratedFromRefreshTokenEvent`

Triggered when an new access token is generated from refresh token.

You can store the relevant new access token in database using this event.

#### Sample usages

```typescript
import { AccessTokenGeneratedFromRefreshTokenEvent } from '@mercury-labs/nest-auth'
import { EventsHandler, IEventHandler } from '@nestjs/cqrs'
import { delay, lastValueFrom, of, tap } from 'rxjs'

@EventsHandler(AccessTokenGeneratedFromRefreshTokenEvent)
export class UserLoggedInEventHandler implements IEventHandler<AccessTokenGeneratedFromRefreshTokenEvent> {
public async handle(event: AccessTokenGeneratedFromRefreshTokenEvent): Promise<void> {
console.log('AccessTokenGeneratedFromRefreshTokenEvent', event)
}
}
```

#### Notes:

- You must install
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/auth.module.express.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IAuthResponse, IRefreshTokenAuthResponse } from '../domain'
import { IAuthWithTokenResponse, IRefreshTokenAuthResponse } from '../domain'
import { e2eTestsSetup } from './cases/e2e-tests'
import {
createTestAuthApplicationExpress,
Expand All @@ -24,7 +24,7 @@ describe('AuthModule (e2e) - Express Adaptor', () => {
path: '/auth/login',
body,
}).then((response) => {
const parsedResponseBody: IAuthResponse = JSON.parse(
const parsedResponseBody: IAuthWithTokenResponse = JSON.parse(
response.text || '{}'
)

Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/auth.module.fastify.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NestFastifyApplication } from '@nestjs/platform-fastify'
import { IAuthResponse, IRefreshTokenAuthResponse } from '../domain'
import { IAuthWithTokenResponse, IRefreshTokenAuthResponse } from '../domain'
import { e2eTestsSetup } from './cases/e2e-tests'
import {
createTestAuthApplicationFastify,
Expand All @@ -25,7 +25,7 @@ describe('AuthModule (e2e) - Fastify Adaptor', () => {
path: '/auth/login',
body,
}).then((response) => {
const parsedResponseBody: IAuthResponse = response.json() || {}
const parsedResponseBody: IAuthWithTokenResponse = response.json() || {}

return {
statusCode: response.statusCode,
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/cases/e2e-tests.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { HttpStatus, INestApplication } from '@nestjs/common'
import {
IAuthDefinitions,
IAuthResponse,
IAuthUserEntityForResponse,
IAuthWithTokenResponse,
IRefreshTokenAuthResponse,
} from '../../domain'
import {
Expand All @@ -13,7 +13,7 @@ import {
UserLoggedInEventHandler,
} from '../helpers'

interface ITokenResponse<T = IAuthResponse> {
interface ITokenResponse<T = IAuthWithTokenResponse> {
statusCode: HttpStatus
authResponse: T
headers: Record<string, any>
Expand Down
27 changes: 24 additions & 3 deletions src/__tests__/helpers/sample.auth.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { asyncScheduler, map, Observable, scheduled } from 'rxjs'
import {
AuthDto,
AuthRepository,
IAuthUserEntity,
IAuthUserEntity, IJwtPayload,
InjectPasswordHasher,
PasswordHasherService,
} from '../../domain'
Expand All @@ -14,8 +14,7 @@ export class SampleAuthRepository implements AuthRepository<string, AuthDto> {
public constructor(
@InjectPasswordHasher()
protected readonly hasher: PasswordHasherService
) {
}
) {}

public getAuthUserByUsername(
username: string
Expand Down Expand Up @@ -49,4 +48,26 @@ export class SampleAuthRepository implements AuthRepository<string, AuthDto> {
})
)
}

public getAuthUserByAccessToken(
accessToken: string,
jwtPayload: IJwtPayload
): Observable<IAuthUserEntity | undefined> {
/**
* You can check the token if it's stored in database.
*/

return this.getAuthUserByUsername(jwtPayload.username)
}

public getAuthUserByRefreshToken(
refreshToken: string,
jwtPayload: IJwtPayload
): Observable<IAuthUserEntity | undefined> {
/**
* You can check the token if it's stored in database.
*/

return this.getAuthUserByUsername(jwtPayload.username)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { IJwtPayload } from '../../domain'

export class GetCurrentUserByAccessTokenQuery {
public constructor(public input: IJwtPayload) {}
public constructor(
public accessToken: string,
public jwtPayload: IJwtPayload
) {}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export class GetCurrentUserByRefreshTokenQuery {
public constructor(public token: string) {}
public constructor(public refreshToken: string) {}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import { UnauthorizedException } from '@nestjs/common'
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'
import { lastValueFrom } from 'rxjs'
import {
GetUserByJwtTokenAction,
IAuthUserEntityForResponse,
} from '../../../domain'
import { GetUserByJwtTokenAction, IAuthResponse } from '../../../domain'
import { GetCurrentUserByAccessTokenQuery } from '../get-current-user-by-access-token.query'

@QueryHandler(GetCurrentUserByAccessTokenQuery)
export class GetCurrentUserByAccessTokenQueryHandler
implements
IQueryHandler<
GetCurrentUserByAccessTokenQuery,
IAuthUserEntityForResponse | undefined
>
{
public constructor(protected readonly action: GetUserByJwtTokenAction) {}
implements IQueryHandler<GetCurrentUserByAccessTokenQuery, IAuthResponse | undefined> {
public constructor(protected readonly action: GetUserByJwtTokenAction) {
}

public async execute(query: GetCurrentUserByAccessTokenQuery) {
try {
return await lastValueFrom(this.action.handle(query.input))
return await lastValueFrom(this.action.handle(query))
} catch (error) {
throw new UnauthorizedException()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,21 @@
import { UnauthorizedException } from '@nestjs/common'
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'
import { lastValueFrom, map } from 'rxjs'
import {
AuthRepository,
hideRedactedFields,
IAuthDefinitions,
IAuthUserEntityForResponse,
InjectAuthDefinitions,
TokenService,
} from '../../../domain'
import { lastValueFrom } from 'rxjs'
import { GetUserByRefreshTokenAction, IAuthResponse } from '../../../domain'
import { GetCurrentUserByRefreshTokenQuery } from '../get-current-user-by-refresh-token.query'

@QueryHandler(GetCurrentUserByRefreshTokenQuery)
export class GetCurrentUserByRefreshTokenQueryHandler
implements
IQueryHandler<
GetCurrentUserByRefreshTokenQuery,
IAuthUserEntityForResponse | undefined
>
IQueryHandler<GetCurrentUserByRefreshTokenQuery, IAuthResponse | undefined>
{
public constructor(
@InjectAuthDefinitions()
protected readonly authDefinitions: IAuthDefinitions,
protected readonly authRepository: AuthRepository,
protected readonly jwtService: TokenService
) {}
public constructor(protected readonly action: GetUserByRefreshTokenAction) {}

public async execute(query: GetCurrentUserByRefreshTokenQuery) {
const jwtPayload = query.token
? this.jwtService.decodeRefreshToken(query.token)
: undefined

return jwtPayload
? await lastValueFrom(
this.authRepository
.getAuthUserByUsername(jwtPayload.username)
.pipe(map(hideRedactedFields(this.authDefinitions.redactedFields)))
)
: undefined
try {
return await lastValueFrom(this.action.handle(query))
} catch (error) {
throw new UnauthorizedException()
}
}
}
4 changes: 3 additions & 1 deletion src/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
AuthRefreshTokenGuard,
AuthRepository,
BcryptPasswordHasherService,
GetUserByJwtTokenAction,
GetUserByJwtTokenAction, GetUserByRefreshTokenAction,
GraphqlAuthJwtGuard,
GraphqlAuthRefreshTokenGuard,
IAuthDefinitions,
Expand Down Expand Up @@ -127,6 +127,7 @@ export class AuthModule implements NestModule {
GetCurrentUserByRefreshTokenQueryHandler,

GetUserByJwtTokenAction,
GetUserByRefreshTokenAction,
LocalLoginAction,
ParseJwtTokenAction,

Expand Down Expand Up @@ -192,6 +193,7 @@ export class AuthModule implements NestModule {
TokenService,

GetUserByJwtTokenAction,
GetUserByRefreshTokenAction,
LocalLoginAction,
ParseJwtTokenAction,

Expand Down
23 changes: 14 additions & 9 deletions src/domain/actions/__tests__/local-login.action.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,27 @@ describe('LocalLoginAction', () => {
expect(action).toBeDefined()
})

it('should able to login using username/password', async function() {
it('should able to login using username/password', async function () {
await lastValueFrom(
action.handle(correctUserInfo).pipe(
tap((res) => {
expect(res.username).toEqual(correctUserInfo.username)
tap(({ userData }) => {
expect(userData.username).toEqual(correctUserInfo.username)
})
)
)
})

it('should able to login using impersonate', async function() {
it('should able to login using impersonate', async function () {
await lastValueFrom(
action.handle(correctUserInfoImpersonate).pipe(
tap((res) => {
expect(res.username).toEqual(correctUserInfo.username)
tap(({ userData }) => {
expect(userData.username).toEqual(correctUserInfo.username)
})
)
)
})

it('should throw error if invalid credentials', async function() {
it('should throw error if invalid credentials', async function () {
await lastValueFrom(
action.handle(invalidUserInfo).pipe(
catchError((error) => {
Expand All @@ -71,7 +71,7 @@ describe('LocalLoginAction', () => {
)
})

it('should handle UserLoggedInEvent', async function() {
it('should handle UserLoggedInEvent', async function () {
const eventBus: EventBus = action['eventBus']

const spyEventBus = jest.spyOn(eventBus, 'publish')
Expand All @@ -81,7 +81,12 @@ describe('LocalLoginAction', () => {
tap((res) => {
expect(spyEventBus).toHaveBeenCalledTimes(1)
expect(spyEventBus).toHaveBeenCalledWith(
new UserLoggedInEvent(res, false, correctUserInfo)
new UserLoggedInEvent(
res.userData,
false,
correctUserInfo,
res.token
)
)
})
)
Expand Down
Loading

0 comments on commit a4ea357

Please sign in to comment.