Skip to content

Commit

Permalink
Session test refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
corbadoman committed Oct 2, 2024
1 parent 5b00e51 commit 192a90c
Showing 1 changed file with 60 additions and 95 deletions.
155 changes: 60 additions & 95 deletions tests/unit/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import SessionService from '../../src/services/sessionService';

const app = express();
const PORT = 8081;
const TEST_USER_ID = '12345';
const TEST_USER_FULL_NAME = 'Test Name';

let validPrivateKey: KeyLike;
let invalidPrivateKey: KeyLike;
Expand All @@ -17,6 +19,7 @@ let publicKeyJwk: any;
async function initializeKeys() {
const { privateKey: key, publicKey } = await generateKeyPair('RS256');
validPrivateKey = key;

const { privateKey: invalidKey } = await generateKeyPair('RS256');
invalidPrivateKey = invalidKey;
publicKeyJwk = await exportJWK(publicKey);
Expand All @@ -41,16 +44,16 @@ async function startJWKSserver() {
async function generateJWT(
issuer: string,
expiresIn: number,
userId: string,
fullName: string,
notBefore: number,
privateKey: KeyLike,
): Promise<string> {
return await new SignJWT({
sub: userId,
name: fullName,
sub: TEST_USER_ID,
name: TEST_USER_FULL_NAME,
iss: issuer,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + expiresIn,
nbf: Math.floor(Date.now() / 1000) + notBefore,
})
.setProtectedHeader({
alg: 'RS256',
Expand All @@ -59,16 +62,18 @@ async function generateJWT(
.sign(privateKey);
}

function createSessionService(issuer: string): SessionService {
return new SessionService(
'cbo_short_session',
issuer,
`http://localhost:${PORT}/jwks`,
10,
'pro-1',
);
}

describe('Session Service Unit Tests', () => {
let server: http.Server;
let sessionService: SessionService;

const TEST_USER_ID = '12345';
const TEST_FULL_NAME = 'Test Name';
const TEST_ISSUER = 'https://pro-2.frontendapi.corbado.io';
const JWKS_URI = `http://localhost:${PORT}/jwks`;
const SHORT_SESSION_COOKIE_NAME = 'short_session_cookie';
const PROJECT_ID = 'pro-2';

beforeAll(async () => {
server = await startJWKSserver();
Expand All @@ -78,41 +83,42 @@ describe('Session Service Unit Tests', () => {
server.close();
});

beforeEach(async () => {
sessionService = new SessionService(SHORT_SESSION_COOKIE_NAME, TEST_ISSUER, JWKS_URI, 10, PROJECT_ID);
});

test('should throw error if required parameters are missing in constructor', () => {
expect(() => new SessionService('', TEST_ISSUER, JWKS_URI, 10, PROJECT_ID)).toThrow('Required parameter is empty');
expect(() => new SessionService(SHORT_SESSION_COOKIE_NAME, '', JWKS_URI, 10, PROJECT_ID)).toThrow(
expect(() => new SessionService('', 'https://pro-1.frontendapi.cloud.corbado.io', `http://localhost:${PORT}/jwks`, 10, 'pro-1')).toThrow(
'Required parameter is empty',
);
expect(() => new SessionService(SHORT_SESSION_COOKIE_NAME, TEST_ISSUER, '', 10, PROJECT_ID)).toThrow(
expect(() => new SessionService('cbo_short_session', '', `http://localhost:${PORT}/jwks`, 10, 'pro-1')).toThrow(
'Required parameter is empty',
);
expect(() => new SessionService('cbo_short_session', 'https://pro-1.frontendapi.cloud.corbado.io', '', 10, 'pro-1')).toThrow(
'Required parameter is empty',
);
});

test('should throw ValidationError if short session is empty', async () => {
const shortSession = '';
await expect(sessionService.validateToken(shortSession)).rejects.toThrow(BaseError);
await expect(sessionService.validateToken(shortSession)).rejects.toHaveProperty(
const sessionService = createSessionService('https://pro-1.frontendapi.cloud.corbado.io')

await expect(sessionService.validateToken('')).rejects.toThrow(BaseError);
await expect(sessionService.validateToken('')).rejects.toHaveProperty(
'statusCode',
httpStatusCodes.EMPTY_STRING.code,
);
});

test('should throw ValidationError if short session is too short', async () => {
const shortSession = 'short';
await expect(sessionService.validateToken(shortSession)).rejects.toThrow(ValidationError);
await expect(sessionService.validateToken(shortSession)).rejects.toHaveProperty(
const sessionService = createSessionService('https://pro-1.frontendapi.cloud.corbado.io')

await expect(sessionService.validateToken('short')).rejects.toThrow(ValidationError);
await expect(sessionService.validateToken('short')).rejects.toHaveProperty(
'name',
ValidationErrorNames.InvalidShortSession,
);
});

test('should throw ValidationError if jwt has an invalid signature', async () => {
const shortSession =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtpZDEyMyJ9.eyJpc3MiOiJodHRwczovL2F1dGguYWNtZS5jb20iLCJpYXQiOjE3MjY0OTE4MDcsImV4cCI6MTcyNjQ5MTkwNywibmJmIjoxNzI2NDkxNzA3LCJzdWIiOiJ1c3ItMTIzNDU2Nzg5MCIsIm5hbWUiOiJuYW1lIiwiZW1haWwiOiJlbWFpbCIsInBob25lX251bWJlciI6InBob25lTnVtYmVyIiwib3JpZyI6Im9yaWcifQ.invalid';
const sessionService = createSessionService('https://pro-1.frontendapi.cloud.corbado.io')

const shortSession = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtpZDEyMyJ9.eyJpc3MiOiJodHRwczovL2F1dGguYWNtZS5jb20iLCJpYXQiOjE3MjY0OTE4MDcsImV4cCI6MTcyNjQ5MTkwNywibmJmIjoxNzI2NDkxNzA3LCJzdWIiOiJ1c3ItMTIzNDU2Nzg5MCIsIm5hbWUiOiJuYW1lIiwiZW1haWwiOiJlbWFpbCIsInBob25lX251bWJlciI6InBob25lTnVtYmVyIiwib3JpZyI6Im9yaWcifQ.invalid';
await expect(sessionService.validateToken(shortSession)).rejects.toThrow(ValidationError);
await expect(sessionService.validateToken(shortSession)).rejects.toHaveProperty(
'name',
Expand All @@ -121,25 +127,16 @@ describe('Session Service Unit Tests', () => {
});

test('should throw ValidationError using invalid private key', async () => {
const jwt = await generateJWT(TEST_ISSUER, 600, TEST_USER_ID, TEST_FULL_NAME, invalidPrivateKey);
const sessionService = createSessionService('https://pro-1.frontendapi.cloud.corbado.io')
const jwt = await generateJWT('https://pro-1.frontendapi.cloud.corbado.io', 600, 0, invalidPrivateKey);

await expect(sessionService.validateToken(jwt)).rejects.toThrow(ValidationError);
await expect(sessionService.validateToken(jwt)).rejects.toHaveProperty('name', ValidationErrorNames.JWTInvalid);
});

test('should throw ValidationError if JWT is not yet valid (nbf claim in the future)', async () => {
const notBeforeTime = Math.floor(Date.now() / 1000) + 600;
const jwt = await new SignJWT({
iss: TEST_ISSUER,
sub: TEST_USER_ID,
name: TEST_FULL_NAME,
nbf: notBeforeTime,
})
.setProtectedHeader({
alg: 'RS256',
kid: 'kid123',
})
.sign(validPrivateKey);
const sessionService = createSessionService('https://pro-1.frontendapi.cloud.corbado.io')
const jwt = await generateJWT('https://pro-1.frontendapi.cloud.corbado.io', 600, 600, validPrivateKey);

await expect(sessionService.validateToken(jwt)).rejects.toThrow(ValidationError);
await expect(sessionService.validateToken(jwt)).rejects.toHaveProperty(
Expand All @@ -149,93 +146,61 @@ describe('Session Service Unit Tests', () => {
});

test('should throw ValidationError using an expired JWT', async () => {
const jwt = await generateJWT(TEST_ISSUER, -600, TEST_USER_ID, TEST_FULL_NAME, validPrivateKey);
const sessionService = createSessionService('https://pro-1.frontendapi.cloud.corbado.io')
const jwt = await generateJWT('https://pro-1.frontendapi.cloud.corbado.io', -600, 0, validPrivateKey);

await expect(sessionService.validateToken(jwt)).rejects.toThrow(ValidationError);
await expect(sessionService.validateToken(jwt)).rejects.toHaveProperty('name', ValidationErrorNames.JWTExpired);
});

test('should throw ValidationError if issuer is mismatched 1', async () => {
sessionService = new SessionService(
SHORT_SESSION_COOKIE_NAME,
'https://pro-2.frontendapi.cloud.corbado.io',
JWKS_URI,
10,
PROJECT_ID,
);
const jwt = await generateJWT(
'https://pro-1.frontendapi.corbado.io',
600,
TEST_USER_ID,
TEST_FULL_NAME,
validPrivateKey,
);
test('should throw ValidationError if issuer is empty', async () => {
const sessionService = createSessionService('https://pro-1.frontendapi.cloud.corbado.io')
const jwt = await generateJWT('', 600, 0, validPrivateKey);

await expect(sessionService.validateToken(jwt)).rejects.toThrow(ValidationError);
await expect(sessionService.validateToken(jwt)).rejects.toHaveProperty('name', ValidationErrorNames.InvalidIssuer);
await expect(sessionService.validateToken(jwt)).rejects.toHaveProperty('name', ValidationErrorNames.EmptyIssuer);
});

test('should throw ValidationError if issuer is mismatched 2', async () => {
const jwt = await generateJWT(
'https://pro-1.frontendapi.cloud.corbado.io',
600,
TEST_USER_ID,
TEST_FULL_NAME,
validPrivateKey,
);
test('should throw ValidationError if issuer is mismatch 1', async () => {
const sessionService = createSessionService('https://pro-1.frontendapi.corbado.io')
const jwt = await generateJWT('https://pro-2.frontendapi.cloud.corbado.io', 600, 0, validPrivateKey);

await expect(sessionService.validateToken(jwt)).rejects.toThrow(ValidationError);
await expect(sessionService.validateToken(jwt)).rejects.toHaveProperty('name', ValidationErrorNames.InvalidIssuer);
});

test('should throw ValidationError if issuer is undefined', async () => {
const jwt = await generateJWT('', 600, TEST_USER_ID, TEST_FULL_NAME, validPrivateKey);
test('should throw ValidationError if issuer is mismatch 2', async () => {
const sessionService = createSessionService('https://pro-1.frontendapi.cloud.corbado.io')
const jwt = await generateJWT('https://pro-2.frontendapi.corbado.io', 600, 0, validPrivateKey);

await expect(sessionService.validateToken(jwt)).rejects.toThrow(ValidationError);
await expect(sessionService.validateToken(jwt)).rejects.toHaveProperty('name', ValidationErrorNames.EmptyIssuer);
await expect(sessionService.validateToken(jwt)).rejects.toHaveProperty('name', ValidationErrorNames.InvalidIssuer);
});

test('should return user using old Frontend API URL as issuer in JWT', async () => {
const jwt = await generateJWT(
'https://pro-2.frontendapi.cloud.corbado.io',
600,
TEST_USER_ID,
TEST_FULL_NAME,
validPrivateKey,
);
const sessionService = createSessionService('https://pro-1.frontendapi.cloud.corbado.io')
const jwt = await generateJWT('https://pro-1.frontendapi.corbado.io', 600, 0, validPrivateKey);

const user = await sessionService.validateToken(jwt);
expect(user.userId).toBe(TEST_USER_ID);
expect(user.fullName).toBe(TEST_FULL_NAME);
expect(user.fullName).toBe(TEST_USER_FULL_NAME);
});

test('should return user using old Frontend API URL as config issuer', async () => {
sessionService = new SessionService(
SHORT_SESSION_COOKIE_NAME,
'https://pro-2.frontendapi.cloud.corbado.io',
JWKS_URI,
10,
PROJECT_ID,
);
const jwt = await generateJWT(
'https://pro-2.frontendapi.corbado.io',
600,
TEST_USER_ID,
TEST_FULL_NAME,
validPrivateKey,
);
test('should return user using old Frontend API URL as issuer in config', async () => {
const sessionService = createSessionService('https://pro-1.frontendapi.corbado.io')
const jwt = await generateJWT('https://pro-1.frontendapi.cloud.corbado.io', 600, 0, validPrivateKey);

const user = await sessionService.validateToken(jwt);
expect(user.userId).toBe(TEST_USER_ID);
expect(user.fullName).toBe(TEST_FULL_NAME);
expect(user.fullName).toBe(TEST_USER_FULL_NAME);
});

test('should return user data using CNAME', async () => {
sessionService = new SessionService(SHORT_SESSION_COOKIE_NAME, 'https://auth.acme.com', JWKS_URI, 10, PROJECT_ID);
const jwt = await generateJWT('https://auth.acme.com', 600, TEST_USER_ID, TEST_FULL_NAME, validPrivateKey);
const sessionService = createSessionService('https://auth.acme.com')
const jwt = await generateJWT('https://auth.acme.com', 600, 0, validPrivateKey);

const user = await sessionService.validateToken(jwt);
expect(user.userId).toBe(TEST_USER_ID);
expect(user.fullName).toBe(TEST_FULL_NAME);
expect(user.fullName).toBe(TEST_USER_FULL_NAME);
});
});

0 comments on commit 192a90c

Please sign in to comment.