Skip to content

Commit eef4091

Browse files
authored
Merge pull request #2 from Iwayemi-Kehinde/cto/add-e2e-auth-refresh-tests
test: add e2e tests for token refresh endpoint
2 parents 4bff97a + 6e90288 commit eef4091

1 file changed

Lines changed: 161 additions & 0 deletions

File tree

test/e2e/modules/auth/auth.e2e-spec.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
22
import { INestApplication, ValidationPipe } from '@nestjs/common';
33
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
44
import * as request from 'supertest';
5+
import * as jwt from 'jsonwebtoken';
56
import { ConfigModule, ConfigService } from '@nestjs/config';
67
import { AuthModule } from '../../../../src/modules/auth/auth.module';
78
import { UsersModule } from '../../../../src/modules/users/users.module';
@@ -512,4 +513,164 @@ describe('AuthController (e2e)', () => {
512513
expect(new Date(session.expires_at).getTime()).toBeGreaterThan(Date.now());
513514
});
514515
});
516+
517+
describe('POST /auth/refresh', () => {
518+
it('should return new tokens on happy path (valid refresh token)', async () => {
519+
const keypair = createTestKeypair();
520+
const wallet = keypair.publicKey();
521+
testWallets.push(wallet);
522+
523+
// Complete auth flow to get refresh token
524+
const nonceResponse = await request(app.getHttpServer())
525+
.post('/auth/nonce')
526+
.send({ wallet })
527+
.expect(201);
528+
529+
const nonce = nonceResponse.body.nonce;
530+
const signature = signMessage(keypair, nonce);
531+
532+
const verifyResponse = await request(app.getHttpServer())
533+
.post('/auth/verify')
534+
.send({ wallet, nonce, signature })
535+
.expect(200);
536+
537+
const originalRefreshToken = verifyResponse.body.refreshToken;
538+
539+
// Use refresh token to get new tokens
540+
const refreshResponse = await request(app.getHttpServer())
541+
.post('/auth/refresh')
542+
.send({ refreshToken: originalRefreshToken })
543+
.expect(200);
544+
545+
expect(refreshResponse.body).toHaveProperty('accessToken');
546+
expect(refreshResponse.body).toHaveProperty('refreshToken');
547+
expect(refreshResponse.body).toHaveProperty('expiresIn');
548+
expect(refreshResponse.body).toHaveProperty('tokenType', 'Bearer');
549+
550+
// New tokens should be different from the original ones
551+
expect(refreshResponse.body.accessToken).not.toBe(verifyResponse.body.accessToken);
552+
expect(refreshResponse.body.refreshToken).not.toBe(originalRefreshToken);
553+
554+
// New access token should work for protected endpoints
555+
await request(app.getHttpServer())
556+
.get('/users/me')
557+
.set('Authorization', `Bearer ${refreshResponse.body.accessToken}`)
558+
.expect(200);
559+
});
560+
561+
it('should return 401 with an expired refresh token', async () => {
562+
const keypair = createTestKeypair();
563+
const wallet = keypair.publicKey();
564+
testWallets.push(wallet);
565+
566+
// Complete auth flow to create a user in the database
567+
const nonceResponse = await request(app.getHttpServer())
568+
.post('/auth/nonce')
569+
.send({ wallet })
570+
.expect(201);
571+
572+
const nonce = nonceResponse.body.nonce;
573+
const signature = signMessage(keypair, nonce);
574+
575+
await request(app.getHttpServer())
576+
.post('/auth/verify')
577+
.send({ wallet, nonce, signature })
578+
.expect(200);
579+
580+
// Create an expired refresh token signed with the same secret
581+
const configService = app.get(ConfigService);
582+
const refreshSecret = configService.get<string>('JWT_REFRESH_SECRET');
583+
const expiredRefreshToken = jwt.sign(
584+
{ wallet, type: 'refresh' },
585+
refreshSecret,
586+
{ expiresIn: '0s' },
587+
);
588+
589+
// Wait a moment to ensure the token is expired
590+
await new Promise(resolve => setTimeout(resolve, 100));
591+
592+
const response = await request(app.getHttpServer())
593+
.post('/auth/refresh')
594+
.send({ refreshToken: expiredRefreshToken })
595+
.expect(401);
596+
597+
expect(response.body).toHaveProperty('message');
598+
});
599+
600+
it('should return 401 on reuse of a refresh token (rotation detection)', async () => {
601+
const keypair = createTestKeypair();
602+
const wallet = keypair.publicKey();
603+
testWallets.push(wallet);
604+
605+
// Get refresh token via verify
606+
const nonceResponse = await request(app.getHttpServer())
607+
.post('/auth/nonce')
608+
.send({ wallet })
609+
.expect(201);
610+
611+
const nonce = nonceResponse.body.nonce;
612+
const signature = signMessage(keypair, nonce);
613+
614+
const verifyResponse = await request(app.getHttpServer())
615+
.post('/auth/verify')
616+
.send({ wallet, nonce, signature })
617+
.expect(200);
618+
619+
const refreshToken = verifyResponse.body.refreshToken;
620+
621+
// First use — should succeed
622+
await request(app.getHttpServer())
623+
.post('/auth/refresh')
624+
.send({ refreshToken })
625+
.expect(200);
626+
627+
// Second use with the same token — should fail (session was deleted)
628+
await request(app.getHttpServer())
629+
.post('/auth/refresh')
630+
.send({ refreshToken })
631+
.expect(401);
632+
});
633+
634+
it('should return 401 with a malformed refresh token', async () => {
635+
await request(app.getHttpServer())
636+
.post('/auth/refresh')
637+
.send({ refreshToken: 'invalid-token-string' })
638+
.expect(401);
639+
});
640+
641+
it('should return 401 with an empty refresh token', async () => {
642+
await request(app.getHttpServer())
643+
.post('/auth/refresh')
644+
.send({ refreshToken: '' })
645+
.expect(401);
646+
});
647+
648+
it('should return 401 when refresh token has wrong type (access token instead of refresh token)', async () => {
649+
const keypair = createTestKeypair();
650+
const wallet = keypair.publicKey();
651+
testWallets.push(wallet);
652+
653+
// Get an access token via verify
654+
const nonceResponse = await request(app.getHttpServer())
655+
.post('/auth/nonce')
656+
.send({ wallet })
657+
.expect(201);
658+
659+
const nonce = nonceResponse.body.nonce;
660+
const signature = signMessage(keypair, nonce);
661+
662+
const verifyResponse = await request(app.getHttpServer())
663+
.post('/auth/verify')
664+
.send({ wallet, nonce, signature })
665+
.expect(200);
666+
667+
const accessToken = verifyResponse.body.accessToken;
668+
669+
// Try to use access token as refresh token
670+
await request(app.getHttpServer())
671+
.post('/auth/refresh')
672+
.send({ refreshToken: accessToken })
673+
.expect(401);
674+
});
675+
});
515676
});

0 commit comments

Comments
 (0)