rodauth-ciba 0.1.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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +34 -0
  3. data/LICENSE +21 -0
  4. data/README.md +106 -0
  5. data/docs/adr/0003-follow-rodauth-hook-conventions.md +9 -0
  6. data/docs/adr/0004-retain-atomic-issuance-and-stage-alignment.md +16 -0
  7. data/docs/alignment-closeout.md +136 -0
  8. data/docs/alignment-roadmap.md +160 -0
  9. data/docs/api.md +337 -0
  10. data/docs/authentication-claims.md +80 -0
  11. data/docs/authorization-details.md +50 -0
  12. data/docs/claims.md +46 -0
  13. data/docs/failure-contract.md +34 -0
  14. data/docs/id-token-encryption.md +288 -0
  15. data/docs/id-token-hint.md +174 -0
  16. data/docs/login-hint-token.md +35 -0
  17. data/docs/max-age.md +65 -0
  18. data/docs/node-alignment.md +336 -0
  19. data/docs/operations.md +130 -0
  20. data/docs/outbound-http.md +52 -0
  21. data/docs/pairwise.md +76 -0
  22. data/docs/ping.md +118 -0
  23. data/docs/protocol-coverage.md +114 -0
  24. data/docs/refresh-tokens.md +151 -0
  25. data/docs/release-readiness.md +75 -0
  26. data/docs/release-validation.md +498 -0
  27. data/docs/request-context.md +51 -0
  28. data/docs/requirements-audit-current.json +3221 -0
  29. data/docs/requirements-audit-current.md +424 -0
  30. data/docs/requirements-audit.json +3190 -0
  31. data/docs/requirements-audit.md +175 -0
  32. data/docs/research/access-token-revocation-reference.md +81 -0
  33. data/docs/research/alignment-partial-triage.md +50 -0
  34. data/docs/research/claims-reference-contract.md +33 -0
  35. data/docs/research/client-assertion-algorithm-contract.md +177 -0
  36. data/docs/research/completion-error-boundaries.md +56 -0
  37. data/docs/research/consent-api-evolution.md +59 -0
  38. data/docs/research/dpop-reference-contract.md +423 -0
  39. data/docs/research/id-token-hint-reference-contract.md +254 -0
  40. data/docs/research/mtls-reference-contract.md +449 -0
  41. data/docs/research/node-ciba-lifecycle.md +30 -0
  42. data/docs/research/node-oidc-provider-comparison.md +45 -0
  43. data/docs/research/pairwise-reference-contract.md +264 -0
  44. data/docs/research/ping-reference-contract.md +165 -0
  45. data/docs/research/rar-reference-contract.md +55 -0
  46. data/docs/research/refresh-reference-contract.md +85 -0
  47. data/docs/research/registration-reference-contract.md +567 -0
  48. data/docs/research/request-parameter-inventory.md +42 -0
  49. data/docs/research/required-parameter-inventory.md +39 -0
  50. data/docs/research/resource-reference-contract.md +40 -0
  51. data/docs/research/signed-request-reference-contract.md +113 -0
  52. data/docs/resources.md +55 -0
  53. data/docs/security-review.md +294 -0
  54. data/docs/signed-requests.md +162 -0
  55. data/docs/user-code.md +73 -0
  56. data/docs/validation/node-alignment-tests.txt +10 -0
  57. data/examples/client.rb +39 -0
  58. data/examples/demo_app.rb +121 -0
  59. data/examples/dpop_smoke.rb +169 -0
  60. data/examples/ecdh_id_token_smoke.rb +84 -0
  61. data/examples/edwards_id_token_smoke.rb +48 -0
  62. data/examples/edwards_smoke.rb +87 -0
  63. data/examples/edwards_tls_smoke.rb +73 -0
  64. data/examples/encrypted_id_token_smoke.rb +73 -0
  65. data/examples/encryption_tls_smoke.rb +123 -0
  66. data/examples/extensions_smoke.rb +226 -0
  67. data/examples/fixtures/edwards-requests.json +30 -0
  68. data/examples/hmac_id_token_smoke.rb +72 -0
  69. data/examples/migration.rb +18 -0
  70. data/examples/mtls_smoke.rb +395 -0
  71. data/examples/ping_smoke.rb +226 -0
  72. data/examples/registration_smoke.rb +73 -0
  73. data/examples/server.rb +14 -0
  74. data/examples/smoke.rb +46 -0
  75. data/examples/symmetric_id_token_smoke.rb +83 -0
  76. data/lib/rodauth/ciba/authorization_details.rb +60 -0
  77. data/lib/rodauth/ciba/dpop.rb +250 -0
  78. data/lib/rodauth/ciba/http.rb +69 -0
  79. data/lib/rodauth/ciba/id_token_encryption.rb +175 -0
  80. data/lib/rodauth/ciba/jwks_cache.rb +149 -0
  81. data/lib/rodauth/ciba/mtls.rb +174 -0
  82. data/lib/rodauth/ciba/pairwise.rb +105 -0
  83. data/lib/rodauth/ciba/registration.rb +489 -0
  84. data/lib/rodauth/ciba/schema.rb +182 -0
  85. data/lib/rodauth/ciba/version.rb +6 -0
  86. data/lib/rodauth/ciba.rb +4 -0
  87. data/lib/rodauth/features/ciba.rb +2185 -0
  88. metadata +231 -0
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ module CibaSupport
5
+ # Explicit migration helper. Never called automatically on application startup.
6
+ module Schema
7
+ # Preserve the signed token's original subject across client sector changes.
8
+ def self.add_pairwise_subject(db, table: :oauth_grants, column: :ciba_subject)
9
+ db.alter_table(table) { add_column column, String, size: 255 }
10
+ end
11
+
12
+ # Lookup only: management credentials remain verified against upstream's
13
+ # password hash. Existing clients have no digest until credentials are issued.
14
+ def self.add_registration_token_digest(db, table: :oauth_applications, column: :ciba_registration_token_digest)
15
+ db.alter_table(table) do
16
+ add_column column, String, size: 64
17
+ add_index column, unique: true
18
+ end
19
+ end
20
+
21
+ def self.add_request_context(db, table: :ciba_requests, column: :request_context)
22
+ db.alter_table(table) { add_column column, String, text: true }
23
+ end
24
+
25
+ # NULL preserves the output contract of already accepted requests/sources.
26
+ def self.add_authentication_claims(db, table: :ciba_requests, column: :authentication_claims)
27
+ db.alter_table(table) { add_column column, String, text: true }
28
+ end
29
+
30
+ def self.add_request_max_age(db, table: :ciba_requests, column: :max_age)
31
+ db.alter_table(table) { add_column column, :bigint }
32
+ end
33
+
34
+ # NULL retains access_denied for previously denied requests.
35
+ def self.add_completion_error(db, table: :ciba_requests, column: :completion_error)
36
+ db.alter_table(table) { add_column column, String, text: true }
37
+ end
38
+
39
+ # Optional refresh sources are independent of collected access tokens and
40
+ # request cleanup. Consumed digests remain until expiry for replay detection.
41
+ # Creating storage does not enable protocol support.
42
+ def self.create_refresh_tokens(db, table: :ciba_refresh_tokens, grants_table: :ciba_grants,
43
+ accounts_table: :accounts, account_key: :id, account_type: Integer,
44
+ applications_table: :oauth_applications, application_key: :id,
45
+ application_type: Integer)
46
+ db.create_table(table) do
47
+ primary_key :id
48
+ String :token_digest, size: 64, unique: true, null: false
49
+ foreign_key :grant_id, grants_table, null: false, on_delete: :cascade
50
+ foreign_key :account_id, accounts_table, key: account_key, type: account_type, null: false, on_delete: :cascade
51
+ foreign_key :oauth_application_id, applications_table, key: application_key, type: application_type, null: false, on_delete: :cascade
52
+ String :scopes, text: true, null: false
53
+ %i[created_at issued_at expires_at].each { |name| Bignum name, null: false }
54
+ Bignum :auth_time
55
+ Bignum :consumed_at
56
+ Integer :lock_version, null: false, default: 0
57
+ Integer :rotations, null: false, default: 0
58
+ %i[acr amr nonce authentication_claims requested_claims requested_resources requested_authorization_details].each do |name|
59
+ String name, text: true
60
+ end
61
+ index :grant_id
62
+ index :expires_at
63
+ end
64
+ end
65
+
66
+ def self.add_ping(db, applications_table: :oauth_applications,
67
+ endpoint_column: :backchannel_client_notification_endpoint,
68
+ table: :ciba_ping_deliveries, requests_table: :ciba_requests, request_key: :id)
69
+ db.alter_table(applications_table) { add_column endpoint_column, String, text: true }
70
+ db.create_table(table) do
71
+ foreign_key :request_id, requests_table, key: request_key, primary_key: true, null: false, on_delete: :cascade
72
+ String :auth_req_id, null: false
73
+ String :notification_token, text: true, null: false
74
+ String :endpoint, text: true, null: false
75
+ end
76
+ end
77
+
78
+ def self.add_user_code(db, table: :oauth_applications, column: :backchannel_user_code_parameter)
79
+ db.alter_table(table) { add_column column, TrueClass }
80
+ end
81
+
82
+ def self.add_signed_requests(db, table: :oauth_applications,
83
+ column: :backchannel_authentication_request_signing_alg)
84
+ db.alter_table(table) { add_column column, String, size: 16 }
85
+ end
86
+
87
+ # Storage foundation for optional RAR; no backfill implies no permission.
88
+ # Protocol activation is separate from this explicit migration.
89
+ def self.add_authorization_details(db, requests_table: :ciba_requests,
90
+ request_column: :requested_authorization_details,
91
+ grants_table: :ciba_grants, tokens_table: :oauth_grants,
92
+ applications_table: :oauth_applications)
93
+ db.alter_table(requests_table) { add_column request_column, String, text: true }
94
+ db.alter_table(grants_table) { add_column :authorization_details, String, text: true }
95
+ db.alter_table(tokens_table) { add_column :ciba_authorization_details, String, text: true }
96
+ db.alter_table(applications_table) { add_column :authorization_details_types, String, text: true }
97
+ end
98
+
99
+ # Optional resource indicators. Activate only after all workers are upgraded.
100
+ def self.add_resources(db, requests_table: :ciba_requests, request_column: :requested_resources,
101
+ grants_table: :ciba_grants, tokens_table: :oauth_grants)
102
+ db.alter_table(requests_table) { add_column request_column, String, text: true }
103
+ db.alter_table(grants_table) { add_column :resources, String, text: true }
104
+ db.alter_table(tokens_table) do
105
+ add_column :ciba_resource, String, text: true
106
+ add_column :ciba_resource_audience, String, text: true
107
+ end
108
+ end
109
+
110
+ # Optional explicit-claims capability; run once after the base schema.
111
+ def self.add_claims(db, requests_table: :ciba_requests, request_column: :requested_claims,
112
+ grants_table: :ciba_grants, tokens_table: :oauth_grants)
113
+ db.alter_table(requests_table) { add_column request_column, String, text: true }
114
+ db.alter_table(grants_table) { add_column :claims, String, text: true }
115
+ db.alter_table(tokens_table) { add_column :ciba_claims, String, text: true }
116
+ end
117
+
118
+ # Forward migration for development schemas created before nonce support.
119
+ def self.add_request_nonce(db, table: :ciba_requests, column: :nonce)
120
+ db.alter_table(table) { add_column column, String, text: true }
121
+ end
122
+
123
+ def self.add_client_metadata(db, table: :oauth_applications, column: :backchannel_token_delivery_mode)
124
+ db.alter_table(table) { add_column column, String, size: 8 }
125
+ end
126
+
127
+ # Shared replay ledger for JWT authentication by CIBA-capable clients.
128
+ def self.create_client_assertions(db, table: :ciba_client_assertions)
129
+ db.create_table(table) do
130
+ String :digest, size: 64, primary_key: true, null: false
131
+ Bignum :expires_at, null: false
132
+ index :expires_at
133
+ end
134
+ end
135
+
136
+ # Consent is separate from upstream OAuth grant rows, which also store tokens.
137
+ def self.create_grants(db, table: :ciba_grants, accounts_table: :accounts,
138
+ account_key: :id, account_type: Integer,
139
+ applications_table: :oauth_applications, application_key: :id,
140
+ application_type: Integer)
141
+ db.create_table(table) do
142
+ primary_key :id
143
+ foreign_key :account_id, accounts_table, key: account_key, type: account_type, null: false, on_delete: :cascade
144
+ foreign_key :oauth_application_id, applications_table, key: application_key, type: application_type, null: false, on_delete: :cascade
145
+ String :scopes, text: true, null: false
146
+ Bignum :created_at, null: false
147
+ Bignum :expires_at
148
+ Bignum :revoked_at
149
+ end
150
+ end
151
+
152
+ def self.add_grant_reference(db, table: :oauth_grants, column: :ciba_grant_id, grants_table: :ciba_grants)
153
+ db.alter_table(table) { add_foreign_key column, grants_table, index: true, on_delete: :cascade }
154
+ end
155
+
156
+ def self.create(db, table: :ciba_requests, columns: {}, accounts_table: :accounts,
157
+ account_key: :id, account_type: Integer, applications_table: :oauth_applications,
158
+ application_key: :id, application_type: Integer, grants_table: :ciba_grants)
159
+ col = ->(name) { columns.fetch(name, name) }
160
+ db.create_table(table) do
161
+ primary_key col[:id]
162
+ String col[:auth_req_id_digest], size: 64, unique: true, null: false
163
+ foreign_key col[:account_id], accounts_table, key: account_key, type: account_type, null: false, on_delete: :cascade
164
+ foreign_key col[:oauth_application_id], applications_table, key: application_key, type: application_type, null: false, on_delete: :cascade
165
+ foreign_key col[:grant_id], grants_table, on_delete: :cascade
166
+ String col[:scopes], text: true, null: false
167
+ String col[:status], size: 16, null: false, default: "pending"
168
+ Integer col[:lock_version], null: false, default: 0
169
+ %i[created_at expires_at].each { |name| Bignum col[name], null: false }
170
+ %i[last_polled_at completed_at consumed_at auth_time max_age].each { |name| Bignum col[name] }
171
+ Bignum col[:interval], null: false, default: 5
172
+ %i[binding_message acr_values acr amr nonce authentication_claims completion_error].each { |name| String col[name], text: true }
173
+ index [col[:status], col[:id]]
174
+ index col[:expires_at]
175
+ check(Sequel[col[:interval]] > 0)
176
+ check(Sequel[col[:lock_version]] >= 0)
177
+ check(Sequel[col[:status]] => %w[pending approved denied consumed])
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ module Rodauth
3
+ module CibaSupport
4
+ VERSION = "0.1.0"
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ require "rodauth/ciba/version"
3
+ require "rodauth/ciba/schema"
4
+ require "rodauth/features/ciba"