From ae905a14cf86de088356b3433cae5fd2fb96aaa9 Mon Sep 17 00:00:00 2001 From: csmig Date: Sat, 8 Feb 2025 10:18:59 -0500 Subject: [PATCH] fix: user-defined jwt claims handling --- api/source/index.js | 10 +--------- api/source/service/CollectionService.js | 2 +- api/source/service/OperationService.js | 2 +- api/source/service/UserService.js | 4 ++-- api/source/utils/auth.js | 2 +- api/source/utils/config.js | 14 +++++++++----- client/src/js/SM/User.js | 2 +- docs/installation-and-setup/envvars.csv | 14 +++++++------- 8 files changed, 23 insertions(+), 27 deletions(-) diff --git a/api/source/index.js b/api/source/index.js index 9ec67681f..828300c85 100644 --- a/api/source/index.js +++ b/api/source/index.js @@ -219,15 +219,7 @@ const STIGMAN = { disabled: ${config.client.refreshToken.disabled} }, extraScopes: "${config.client.extraScopes ?? ''}", - scopePrefix: "${config.client.scopePrefix ?? ''}", - claims: { - scope: "${config.oauth.claims.scope}", - username: "${config.oauth.claims.username}", - servicename: "${config.oauth.claims.servicename}", - name: "${config.oauth.claims.name}", - privileges: "${config.oauth.claims.privileges}", - email: "${config.oauth.claims.email}" - } + scopePrefix: "${config.client.scopePrefix ?? ''}" }, experimental: { appData: "${config.experimental.appData}" diff --git a/api/source/service/CollectionService.js b/api/source/service/CollectionService.js index ebb415d58..8decdfe21 100644 --- a/api/source/service/CollectionService.js +++ b/api/source/service/CollectionService.js @@ -122,7 +122,7 @@ exports.queryCollection = async function ({collectionId, projections = [], eleva 'userId', CAST(ud.userId as char), 'username', ud.username, 'displayName', COALESCE( - JSON_UNQUOTE(JSON_EXTRACT(ud.lastClaims, "$.name")), + JSON_UNQUOTE(JSON_EXTRACT(ud.lastClaims, "$.${config.oauth.claims.name}")), ud.username)), 'roleId', cgs.roleId, 'grantees', cgs.grantees)) diff --git a/api/source/service/OperationService.js b/api/source/service/OperationService.js index 3ccdea628..ffc60936c 100755 --- a/api/source/service/OperationService.js +++ b/api/source/service/OperationService.js @@ -616,7 +616,7 @@ exports.getAppInfo = async function() { ud.created, ud.lastAccess, coalesce( - JSON_EXTRACT(ud.lastClaims, "$.${config.oauth.claims.privilegesPath}"), + JSON_EXTRACT(ud.lastClaims, '$.${config.oauth.claims.privileges}'), json_array() ) as privileges, json_object( diff --git a/api/source/service/UserService.js b/api/source/service/UserService.js index f04d165de..856b4d1e3 100644 --- a/api/source/service/UserService.js +++ b/api/source/service/UserService.js @@ -82,8 +82,8 @@ exports.queryUsers = async function (inProjection, inPredicates, elevate, userOb binds: [ `$.${config.oauth.claims.email}`, `$.${config.oauth.claims.name}`, - `$.${config.oauth.claims.privilegesPath}`, - `$.${config.oauth.claims.privilegesPath}` + `$.${config.oauth.claims.privileges}`, + `$.${config.oauth.claims.privileges}` ] } if (inPredicates.userId) { diff --git a/api/source/utils/auth.js b/api/source/utils/auth.js index a54edd260..246e7ddd6 100644 --- a/api/source/utils/auth.js +++ b/api/source/utils/auth.js @@ -10,7 +10,7 @@ const SmError = require('./error') let client -const privilegeGetter = new Function("obj", "return obj?." + config.oauth.claims.privileges + " || [];"); +const privilegeGetter = new Function("obj", "return obj?." + config.oauth.claims.privilegesChain + " || [];"); // express middleware to validate token const validateToken = async function (req, res, next) { diff --git a/api/source/utils/config.js b/api/source/utils/config.js index a1eebf425..5e0145e4b 100644 --- a/api/source/utils/config.js +++ b/api/source/utils/config.js @@ -76,8 +76,8 @@ const config = { username: process.env.STIGMAN_JWT_USERNAME_CLAIM, servicename: process.env.STIGMAN_JWT_SERVICENAME_CLAIM, name: process.env.STIGMAN_JWT_NAME_CLAIM || process.env.STIGMAN_JWT_USERNAME_CLAIM || "name", - privileges: formatChain(process.env.STIGMAN_JWT_PRIVILEGES_CLAIM || "realm_access.roles"), - privilegesPath: process.env.STIGMAN_JWT_PRIVILEGES_CLAIM || "realm_access.roles", + privileges: formatMySqlJsonPath(process.env.STIGMAN_JWT_PRIVILEGES_CLAIM || "realm_access.roles"), + privilegesChain: formatJsChain(process.env.STIGMAN_JWT_PRIVILEGES_CLAIM || "realm_access.roles"), email: process.env.STIGMAN_JWT_EMAIL_CLAIM || "email", assertion: process.env.STIGMAN_JWT_ASSERTION_CLAIM || "jti" } @@ -92,13 +92,17 @@ const config = { } } -function formatChain(path) { +function formatJsChain(path) { const components = path?.split('.') if (components?.length === 1) return path for (let x=0; x < components.length; x++) { components[x] = `['${components[x]}']` } return components.join('?.') - } +} + +function formatMySqlJsonPath(path) { + return path?.split('.').map(p => `"${p}"`).join('.') +} -module.exports = config \ No newline at end of file +module.exports = config \ No newline at end of file diff --git a/client/src/js/SM/User.js b/client/src/js/SM/User.js index 066108e09..5e77c656d 100644 --- a/client/src/js/SM/User.js +++ b/client/src/js/SM/User.js @@ -1371,7 +1371,7 @@ SM.User.showUserProps = async function showUserProps(userId) { apiUser.statistics.lastClaims.scope = apiUser.statistics.lastClaims.scope.split(' ') } const privileges = [] - for (privilege in apiUser.privileges) { + for (const privilege in apiUser.privileges) { if (apiUser.privileges[privilege]) privileges.push(privilege) } const formValues = { diff --git a/docs/installation-and-setup/envvars.csv b/docs/installation-and-setup/envvars.csv index 7620ebde3..0f933f043 100644 --- a/docs/installation-and-setup/envvars.csv +++ b/docs/installation-and-setup/envvars.csv @@ -62,19 +62,19 @@ "STIGMAN_LOG_MODE","| **Default** ``combined`` | Controls whether the logs will create one “combined” log entry for http requests that includes both the request and response information; or two separate log entries, one for the request and one for the response, that can be correlated via a generated Request GUID in each entry","API" "STIGMAN_JWT_ASSERTION_CLAIM","| **Default** ``jti`` -| The access token claim whose value is the OIDC provider's Assertion ID. Updates to this value trigger the API to update a User's ``lastClaims`` property. The claim MUST be a top-level claim and cannot be nested.","API" +| The access token claim whose value is the OIDC provider's Assertion ID. Updates to this value trigger the API to update a User's ``lastClaims`` property. The claim MUST NOT be nested and MUST be a valid ECMAScript identifier.","API" "STIGMAN_JWT_EMAIL_CLAIM","| **Default** ``email`` -| The access token claim whose value is the user's email address","API, Client" +| The access token claim whose value is the user's email address. The claim MUST NOT be nested and MUST be a valid ECMAScript identifier.","API, Client" "STIGMAN_JWT_NAME_CLAIM","| **Default** ``name`` -| The access token claim whose value is the user's full name","API, Client" +| The access token claim whose value is the user's full name. The claim MUST NOT be nested and MUST be a valid ECMAScript identifier.","API, Client" "STIGMAN_JWT_PRIVILEGES_CLAIM","| **Default** ``realm_access.roles`` -| The access token claim whose value is the user’s privileges ","API, Client" +| The access token claim whose value is the user’s privileges. The claim MAY be nested but SHOULD avoid invalid ECMAScript identifiers. ","API, Client" "STIGMAN_JWT_SCOPE_CLAIM","| **Default** ``scope`` -| The access token claim whose value is the user's scopes. Some OIDC Providers (Okta, Azure AD) use the claim ``scp`` to enumerate scopes","API, Client" +| The access token claim whose value is the user's scopes. Some OIDC Providers (Okta, Azure AD) use the claim ``scp`` to enumerate scopes. The claim MUST NOT be nested and MUST be a valid ECMAScript identifier.","API, Client" "STIGMAN_JWT_SERVICENAME_CLAIM","| **Default** ``clientId`` -| The access token claim whose value is the user's client","API, Client" +| The access token claim whose value is the user's client. The claim MUST NOT be nested and MUST be a valid ECMAScript identifier.","API, Client" "STIGMAN_JWT_USERNAME_CLAIM","| **Default** ``preferred_username`` -| The access token claim whose value is the user's username","API, Client" +| The access token claim whose value is the user's username. The claim MUST NOT be nested and MUST be a valid ECMAScript identifier.","API, Client" "STIGMAN_OIDC_PROVIDER","| **Default** ``http://localhost:8080/auth/realms/stigman`` | The base URL of the OIDC provider issuing signed JWTs for the API. The string ``/.well-known/openid-configuration`` will be appended when fetching metadata.","API, Client " "STIGMAN_SWAGGER_ENABLED","| **Default** ``false``