Add OIDC fixes, add prefered_username, add application-user claims

This commit is contained in:
Dan Milne
2025-11-25 16:29:40 +11:00
parent 7796c38c08
commit d6029556d3
34 changed files with 1003 additions and 64 deletions

View File

@@ -0,0 +1,35 @@
module ClaimsMerger
extend ActiveSupport::Concern
# Deep merge claims, combining arrays instead of overwriting them
# This ensures that array values (like roles) are combined across group/user/app claims
#
# Example:
# base = { "roles" => ["user"], "level" => 1 }
# incoming = { "roles" => ["admin"], "department" => "IT" }
# deep_merge_claims(base, incoming)
# # => { "roles" => ["user", "admin"], "level" => 1, "department" => "IT" }
def deep_merge_claims(base, incoming)
result = base.dup
incoming.each do |key, value|
if result.key?(key)
# If both values are arrays, combine them (union to avoid duplicates)
if result[key].is_a?(Array) && value.is_a?(Array)
result[key] = (result[key] + value).uniq
# If both values are hashes, recursively merge them
elsif result[key].is_a?(Hash) && value.is_a?(Hash)
result[key] = deep_merge_claims(result[key], value)
else
# Otherwise, incoming value wins (override)
result[key] = value
end
else
# New key, just add it
result[key] = value
end
end
result
end
end

View File

@@ -1,4 +1,6 @@
class OidcJwtService
extend ClaimsMerger
class << self
# Generate an ID token (JWT) for the user
def generate_id_token(user, application, consent: nil, nonce: nil)
@@ -17,7 +19,7 @@ class OidcJwtService
iat: now,
email: user.email_address,
email_verified: true,
preferred_username: user.email_address,
preferred_username: user.username.presence || user.email_address,
name: user.name.presence || user.email_address
}
@@ -29,16 +31,16 @@ class OidcJwtService
payload[:groups] = user.groups.pluck(:name)
end
# Add admin claim if user is admin
payload[:admin] = true if user.admin?
# Merge custom claims from groups
# Merge custom claims from groups (arrays are combined, not overwritten)
user.groups.each do |group|
payload.merge!(group.parsed_custom_claims)
payload = deep_merge_claims(payload, group.parsed_custom_claims)
end
# Merge custom claims from user (overrides group claims)
payload.merge!(user.parsed_custom_claims)
# Merge custom claims from user (arrays are combined, other values override)
payload = deep_merge_claims(payload, user.parsed_custom_claims)
# Merge app-specific custom claims (highest priority, arrays are combined)
payload = deep_merge_claims(payload, application.custom_claims_for_user(user))
JWT.encode(payload, private_key, "RS256", { kid: key_id, typ: "JWT" })
end