From 80903243dfee61d7d2c16b5674a531f886b96f71 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Sun, 20 Jul 2025 08:46:47 +0200 Subject: [PATCH] add .well-known routes for openid connect protocol, show jwks.json and openid-configuration --- backend/src/config/const.ts | 1 + backend/src/openIDConnect/index.ts | 55 +++++++++++++++++++ backend/src/server/createServer.ts | 5 ++ .../sites-available/gradido.conf.ssl.template | 18 ++++++ .../sites-available/gradido.conf.template | 18 ++++++ nginx/gradido.conf | 13 +++++ 6 files changed, 110 insertions(+) create mode 100644 backend/src/openIDConnect/index.ts diff --git a/backend/src/config/const.ts b/backend/src/config/const.ts index 68bd124a8..de9f094b2 100644 --- a/backend/src/config/const.ts +++ b/backend/src/config/const.ts @@ -1 +1,2 @@ export const LOG4JS_BASE_CATEGORY_NAME = 'backend' +export const FRONTEND_LOGIN_ROUTE = 'login' \ No newline at end of file diff --git a/backend/src/openIDConnect/index.ts b/backend/src/openIDConnect/index.ts new file mode 100644 index 000000000..7bfdde040 --- /dev/null +++ b/backend/src/openIDConnect/index.ts @@ -0,0 +1,55 @@ +import { CONFIG } from '@/config' +import { FRONTEND_LOGIN_ROUTE, LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' +import { getHomeCommunity } from 'database' +import { importSPKI, exportJWK } from 'jose' +import { createHash } from 'crypto' +import { getLogger } from 'log4js' + +const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.openIDConnect`) +const defaultErrorForCaller = 'Internal Server Error' + +export const openidConfiguration = async (req: any, res: any): Promise => { + res.setHeader('Content-Type', 'application/json') + res.status(200).json({ + issuer: new URL(FRONTEND_LOGIN_ROUTE, CONFIG.COMMUNITY_URL).toString(), + jwks_uri: new URL('/.well-known/jwks.json', CONFIG.COMMUNITY_URL).toString(), + }) +} + +export const jwks = async (req: any, res: any): Promise => { + const homeCommunity = await getHomeCommunity() + if (!homeCommunity) { + logger.error('HomeCommunity not found') + throw new Error(defaultErrorForCaller) + } + if (!homeCommunity.publicJwtKey) { + logger.error('HomeCommunity publicJwtKey not found') + throw new Error(defaultErrorForCaller) + } + try { + const publicKey = await importSPKI(homeCommunity.publicJwtKey, 'RS256') + const jwk = await exportJWK(publicKey) + + // Optional: calculate Key ID (z.B. SHA-256 Fingerprint) + const kid = createHash('sha256') + .update(homeCommunity.publicJwtKey) + .digest('base64url') + + const jwks = { + keys: [ + { + ...jwk, + alg: 'RS256', + use: 'sig', + kid, + }, + ], + } + res.setHeader('Cache-Control', 'public, max-age=3600, immutable') + res.setHeader('Content-Type', 'application/json') + res.status(200).json(jwks) + } catch (err) { + logger.error('Failed to convert publicJwtKey to JWK', err) + res.status(500).json({ error: 'Failed to generate JWKS' }) + } +} \ No newline at end of file diff --git a/backend/src/server/createServer.ts b/backend/src/server/createServer.ts index 60f6fc31e..81c5c1cb6 100644 --- a/backend/src/server/createServer.ts +++ b/backend/src/server/createServer.ts @@ -15,6 +15,7 @@ import { context as serverContext } from './context' import { cors } from './cors' import { i18n } from './localization' import { plugins } from './plugins' +import { jwks, openidConfiguration } from '@/openIDConnect' // TODO implement // import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity"; @@ -84,6 +85,10 @@ export const createServer = async ( app.get('/hook/gms/' + CONFIG.GMS_WEBHOOK_SECRET, gmsWebhook) + // OpenID Connect + app.get('/.well-known/openid-configuration', openidConfiguration) + app.get('/.well-known/jwks.json', jwks) + // Apollo Server const apollo = new ApolloServer({ schema: await schema(), diff --git a/deployment/bare_metal/nginx/sites-available/gradido.conf.ssl.template b/deployment/bare_metal/nginx/sites-available/gradido.conf.ssl.template index 1eb01f09e..3d0e72cec 100644 --- a/deployment/bare_metal/nginx/sites-available/gradido.conf.ssl.template +++ b/deployment/bare_metal/nginx/sites-available/gradido.conf.ssl.template @@ -129,6 +129,24 @@ server { error_log $GRADIDO_LOG_PATH/nginx-error.hooks.log warn; } + # Well-Known for openid connect + location /.well-known/ { + limit_req zone=backend burst=20 nodelay; + limit_conn addr 10; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + + proxy_pass http://127.0.0.1:4000/.well-known/; + proxy_redirect off; + + access_log $GRADIDO_LOG_PATH/nginx-access.well-known.log gradido_log; + error_log $GRADIDO_LOG_PATH/nginx-error.well-known.log warn; + } + # Admin Frontend location /admin { limit_req zone=frontend burst=30 nodelay; diff --git a/deployment/bare_metal/nginx/sites-available/gradido.conf.template b/deployment/bare_metal/nginx/sites-available/gradido.conf.template index 1f5ca2304..f420d7059 100644 --- a/deployment/bare_metal/nginx/sites-available/gradido.conf.template +++ b/deployment/bare_metal/nginx/sites-available/gradido.conf.template @@ -114,6 +114,24 @@ server { error_log $GRADIDO_LOG_PATH/nginx-error.hooks.log warn; } + # Well-Known for openid connect + location /.well-known/ { + limit_req zone=backend burst=20 nodelay; + limit_conn addr 10; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + + proxy_pass http://127.0.0.1:4000/.well-known/; + proxy_redirect off; + + access_log $GRADIDO_LOG_PATH/nginx-access.well-known.log gradido_log; + error_log $GRADIDO_LOG_PATH/nginx-error.well-known.log warn; + } + # Admin Frontend location /admin { limit_req zone=frontend burst=30 nodelay; diff --git a/nginx/gradido.conf b/nginx/gradido.conf index 1f4788ae2..be10a499f 100644 --- a/nginx/gradido.conf +++ b/nginx/gradido.conf @@ -43,6 +43,19 @@ server { proxy_redirect off; } + # Well-Known for openid connect + location /.well-known/ { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + + proxy_pass http://backend:4000/.well-known/; + proxy_redirect off; + } + # Admin Frontend location /admin { proxy_http_version 1.1;