strongmind-auth 1.1.138 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fce6d989dec60e9621d3ef78bd67e54b317e87ea6cebfd1062daed7a7da009cd
4
- data.tar.gz: 020d99b5397adc14b1e3583c75185c354c0c50b8dd30d46f39baf02920c3651e
3
+ metadata.gz: b1c87c87745c0a96b93591482bfb073b7aee75ad305af4acc7d32632e52f2e51
4
+ data.tar.gz: 5f199cf554635e03c7949ab35ae0e8740ccfef983c1d92fe5d1c72b839367d50
5
5
  SHA512:
6
- metadata.gz: 5ba8b3b1b22b901a32b8e5d913d7641aa5558bd8fd07d7af9e043e2e373e783dca127ea8eeb987902240f9da504be1a3fe53ddc1b8990efadbd7d80fa480cd11
7
- data.tar.gz: d416b410304413e381bb31ff8a7a216d9dcfd6355c55d6ba9126d46be321bedaa6b20fb8a24c63c9fb6035268696a66fbbc0145ceeb233fc199dda3858335c68
6
+ metadata.gz: c71f32bab0c99a7c87d0b62121d26f61ad8c67841ad67ac80968d2db28cbf1c14e089ea2afc147aaad36a8e2c6040d40fa206db831e9f8f453468d58374a3ef1
7
+ data.tar.gz: 9aeae95af157a89828bd820983c86b4452b6ad0b48f4096836d69d24d41a533d943a7f74d3eeceec4c209a1155539d9cff0579e18c892feccd59c0d377381a9f
@@ -4,6 +4,10 @@
4
4
  module JwtUtilities
5
5
  extend ActiveSupport::Concern
6
6
 
7
+ # Matches Zitadel's role-grant claim, e.g. "urn:zitadel:iam:org:project:123:roles"
8
+ # or the project-agnostic "urn:zitadel:iam:org:project:roles".
9
+ ZITADEL_ROLES_CLAIM_PATTERN = /\Aurn:zitadel:iam:org:project:(?:[^:]+:)?roles\z/.freeze
10
+
7
11
  def jwt_valid?(jwt, condition_key = nil, scopes = [], attributes = [])
8
12
  payload = decode_jwt(jwt)
9
13
  return false unless payload
@@ -41,6 +45,10 @@ module JwtUtilities
41
45
  verify_iat: true,
42
46
  verify_iss: true,
43
47
  verify_sub: true,
48
+ # RS256 matches Zitadel's current default signing algorithm.
49
+ # While Zitadel supports EdDSA (Ed25519), the platform is not currently
50
+ # configured to use it. When/if that changes, migrate this to EdDSA for
51
+ # post-quantum resistance. See also: go/auth/middleware.go (strongmind-platform).
44
52
  algorithm: 'RS256',
45
53
  iss: ENV['IDENTITY_BASE_URL'],
46
54
  leeway: 60
@@ -48,7 +56,70 @@ module JwtUtilities
48
56
  end
49
57
 
50
58
  def scope_valid?(payload, scopes)
51
- scopes.empty? || (payload['scope'].present? && scopes.all? { |scope| payload['scope'].include?(scope) })
59
+ return true if scopes.empty?
60
+
61
+ legacy_scope_valid?(payload, scopes) || roles_valid?(payload, scopes)
62
+ end
63
+
64
+ def legacy_scope_valid?(payload, scopes)
65
+ payload['scope'].present? && scopes.all? { |scope| payload['scope'].include?(scope) }
66
+ end
67
+
68
+ # Zitadel roles are provisioned 1-to-1 with the legacy scope strings, so a
69
+ # required scope is satisfied by a same-named grant in the roles claim.
70
+ # Comparison is case-insensitive to handle different casing conventions.
71
+ def roles_valid?(payload, scopes)
72
+ project_id = ENV['IDENTITY_PROJECT_ID']
73
+ return false if project_id.blank?
74
+ return false unless audience_valid?(payload)
75
+ return false unless project_id_valid?(payload)
76
+
77
+ roles = zitadel_roles(payload, project_id)
78
+ return false unless roles.present?
79
+
80
+ normalized_roles = roles.keys.map(&:downcase).to_set
81
+ scopes.all? { |scope| normalized_roles.include?(scope.downcase) }
82
+ end
83
+
84
+ def zitadel_roles(payload, project_id = nil)
85
+ payload.keys
86
+ .select { |key| key.to_s.match?(ZITADEL_ROLES_CLAIM_PATTERN) }
87
+ .select do |key|
88
+ claim_project_id = extract_project_id(key)
89
+ project_id.blank? || claim_project_id.nil? || claim_project_id == project_id
90
+ end
91
+ .each_with_object({}) do |key, acc|
92
+ value = payload[key]
93
+ acc.merge!(value) if value.is_a?(Hash)
94
+ end
95
+ .presence
96
+ end
97
+
98
+ def audience_valid?(payload)
99
+ aud_list = Array(payload['aud'])
100
+
101
+ if ENV['IDENTITY_PROJECT_ID'].present?
102
+ aud_list.include?(ENV['IDENTITY_PROJECT_ID'])
103
+ elsif ENV['IDENTITY_CLIENT_ID'].present?
104
+ aud_list.include?(ENV['IDENTITY_CLIENT_ID'])
105
+ else
106
+ true
107
+ end
108
+ end
109
+
110
+ def project_id_valid?(payload)
111
+ roles_claims = payload.keys.select { |key| key.to_s.match?(ZITADEL_ROLES_CLAIM_PATTERN) }
112
+ return true if roles_claims.empty?
113
+ return true if ENV['IDENTITY_PROJECT_ID'].blank?
114
+
115
+ roles_claims.any? do |claim_key|
116
+ project_id = extract_project_id(claim_key)
117
+ project_id.nil? || project_id == ENV['IDENTITY_PROJECT_ID']
118
+ end
119
+ end
120
+
121
+ def extract_project_id(claim_key)
122
+ claim_key.to_s[/project:([^:]+):/, 1]
52
123
  end
53
124
 
54
125
  def nonce_valid?(payload)
@@ -9,11 +9,11 @@ module Users
9
9
  skip_before_action :verify_authenticity_token, only: :endsession
10
10
 
11
11
  def login
12
- redirect_to user_strongmind_omniauth_authorize_url, allow_other_host: true
12
+ redirect_to unauthenticated_root_path
13
13
  end
14
14
 
15
15
  def new
16
- redirect_to user_strongmind_omniauth_authorize_url, allow_other_host: true
16
+ redirect_to unauthenticated_root_path
17
17
  end
18
18
 
19
19
  def endsession
@@ -38,7 +38,7 @@ module Users
38
38
  def initiate_backchannel_logout
39
39
  Rails.logger.info "[DEBUG] INITIATE BACKCHANNEL LOGOUT - User: #{current_user&.id}, Session: #{session.id}, Refresh Token: #{session[:refresh_token]}"
40
40
 
41
- redirect_to user_strongmind_omniauth_authorize_url, allow_other_host: true and return unless current_user
41
+ redirect_to unauthenticated_root_path and return unless current_user
42
42
 
43
43
  if session[:refresh_token].blank? && current_user&.auth_token_cache.blank?
44
44
  Rails.logger.error "[DEBUG] LOGOUT ISSUE DETECTED - User: #{current_user.id}, Session: #{session.id}, No tokens available"
@@ -1,5 +1,5 @@
1
1
  module Strongmind
2
2
  module Auth
3
- VERSION = "1.1.138"
3
+ VERSION = "1.2.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strongmind-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.138
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Team Belding
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-15 00:00:00.000000000 Z
11
+ date: 2026-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails