Skip to content

Commit 1c3ec93

Browse files
committed
feat(refresh token): add tests
1 parent a5f8422 commit 1c3ec93

File tree

3 files changed

+321
-25
lines changed

3 files changed

+321
-25
lines changed

src/jwt.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -177,15 +177,6 @@ export class JwtGuard<UserProvider extends JwtUserProviderContract<unknown>>
177177
async authenticateWithRefreshToken(): Promise<
178178
UserProvider[typeof symbols.PROVIDER_REAL_USER] & { currentToken: string }
179179
> {
180-
/**
181-
* Ensure the refresh token user provider is defined
182-
*/
183-
if (!this.#refreshTokenUserProvider) {
184-
throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
185-
guardDriverName: this.driverName,
186-
})
187-
}
188-
189180
/**
190181
* Avoid re-authentication when it has been done already
191182
* for the given request
@@ -195,6 +186,14 @@ export class JwtGuard<UserProvider extends JwtUserProviderContract<unknown>>
195186
}
196187
this.authenticationAttempted = true
197188

189+
/**
190+
* Ensure the refresh token user provider is defined
191+
*/
192+
if (!this.#refreshTokenUserProvider) {
193+
throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
194+
guardDriverName: this.driverName,
195+
})
196+
}
198197
/**
199198
* Ensure the auth header exists
200199
*/
@@ -218,9 +217,10 @@ export class JwtGuard<UserProvider extends JwtUserProviderContract<unknown>>
218217
const accessToken = await this.#refreshTokenUserProvider.verifyToken(new Secret(refreshToken))
219218

220219
/**
221-
* Fetch the user by user ID and save a reference to it
220+
* Fetch the user by user ID
222221
*/
223222
const providerUser = await this.#userProvider.findById(accessToken.tokenableId)
223+
console.log('providerUser', providerUser?.getOriginal())
224224
if (!providerUser) {
225225
throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
226226
guardDriverName: this.driverName,

tests/guard.spec.ts

Lines changed: 311 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import { createDatabase, createTables } from '../tests/helpers.js'
1212
import { tokensUserProvider } from '@adonisjs/auth/access_tokens'
1313

1414
test.group('Jwt guard | authenticate', () => {
15-
test('it should return a jwt token when user is authenticated with refresh token', async ({ assert }) => {
15+
test('it should return a jwt token when user is authenticated with refresh token', async ({
16+
assert,
17+
}) => {
1618
const ctx = new HttpContextFactory().create()
1719
const userProvider = new JwtFakeUserProvider()
1820

@@ -47,7 +49,7 @@ test.group('Jwt guard | authenticate', () => {
4749
static refreshTokens = DbAccessTokensProvider.forModel(User, {
4850
prefix: 'rt_',
4951
table: 'jwt_refresh_tokens',
50-
type: 'auth_token',
52+
type: 'jwt_refresh_token',
5153
tokenSecretLength: 40,
5254
})
5355
}
@@ -72,6 +74,312 @@ test.group('Jwt guard | authenticate', () => {
7274
assert.exists(refreshToken.value?.release())
7375
})
7476

77+
test('throw error when refresh token user provider is not defined', async ({ assert }) => {
78+
const ctx = new HttpContextFactory().create()
79+
const userProvider = new JwtFakeUserProvider()
80+
81+
const guard = new JwtGuard(ctx, userProvider, {
82+
secret: 'thisisasecret',
83+
})
84+
85+
const [result] = await Promise.allSettled([guard.authenticateWithRefreshToken()])
86+
assert.equal(result!.status, 'rejected')
87+
if (result!.status === 'rejected') {
88+
assert.instanceOf(result!.reason, errors.E_UNAUTHORIZED_ACCESS)
89+
}
90+
assert.isUndefined(guard.user)
91+
assert.throws(() => guard.getUserOrFail(), 'Unauthorized access')
92+
assert.isFalse(guard.isAuthenticated)
93+
assert.isTrue(guard.authenticationAttempted)
94+
})
95+
96+
test('throw error when refresh token authorization header is missing', async ({ assert }) => {
97+
const ctx = new HttpContextFactory().create()
98+
const userProvider = new JwtFakeUserProvider()
99+
const db = await createDatabase()
100+
await createTables(db)
101+
102+
const guard = new JwtGuard(ctx, userProvider, {
103+
secret: 'thisisasecret',
104+
refreshTokenUserProvider: tokensUserProvider({
105+
tokens: 'refreshTokens',
106+
async model() {
107+
return {
108+
default: User,
109+
}
110+
},
111+
}),
112+
})
113+
114+
class User extends BaseModel {
115+
@column({ isPrimary: true })
116+
declare id: number
117+
118+
@column()
119+
declare username: string
120+
121+
@column()
122+
declare email: string
123+
124+
@column()
125+
declare password: string
126+
127+
static refreshTokens = DbAccessTokensProvider.forModel(User, {
128+
prefix: 'rt_',
129+
table: 'jwt_refresh_tokens',
130+
type: 'jwt_refresh_token',
131+
tokenSecretLength: 40,
132+
})
133+
}
134+
135+
const [result] = await Promise.allSettled([guard.authenticateWithRefreshToken()])
136+
assert.equal(result!.status, 'rejected')
137+
if (result!.status === 'rejected') {
138+
assert.instanceOf(result!.reason, errors.E_UNAUTHORIZED_ACCESS)
139+
}
140+
assert.isUndefined(guard.user)
141+
assert.throws(() => guard.getUserOrFail(), 'Unauthorized access')
142+
assert.isFalse(guard.isAuthenticated)
143+
assert.isTrue(guard.authenticationAttempted)
144+
})
145+
146+
test('throw error when refresh token authorization header is invalid', async ({ assert }) => {
147+
const ctx = new HttpContextFactory().create()
148+
const userProvider = new JwtFakeUserProvider()
149+
const db = await createDatabase()
150+
await createTables(db)
151+
152+
const guard = new JwtGuard(ctx, userProvider, {
153+
secret: 'thisisasecret',
154+
refreshTokenUserProvider: tokensUserProvider({
155+
tokens: 'refreshTokens',
156+
async model() {
157+
return {
158+
default: User,
159+
}
160+
},
161+
}),
162+
})
163+
164+
class User extends BaseModel {
165+
@column({ isPrimary: true })
166+
declare id: number
167+
168+
@column()
169+
declare username: string
170+
171+
@column()
172+
declare email: string
173+
174+
@column()
175+
declare password: string
176+
177+
static refreshTokens = DbAccessTokensProvider.forModel(User, {
178+
prefix: 'rt_',
179+
table: 'jwt_refresh_tokens',
180+
type: 'jwt_refresh_token',
181+
tokenSecretLength: 40,
182+
})
183+
}
184+
185+
ctx.request.request.headers.authorization = `foo bar`
186+
const [result] = await Promise.allSettled([guard.authenticateWithRefreshToken()])
187+
assert.equal(result!.status, 'rejected')
188+
if (result!.status === 'rejected') {
189+
assert.instanceOf(result!.reason, errors.E_UNAUTHORIZED_ACCESS)
190+
}
191+
assert.isUndefined(guard.user)
192+
assert.throws(() => guard.getUserOrFail(), 'Unauthorized access')
193+
assert.isFalse(guard.isAuthenticated)
194+
assert.isTrue(guard.authenticationAttempted)
195+
})
196+
197+
test('throw error if the user authentication attempt has been made already', async ({
198+
assert,
199+
}) => {
200+
const ctx = new HttpContextFactory().create()
201+
const userProvider = new JwtFakeUserProvider()
202+
const db = await createDatabase()
203+
await createTables(db)
204+
205+
const guard = new JwtGuard(ctx, userProvider, {
206+
secret: 'thisisasecret',
207+
refreshTokenUserProvider: tokensUserProvider({
208+
tokens: 'refreshTokens',
209+
async model() {
210+
return {
211+
default: User,
212+
}
213+
},
214+
}),
215+
})
216+
217+
class User extends BaseModel {
218+
@column({ isPrimary: true })
219+
declare id: number
220+
221+
@column()
222+
declare username: string
223+
224+
@column()
225+
declare email: string
226+
227+
@column()
228+
declare password: string
229+
230+
static refreshTokens = DbAccessTokensProvider.forModel(User, {
231+
prefix: 'rt_',
232+
table: 'jwt_refresh_tokens',
233+
type: 'jwt_refresh_token',
234+
tokenSecretLength: 40,
235+
})
236+
}
237+
238+
const user = await User.create({
239+
240+
username: 'maxime',
241+
password: 'password',
242+
})
243+
244+
const refreshToken = await User.refreshTokens.create(user)
245+
246+
await assert.rejects(() => guard.authenticate(), 'Unauthorized access')
247+
ctx.request.request.headers.authorization = `Bearer ${refreshToken.value?.release()}`
248+
await assert.rejects(() => guard.authenticate(), 'Unauthorized access')
249+
assert.isUndefined(guard.user)
250+
assert.throws(() => guard.getUserOrFail(), 'Unauthorized access')
251+
assert.isFalse(guard.isAuthenticated)
252+
assert.isTrue(guard.authenticationAttempted)
253+
})
254+
255+
test('it should return the user when user is already authenticated with refresh token', async ({
256+
assert,
257+
}) => {
258+
const ctx = new HttpContextFactory().create()
259+
const userProvider = new JwtFakeUserProvider()
260+
261+
const db = await createDatabase()
262+
await createTables(db)
263+
264+
const guard = new JwtGuard(ctx, userProvider, {
265+
secret: 'thisisasecret',
266+
refreshTokenUserProvider: tokensUserProvider({
267+
tokens: 'refreshTokens',
268+
async model() {
269+
return {
270+
default: User,
271+
}
272+
},
273+
}),
274+
})
275+
276+
class User extends BaseModel {
277+
@column({ isPrimary: true })
278+
declare id: number
279+
280+
@column()
281+
declare username: string
282+
283+
@column()
284+
declare email: string
285+
286+
@column()
287+
declare password: string
288+
289+
static refreshTokens = DbAccessTokensProvider.forModel(User, {
290+
prefix: 'rt_',
291+
table: 'jwt_refresh_tokens',
292+
type: 'jwt_refresh_token',
293+
tokenSecretLength: 40,
294+
})
295+
}
296+
297+
const user = await User.create({
298+
299+
username: 'maxime',
300+
password: 'password',
301+
})
302+
const refreshToken = await User.refreshTokens.create(user)
303+
ctx.request.request.headers.authorization = `Bearer ${refreshToken.value?.release()}`
304+
await guard.authenticateWithRefreshToken()
305+
const authenticatedUser = await guard.authenticateWithRefreshToken()
306+
assert.isTrue(guard.isAuthenticated)
307+
assert.isTrue(guard.authenticationAttempted)
308+
assert.equal(guard.user, authenticatedUser)
309+
assert.deepEqual(guard.getUserOrFail(), authenticatedUser)
310+
assert.exists(authenticatedUser.currentToken)
311+
assert.exists(refreshToken.value?.release())
312+
})
313+
314+
test('throw error when the refresh token used belongs to a unknown user', async ({ assert }) => {
315+
const ctx = new HttpContextFactory().create()
316+
const userProvider = new JwtFakeUserProvider()
317+
const db = await createDatabase()
318+
await createTables(db)
319+
const guard = new JwtGuard(ctx, userProvider, {
320+
secret: 'thisisasecret',
321+
refreshTokenUserProvider: tokensUserProvider({
322+
tokens: 'refreshTokens',
323+
async model() {
324+
return {
325+
default: User,
326+
}
327+
},
328+
}),
329+
})
330+
331+
class User extends BaseModel {
332+
@column({ isPrimary: true })
333+
declare id: number
334+
335+
@column()
336+
declare username: string
337+
338+
@column()
339+
declare email: string
340+
341+
@column()
342+
declare password: string
343+
344+
static refreshTokens = DbAccessTokensProvider.forModel(User, {
345+
prefix: 'rt_',
346+
table: 'jwt_refresh_tokens',
347+
type: 'jwt_refresh_token',
348+
tokenSecretLength: 40,
349+
})
350+
}
351+
352+
await User.createMany([
353+
{
354+
355+
username: 'john',
356+
password: 'password',
357+
},
358+
{
359+
360+
username: 'jane',
361+
password: 'password',
362+
},
363+
])
364+
365+
const user = await User.create({
366+
367+
username: 'maxime',
368+
password: 'password',
369+
})
370+
const refreshToken = await User.refreshTokens.create(user)
371+
ctx.request.request.headers.authorization = `Bearer ${refreshToken.value?.release()}`
372+
const [result] = await Promise.allSettled([guard.authenticateWithRefreshToken()])
373+
assert.equal(result!.status, 'rejected')
374+
if (result!.status === 'rejected') {
375+
assert.instanceOf(result!.reason, errors.E_UNAUTHORIZED_ACCESS)
376+
}
377+
assert.isUndefined(guard.user)
378+
assert.throws(() => guard.getUserOrFail(), 'Unauthorized access')
379+
assert.isFalse(guard.isAuthenticated)
380+
assert.isTrue(guard.authenticationAttempted)
381+
})
382+
75383
test('it should return a token when user is authenticated', async ({ assert }) => {
76384
const ctx = new HttpContextFactory().create()
77385
const userProvider = new JwtFakeUserProvider()
@@ -414,9 +722,7 @@ test.group('Jwt guard | authenticate', () => {
414722
const ctx = new HttpContextFactory().create()
415723
const userProvider = new JwtFakeUserProvider()
416724
const user = await userProvider.findById(1)
417-
const token = await userProvider.createToken(user!.getOriginal(), 'thisisasecret', {
418-
expiresIn: '1h',
419-
})
725+
const token = await userProvider.createToken(user!.getOriginal(), 'thisisasecret')
420726

421727
const guard = new JwtGuard(ctx, userProvider, { secret: 'thisisasecret' })
422728
await assert.rejects(() => guard.authenticate(), 'Unauthorized access')

0 commit comments

Comments
 (0)