diff --git a/backend/src/config/const.ts b/backend/src/config/const.ts index 68bd124a8..05d413da4 100644 --- a/backend/src/config/const.ts +++ b/backend/src/config/const.ts @@ -1 +1,3 @@ export const LOG4JS_BASE_CATEGORY_NAME = 'backend' +export const FRONTEND_LOGIN_ROUTE = 'login' +export const GRADIDO_REALM = 'gradido' \ 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..f6ef5a88d --- /dev/null +++ b/backend/src/openIDConnect/index.ts @@ -0,0 +1,63 @@ +import { CONFIG } from '@/config' +import { FRONTEND_LOGIN_ROUTE, GRADIDO_REALM, 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(`/realms/${GRADIDO_REALM}/protocol/openid-connect/certs`, 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 rs256Key = await importSPKI(homeCommunity.publicJwtKey, 'RS256') + const rsaKey = await importSPKI(homeCommunity.publicJwtKey, 'RSA-OAEP-256') + const jwkRs256 = await exportJWK(rs256Key) + const jwkRsa = await exportJWK(rsaKey) + + // Optional: calculate Key ID (z.B. SHA-256 Fingerprint) + const kid = createHash('sha256') + .update(homeCommunity.publicJwtKey) + .digest('base64url') + + const jwks = { + keys: [ + { + ...jwkRs256, + alg: 'RS256', + use: 'sig', + kid, + }, + { + ...jwkRsa, + alg: 'RSA-OAEP-256', + 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..5f3bb02ef 100644 --- a/backend/src/server/createServer.ts +++ b/backend/src/server/createServer.ts @@ -9,12 +9,13 @@ import helmet from 'helmet' import { Logger, getLogger } from 'log4js' import { DataSource } from 'typeorm' -import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' +import { GRADIDO_REALM, LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { AppDatabase } from 'database' 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(`/realms/${GRADIDO_REALM}/.well-known/openid-configuration`, openidConfiguration) + app.get(`/realms/${GRADIDO_REALM}/protocol/openid-connect/certs`, 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..3bc911d39 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,42 @@ server { error_log $GRADIDO_LOG_PATH/nginx-error.hooks.log warn; } + # Well-Known for openid connect + location /.well-known/ { + limit_req zone=backend burst=10 nodelay; + limit_conn addr 5; + 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/realms/gradido/.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; + } + + # Well-Known for openid connect + location /realms/gradido { + limit_req zone=backend burst=10 nodelay; + limit_conn addr 5; + 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/realms/gradido; + 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..15e66046c 100644 --- a/deployment/bare_metal/nginx/sites-available/gradido.conf.template +++ b/deployment/bare_metal/nginx/sites-available/gradido.conf.template @@ -114,6 +114,42 @@ server { error_log $GRADIDO_LOG_PATH/nginx-error.hooks.log warn; } + # Well-Known for openid connect + location /.well-known/ { + limit_req zone=backend burst=10 nodelay; + limit_conn addr 5; + 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/realms/gradido/.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; + } + + # Well-Known for openid connect + location /realms/gradido { + limit_req zone=backend burst=10 nodelay; + limit_conn addr 5; + 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/realms/gradido; + 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..bbfd8db51 100644 --- a/nginx/gradido.conf +++ b/nginx/gradido.conf @@ -43,6 +43,31 @@ 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/realms/gradido/.well-known; + proxy_redirect off; + } + + location /realms/gradido { + 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/realms/gradido; + proxy_redirect off; + } + # Admin Frontend location /admin { proxy_http_version 1.1;