Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/backend/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const TokenVerificationErrorReason = {
TokenInvalid: 'token-invalid',
TokenInvalidAlgorithm: 'token-invalid-algorithm',
TokenInvalidAuthorizedParties: 'token-invalid-authorized-parties',
TokenMissingAzp: 'token-missing-azp',
TokenInvalidSignature: 'token-invalid-signature',
TokenNotActiveYet: 'token-not-active-yet',
TokenIatInTheFuture: 'token-iat-in-the-future',
Expand Down
135 changes: 135 additions & 0 deletions packages/backend/src/tokens/__tests__/request_azp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { describe, expect, test, vi } from 'vitest';

import { TokenVerificationErrorReason } from '../../errors';
import { decodeJwt } from '../../jwt/verifyJwt';
import { authenticateRequest } from '../request';
import { TokenType } from '../tokenTypes';

Check failure on line 6 in packages/backend/src/tokens/__tests__/request_azp.test.ts

View workflow job for this annotation

GitHub Actions / Static analysis

'TokenType' is defined but never used

Check failure on line 6 in packages/backend/src/tokens/__tests__/request_azp.test.ts

View workflow job for this annotation

GitHub Actions / Static analysis

'TokenType' is defined but never used. Allowed unused vars must match /^_/u
import { verifyToken } from '../verify';

vi.mock('../verify', () => ({
verifyToken: vi.fn(),
verifyMachineAuthToken: vi.fn(),
}));

vi.mock('../../jwt/verifyJwt', () => ({
decodeJwt: vi.fn(),
}));

describe('authenticateRequest with cookie token', () => {
test('throws TokenMissingAzp when azp claim is missing', async () => {
const payload = {
sub: 'user_123',
sid: 'sess_123',
iat: 1234567891,
exp: 1234567991,
// azp is missing
};

// Mock verifyToken to return a payload without azp
vi.mocked(verifyToken).mockResolvedValue({
data: payload as any,
errors: undefined,
});

// Mock decodeJwt to return the same payload
vi.mocked(decodeJwt).mockReturnValue({
data: { payload } as any,
errors: undefined,
});

const request = new Request('http://localhost:3000', {
headers: {
cookie: '__session=mock_token; __client_uat=1234567890',
},
});

const options = {
publishableKey: 'pk_live_Y2xlcmsuaW5zcGlyZWQucHVtYS03NC5sY2wuZGV2JA',
secretKey: 'sk_live_deadbeef',
};

const result = await authenticateRequest(request, options);

expect(result.status).toBe('signed-out');
// @ts-ignore
expect(result.reason).toBe(TokenVerificationErrorReason.TokenMissingAzp);
// @ts-ignore
expect(result.message).toBe(
'Session tokens from cookies must have an azp claim. (reason=token-missing-azp, token-carrier=cookie)',
);
});

test('succeeds when azp claim is present', async () => {
const payload = {
sub: 'user_123',
sid: 'sess_123',
iat: 1234567891,
exp: 1234567991,
azp: 'http://localhost:3000',
};

// Mock verifyToken to return a payload with azp
vi.mocked(verifyToken).mockResolvedValue({
data: payload as any,
errors: undefined,
});

// Mock decodeJwt to return the same payload
vi.mocked(decodeJwt).mockReturnValue({
data: { payload } as any,
errors: undefined,
});

const request = new Request('http://localhost:3000', {
headers: {
cookie: '__session=mock_token; __client_uat=1234567890',
},
});

const options = {
publishableKey: 'pk_live_Y2xlcmsuaW5zcGlyZWQucHVtYS03NC5sY2wuZGV2JA',
secretKey: 'sk_live_deadbeef',
};

const result = await authenticateRequest(request, options);
expect(result.isSignedIn).toBe(true);
});
});

describe('authenticateRequest with header token', () => {
test('succeeds when azp claim is missing', async () => {
const payload = {
sub: 'user_123',
sid: 'sess_123',
iat: 1234567891,
exp: 1234567991,
// azp is missing
};

// Mock verifyToken to return a payload without azp
vi.mocked(verifyToken).mockResolvedValue({
data: payload as any,
errors: undefined,
});

// Mock decodeJwt to return the same payload
vi.mocked(decodeJwt).mockReturnValue({
data: { payload } as any,
errors: undefined,
});

const request = new Request('http://localhost:3000', {
headers: {
authorization: 'Bearer mock_token',
},
});

const options = {
publishableKey: 'pk_live_Y2xlcmsuaW5zcGlyZWQucHVtYS03NC5sY2wuZGV2JA',
secretKey: 'sk_live_deadbeef',
};

const result = await authenticateRequest(request, options);
expect(result.isSignedIn).toBe(true);
});
});
7 changes: 7 additions & 0 deletions packages/backend/src/tokens/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,13 @@ export const authenticateRequest: AuthenticateRequest = (async (
throw errors[0];
}

if (!data.azp) {
throw new TokenVerificationError({
reason: TokenVerificationErrorReason.TokenMissingAzp,
message: 'Session tokens from cookies must have an azp claim.',
});
}

const signedInRequestState = signedIn({
tokenType: TokenType.SessionToken,
authenticateContext,
Expand Down
Loading