mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-20 22:01:45 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83be5fc92a | |||
| 7798d2a3fd |
@@ -1,5 +1,5 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
name: trek
|
name: trek
|
||||||
version: 3.0.2
|
version: 3.0.3
|
||||||
description: Minimal Helm chart for TREK app
|
description: Minimal Helm chart for TREK app
|
||||||
appVersion: "3.0.2"
|
appVersion: "3.0.3"
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "trek-client",
|
"name": "trek-client",
|
||||||
"version": "3.0.2",
|
"version": "3.0.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "trek-client",
|
"name": "trek-client",
|
||||||
"version": "3.0.2",
|
"version": "3.0.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-pdf/renderer": "^4.3.2",
|
"@react-pdf/renderer": "^4.3.2",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trek-client",
|
"name": "trek-client",
|
||||||
"version": "3.0.2",
|
"version": "3.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "trek-server",
|
"name": "trek-server",
|
||||||
"version": "3.0.2",
|
"version": "3.0.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "trek-server",
|
"name": "trek-server",
|
||||||
"version": "3.0.2",
|
"version": "3.0.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.28.0",
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
||||||
"archiver": "^6.0.1",
|
"archiver": "^6.0.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trek-server",
|
"name": "trek-server",
|
||||||
"version": "3.0.2",
|
"version": "3.0.3",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --import tsx src/index.ts",
|
"start": "node --import tsx src/index.ts",
|
||||||
|
|||||||
@@ -313,7 +313,6 @@ export async function verifyIdToken(
|
|||||||
try {
|
try {
|
||||||
const verified = jwt.verify(idToken, publicKey, {
|
const verified = jwt.verify(idToken, publicKey, {
|
||||||
algorithms: [alg as jwt.Algorithm],
|
algorithms: [alg as jwt.Algorithm],
|
||||||
issuer: expectedIssuer,
|
|
||||||
audience: clientId,
|
audience: clientId,
|
||||||
});
|
});
|
||||||
claims = typeof verified === 'string' ? {} : (verified as Record<string, unknown>);
|
claims = typeof verified === 'string' ? {} : (verified as Record<string, unknown>);
|
||||||
@@ -322,6 +321,13 @@ export async function verifyIdToken(
|
|||||||
return { ok: false, error: `signature_or_claim_mismatch: ${msg}` };
|
return { ok: false, error: `signature_or_claim_mismatch: ${msg}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize trailing slash before issuer comparison — some IdPs (e.g. Authentik)
|
||||||
|
// include a trailing slash in the id_token iss claim.
|
||||||
|
const tokenIssuer = typeof claims['iss'] === 'string' ? claims['iss'].replace(/\/+$/, '') : '';
|
||||||
|
if (tokenIssuer !== expectedIssuer) {
|
||||||
|
return { ok: false, error: `signature_or_claim_mismatch: jwt issuer invalid. expected: ${expectedIssuer}` };
|
||||||
|
}
|
||||||
|
|
||||||
return { ok: true, claims };
|
return { ok: true, claims };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
* discover caching, and the ReDoS-sensitive issuer trailing-slash regex.
|
* discover caching, and the ReDoS-sensitive issuer trailing-slash regex.
|
||||||
*/
|
*/
|
||||||
import { describe, it, expect, vi, beforeAll, beforeEach, afterAll, afterEach } from 'vitest';
|
import { describe, it, expect, vi, beforeAll, beforeEach, afterAll, afterEach } from 'vitest';
|
||||||
|
import { generateKeyPairSync } from 'crypto';
|
||||||
|
import jwtLib from 'jsonwebtoken';
|
||||||
|
|
||||||
// ── DB setup ──────────────────────────────────────────────────────────────────
|
// ── DB setup ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -50,6 +52,7 @@ import {
|
|||||||
frontendUrl,
|
frontendUrl,
|
||||||
findOrCreateUser,
|
findOrCreateUser,
|
||||||
discover,
|
discover,
|
||||||
|
verifyIdToken,
|
||||||
} from '../../../src/services/oidcService';
|
} from '../../../src/services/oidcService';
|
||||||
|
|
||||||
const MOCK_CONFIG = {
|
const MOCK_CONFIG = {
|
||||||
@@ -460,3 +463,66 @@ describe('getUserInfo', () => {
|
|||||||
expect(fetchCall[1].headers.Authorization).toBe('Bearer access-token-123');
|
expect(fetchCall[1].headers.Authorization).toBe('Bearer access-token-123');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── verifyIdToken ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('verifyIdToken', () => {
|
||||||
|
const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
|
||||||
|
const jwk = publicKey.export({ format: 'jwk' }) as Record<string, unknown>;
|
||||||
|
const ISSUER = 'https://auth.example.com/application/o/trek';
|
||||||
|
const CLIENT_ID = 'trek-client';
|
||||||
|
const JWKS_URI = 'https://auth.example.com/.well-known/jwks.json';
|
||||||
|
|
||||||
|
function mockJwks() {
|
||||||
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({ keys: [jwk] }),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeToken(iss: string, overrides: object = {}) {
|
||||||
|
return jwtLib.sign(
|
||||||
|
{ sub: 'user-sub', email: 'user@example.com', ...overrides },
|
||||||
|
privateKey,
|
||||||
|
{ algorithm: 'RS256', audience: CLIENT_ID, issuer: iss, expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = { jwks_uri: JWKS_URI } as any;
|
||||||
|
|
||||||
|
afterEach(() => { vi.unstubAllGlobals(); });
|
||||||
|
|
||||||
|
it('OIDC-SVC-033: accepts token whose iss matches expectedIssuer exactly', async () => {
|
||||||
|
mockJwks();
|
||||||
|
const token = makeToken(ISSUER);
|
||||||
|
const result = await verifyIdToken(token, doc, CLIENT_ID, ISSUER);
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('OIDC-SVC-034: accepts token whose iss has a trailing slash (Authentik)', async () => {
|
||||||
|
mockJwks();
|
||||||
|
const token = makeToken(ISSUER + '/');
|
||||||
|
const result = await verifyIdToken(token, doc, CLIENT_ID, ISSUER);
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('OIDC-SVC-035: rejects token with wrong issuer', async () => {
|
||||||
|
mockJwks();
|
||||||
|
const token = makeToken('https://evil.example.com');
|
||||||
|
const result = await verifyIdToken(token, doc, CLIENT_ID, ISSUER);
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
expect((result as any).error).toMatch('jwt issuer invalid');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('OIDC-SVC-036: rejects token with wrong audience', async () => {
|
||||||
|
mockJwks();
|
||||||
|
const token = makeToken(ISSUER, {});
|
||||||
|
const wrongAudToken = jwtLib.sign(
|
||||||
|
{ sub: 'user-sub', iss: ISSUER },
|
||||||
|
privateKey,
|
||||||
|
{ algorithm: 'RS256', audience: 'wrong-client', expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
const result = await verifyIdToken(wrongAudToken, doc, CLIENT_ID, ISSUER);
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user