Skip to content

Commit 03ca400

Browse files
committed
refacto(refresh token)
1 parent 1c3ec93 commit 03ca400

File tree

4 files changed

+84
-24
lines changed

4 files changed

+84
-24
lines changed

src/define_config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { JwtGuardUser, JwtUserProviderContract } from './types.js'
44
import { JwtGuard } from './jwt.js'
55
import { Secret } from '@adonisjs/core/helpers'
66
import type { StringValue } from 'ms'
7+
import { AccessTokensUserProviderContract } from '@adonisjs/auth/types/access_tokens'
78

89
export function jwtGuard<UserProvider extends JwtUserProviderContract<unknown>>(config: {
910
provider: UserProvider
10-
refreshTokenUserProvider?: any
11+
refreshTokenUserProvider?: AccessTokensUserProviderContract<UserProvider>
1112
tokenName?: string
1213
tokenExpiresIn?: number | StringValue
1314
useCookies?: boolean

src/jwt.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ import type { HttpContext } from '@adonisjs/core/http'
44
import jwt from 'jsonwebtoken'
55
import { JwtUserProviderContract, JwtGuardOptions } from './types.js'
66
import { Secret } from '@adonisjs/core/helpers'
7+
import { AccessTokensUserProviderContract } from '@adonisjs/auth/types/access_tokens'
78

89
export class JwtGuard<UserProvider extends JwtUserProviderContract<unknown>>
910
implements GuardContract<UserProvider[typeof symbols.PROVIDER_REAL_USER]>
1011
{
1112
#ctx: HttpContext
1213
#userProvider: UserProvider
13-
#refreshTokenUserProvider?: any
14+
#refreshTokenUserProvider?: AccessTokensUserProviderContract<
15+
UserProvider[typeof symbols.PROVIDER_REAL_USER]
16+
>
1417
#options: JwtGuardOptions<UserProvider[typeof symbols.PROVIDER_REAL_USER]>
1518
#tokenName: string
1619

@@ -216,30 +219,47 @@ export class JwtGuard<UserProvider extends JwtUserProviderContract<unknown>>
216219

217220
const accessToken = await this.#refreshTokenUserProvider.verifyToken(new Secret(refreshToken))
218221

222+
if (!accessToken) {
223+
throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
224+
guardDriverName: this.driverName,
225+
})
226+
}
227+
219228
/**
220229
* Fetch the user by user ID
221230
*/
222-
const providerUser = await this.#userProvider.findById(accessToken.tokenableId)
223-
console.log('providerUser', providerUser?.getOriginal())
231+
const providerUser = await this.#refreshTokenUserProvider.findById(accessToken.tokenableId)
224232
if (!providerUser) {
225233
throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
226234
guardDriverName: this.driverName,
227235
})
228236
}
229237

230-
/**
231-
* Generate a new JWT token for the user
232-
*/
233-
const { token }: any = await this.generate(providerUser.getOriginal())
234238
this.isAuthenticated = true
235239
this.user = providerUser.getOriginal() as UserProvider[typeof symbols.PROVIDER_REAL_USER] & {
236240
currentToken: string
237241
}
238242

239-
if (!this.#options.useCookies) {
240-
this.user!.currentToken = token
243+
/**
244+
* Delete the refresh token from the database
245+
*/
246+
const isDeleted = await this.#refreshTokenUserProvider.invalidateToken(new Secret(refreshToken))
247+
if (!isDeleted) {
248+
throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
249+
guardDriverName: this.driverName,
250+
})
241251
}
242252

253+
const newRefreshToken = await this.#refreshTokenUserProvider.createToken(this.user)
254+
255+
if (!newRefreshToken.value) {
256+
throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
257+
guardDriverName: this.driverName,
258+
})
259+
}
260+
261+
this.user.currentToken = newRefreshToken.value?.release()
262+
243263
return this.getUserOrFail()
244264
}
245265

src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { symbols } from '@adonisjs/auth'
22
import type { StringValue } from 'ms'
3+
import { AccessTokensUserProviderContract } from '@adonisjs/auth/types/access_tokens'
34

45
/**
56
* The bridge between the User provider and the
@@ -46,7 +47,7 @@ export type BaseJwtContent = {
4647

4748
export type JwtGuardOptions<RealUser extends any = unknown> = {
4849
secret: string
49-
refreshTokenUserProvider?: any
50+
refreshTokenUserProvider?: AccessTokensUserProviderContract<RealUser>
5051
tokenName?: string
5152
expiresIn?: number | StringValue
5253
useCookies?: boolean

tests/guard.spec.ts

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -349,25 +349,13 @@ test.group('Jwt guard | authenticate', () => {
349349
})
350350
}
351351

352-
await User.createMany([
353-
{
354-
355-
username: 'john',
356-
password: 'password',
357-
},
358-
{
359-
360-
username: 'jane',
361-
password: 'password',
362-
},
363-
])
364-
365352
const user = await User.create({
366353
367354
username: 'maxime',
368355
password: 'password',
369356
})
370357
const refreshToken = await User.refreshTokens.create(user)
358+
await user.delete()
371359
ctx.request.request.headers.authorization = `Bearer ${refreshToken.value?.release()}`
372360
const [result] = await Promise.allSettled([guard.authenticateWithRefreshToken()])
373361
assert.equal(result!.status, 'rejected')
@@ -380,6 +368,56 @@ test.group('Jwt guard | authenticate', () => {
380368
assert.isTrue(guard.authenticationAttempted)
381369
})
382370

371+
test('throw error when the refresh token is invalid', async ({ assert }) => {
372+
const ctx = new HttpContextFactory().create()
373+
const userProvider = new JwtFakeUserProvider()
374+
const db = await createDatabase()
375+
await createTables(db)
376+
const guard = new JwtGuard(ctx, userProvider, {
377+
secret: 'thisisasecret',
378+
refreshTokenUserProvider: tokensUserProvider({
379+
tokens: 'refreshTokens',
380+
async model() {
381+
return {
382+
default: User,
383+
}
384+
},
385+
}),
386+
})
387+
388+
class User extends BaseModel {
389+
@column({ isPrimary: true })
390+
declare id: number
391+
392+
@column()
393+
declare username: string
394+
395+
@column()
396+
declare email: string
397+
398+
@column()
399+
declare password: string
400+
401+
static refreshTokens = DbAccessTokensProvider.forModel(User, {
402+
prefix: 'rt_',
403+
table: 'jwt_refresh_tokens',
404+
type: 'jwt_refresh_token',
405+
tokenSecretLength: 40,
406+
})
407+
}
408+
409+
ctx.request.request.headers.authorization = `Bearer abcd`
410+
const [result] = await Promise.allSettled([guard.authenticateWithRefreshToken()])
411+
assert.equal(result!.status, 'rejected')
412+
if (result!.status === 'rejected') {
413+
assert.instanceOf(result!.reason, errors.E_UNAUTHORIZED_ACCESS)
414+
}
415+
assert.isUndefined(guard.user)
416+
assert.throws(() => guard.getUserOrFail(), 'Unauthorized access')
417+
assert.isFalse(guard.isAuthenticated)
418+
assert.isTrue(guard.authenticationAttempted)
419+
})
420+
383421
test('it should return a token when user is authenticated', async ({ assert }) => {
384422
const ctx = new HttpContextFactory().create()
385423
const userProvider = new JwtFakeUserProvider()

0 commit comments

Comments
 (0)