From 6558d027ca2d76cadc2f725fdadfaa420a685e0a Mon Sep 17 00:00:00 2001 From: Joakim Olsson Date: Sun, 28 Dec 2025 15:25:52 +0100 Subject: [PATCH] feat: increase resource limits and improve readiness checks Increases memory requests and limits in the Kubernetes deployment configuration to ensure better performance under load. Enhances the readiness and liveness probes by correcting the path and adding a liveness probe to improve service reliability. Updates Code by cleaning up session data after a successful token exchange and formats OpenID configuration response for better readability. Adds `.claude/` to .gitignore to prevent unwanted files from being tracked. --- .gitignore | 1 + CLAUDE.md | 83 ++++++++++++++++++++++++++++ app.js | 141 ++++++++++++++++++++++-------------------------- k8s/deploy.yaml | 12 +++-- 4 files changed, 158 insertions(+), 79 deletions(-) create mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index 78f2710..b4b1cc2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ .idea/ +.claude/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b64cb4e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,83 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +auth0mock is a Node.js/Express application that simulates an Auth0 authentication server for local development. It provides OAuth 2.0 and OpenID Connect (OIDC) endpoints compatible with the Auth0 API, allowing developers to test authentication flows without connecting to the actual Auth0 service. + +## Development Commands + +```bash +# Install dependencies +yarn install + +# Start production server (port 3333) +yarn start + +# Development with auto-reload (nodemon) +yarn dev + +# Format code +yarn lintfix + +# Check formatting +yarn lint +``` + +## Architecture + +This is a single-file Express application (`app.js`) that implements: + +**Authentication Endpoints:** + +- `POST /oauth/token` - Token exchange (OAuth 2.0 authorization code flow) +- `GET /authorize` - Authorization endpoint with HTML login form +- `POST /code` - Code generation for PKCE flow + +**Discovery Endpoints:** + +- `GET /.well-known/openid-configuration` - OIDC discovery document +- `GET /.well-known/jwks.json` - JSON Web Key Set for token verification + +**Management API (Auth0-compatible):** + +- `GET /api/v2/users-by-email` - Get user by email +- `POST /api/v2/users` - Create user +- `PATCH /api/v2/users/:userid` - Update user +- `POST /api/v2/tickets/password-change` - Password change ticket + +**Key Implementation Details:** + +- RSA 2048-bit key pair generated at startup using `node-jose` +- In-memory session and user storage (not persistent) +- PKCE support with code challenge verification +- Custom claims for admin (`https://unbound.se/admin`) and email (`https://unbound.se/email`) + +## Environment Variables + +| Variable | Default | Purpose | +| ------------ | -------------------------- | -------------------------------- | +| `ISSUER` | `localhost:3333` | JWT issuer claim | +| `AUDIENCE` | `https://generic-audience` | JWT audience claim | +| `USERS_FILE` | `./users.json` | Path to initial users JSON file | +| `DEBUG` | (unset) | Debug logging (`app*` to enable) | + +## Initial Users + +Create a `users.json` file to seed users on startup: + +```json +{ + "email@test.com": { + "given_name": "John", + "family_name": "Doe", + "user_id": "auth0|email@test.com", + "email": "email@test.com" + } +} +``` + +## Integration with Shiny + +This service is used for local development and acceptance testing of the Shiny platform. The gateway and frontend services are configured to accept tokens signed by this mock server when running locally. diff --git a/app.js b/app.js index 623d514..bb8ff9b 100644 --- a/app.js +++ b/app.js @@ -8,7 +8,7 @@ const Debug = require('debug') const path = require('path') const cors = require('cors') const bodyParser = require('body-parser') -const jose = require('node-jose'); +const jose = require('node-jose') const favicon = require('serve-favicon') const initialUsers = require('./users') @@ -128,6 +128,10 @@ app.post('/oauth/token', async (req, res) => { debug('Signed token for ' + session.email) + // Clean up session and challenge after successful token exchange + delete sessions[code] + delete challenges[code] + res.json({ access_token: accessToken, id_token: idToken, @@ -141,22 +145,6 @@ app.post('/oauth/token', async (req, res) => { } }) -// This route can be used to generate a valid jwt-token. -app.get('/token/:email', (req, res) => { - if (!req.params.email) { - debug('No user was given!') - return res.status(400).send('user is missing') - } - const token = jwt.sign( - { - user_id: 'auth0|' + req.params.email - }, - privateKey - ) - debug('Signed token for ' + req.params.email) - res.json({ token }) -}) - app.post('/code', (req, res) => { if (!req.body.email || !req.body.password || !req.body.codeChallenge) { debug('Body is invalid!', req.body) @@ -301,65 +289,66 @@ app.get('/.well-known/openid-configuration', (req, res) => { debug('Fetching OpenID configuration') res.contentType('application/json').send( JSON.stringify({ - "issuer": - `${jwksOrigin}`, - "authorization_endpoint": - `${jwksOrigin}authorize`, - "token_endpoint": - `${jwksOrigin}oauth/token`, - "token_endpoint_auth_methods_supported": - ["client_secret_basic", "private_key_jwt"], - "token_endpoint_auth_signing_alg_values_supported": - ["RS256"], - "userinfo_endpoint": - `${jwksOrigin}userinfo`, - "check_session_iframe": - `${jwksOrigin}check_session`, - "end_session_endpoint": - `${jwksOrigin}end_session`, - "jwks_uri": - `${jwksOrigin}.well-known/jwks.json`, - "registration_endpoint": - `${jwksOrigin}register`, - "scopes_supported": - ["openid", "profile", "email", "address", - "phone", "offline_access"], - "response_types_supported": - ["code", "code id_token", "id_token", "id_token token"], - "acr_values_supported": - [], - "subject_types_supported": - ["public", "pairwise"], - "userinfo_signing_alg_values_supported": - ["RS256", "ES256", "HS256"], - "userinfo_encryption_alg_values_supported": - ["RSA-OAEP-256", "A128KW"], - "userinfo_encryption_enc_values_supported": - ["A128CBC-HS256", "A128GCM"], - "id_token_signing_alg_values_supported": - ["RS256", "ES256", "HS256"], - "id_token_encryption_alg_values_supported": - ["RSA-OAEP-256", "A128KW"], - "id_token_encryption_enc_values_supported": - ["A128CBC-HS256", "A128GCM"], - "request_object_signing_alg_values_supported": - ["none", "RS256", "ES256"], - "display_values_supported": - ["page", "popup"], - "claim_types_supported": - ["normal", "distributed"], - "claims_supported": - ["sub", "iss", "auth_time", "acr", - "name", "given_name", "family_name", "nickname", - "profile", "picture", "website", - "email", "email_verified", "locale", "zoneinfo", - "https://unbound.se/email", "https://unbound.se/admin"], - "claims_parameter_supported": - true, - "service_documentation": - "http://auth0/", - "ui_locales_supported": - ["en-US"] + issuer: `${jwksOrigin}`, + authorization_endpoint: `${jwksOrigin}authorize`, + token_endpoint: `${jwksOrigin}oauth/token`, + token_endpoint_auth_methods_supported: [ + 'client_secret_basic', + 'private_key_jwt' + ], + token_endpoint_auth_signing_alg_values_supported: ['RS256'], + userinfo_endpoint: `${jwksOrigin}userinfo`, + check_session_iframe: `${jwksOrigin}check_session`, + end_session_endpoint: `${jwksOrigin}end_session`, + jwks_uri: `${jwksOrigin}.well-known/jwks.json`, + registration_endpoint: `${jwksOrigin}register`, + scopes_supported: [ + 'openid', + 'profile', + 'email', + 'address', + 'phone', + 'offline_access' + ], + response_types_supported: [ + 'code', + 'code id_token', + 'id_token', + 'id_token token' + ], + acr_values_supported: [], + subject_types_supported: ['public', 'pairwise'], + userinfo_signing_alg_values_supported: ['RS256', 'ES256', 'HS256'], + userinfo_encryption_alg_values_supported: ['RSA-OAEP-256', 'A128KW'], + userinfo_encryption_enc_values_supported: ['A128CBC-HS256', 'A128GCM'], + id_token_signing_alg_values_supported: ['RS256', 'ES256', 'HS256'], + id_token_encryption_alg_values_supported: ['RSA-OAEP-256', 'A128KW'], + id_token_encryption_enc_values_supported: ['A128CBC-HS256', 'A128GCM'], + request_object_signing_alg_values_supported: ['none', 'RS256', 'ES256'], + display_values_supported: ['page', 'popup'], + claim_types_supported: ['normal', 'distributed'], + claims_supported: [ + 'sub', + 'iss', + 'auth_time', + 'acr', + 'name', + 'given_name', + 'family_name', + 'nickname', + 'profile', + 'picture', + 'website', + 'email', + 'email_verified', + 'locale', + 'zoneinfo', + 'https://unbound.se/email', + 'https://unbound.se/admin' + ], + claims_parameter_supported: true, + service_documentation: 'http://auth0/', + ui_locales_supported: ['en-US'] }) ) }) diff --git a/k8s/deploy.yaml b/k8s/deploy.yaml index ee73101..e1e80d7 100644 --- a/k8s/deploy.yaml +++ b/k8s/deploy.yaml @@ -18,9 +18,9 @@ spec: imagePullPolicy: "IfNotPresent" resources: requests: - memory: 200Mi + memory: 256Mi limits: - memory: 300Mi + memory: 512Mi ports: - containerPort: 3333 env: @@ -29,7 +29,13 @@ spec: readinessProbe: httpGet: port: 3333 - path: .well-known/jwks.json + path: /.well-known/jwks.json + livenessProbe: + httpGet: + port: 3333 + path: /.well-known/jwks.json + initialDelaySeconds: 10 + periodSeconds: 30 --- apiVersion: v1 kind: Service