rapitapir 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 (157) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +57 -0
  4. data/CHANGELOG.md +94 -0
  5. data/CLEANUP_SUMMARY.md +155 -0
  6. data/CONTRIBUTING.md +280 -0
  7. data/LICENSE +21 -0
  8. data/README.md +485 -0
  9. data/debug_hash.rb +20 -0
  10. data/docs/EXTENSION_COMPARISON.md +388 -0
  11. data/docs/SINATRA_EXTENSION.md +467 -0
  12. data/docs/archive/PHASE_1_2_COMPLETE.md +77 -0
  13. data/docs/archive/PHASE_1_3_COMPLETE.md +152 -0
  14. data/docs/archive/PHASE_2_1_OBSERVABILITY_COMPLETED.md +203 -0
  15. data/docs/archive/PHASE_2_SUMMARY.md +209 -0
  16. data/docs/archive/REFACTORING_SUMMARY.md +184 -0
  17. data/docs/archive/phase_1_3_plan.md +136 -0
  18. data/docs/archive/sinatra_extension_summary.md +188 -0
  19. data/docs/archive/sinatra_working_solution.md +113 -0
  20. data/docs/archive/typescript-client-generator-summary.md +259 -0
  21. data/docs/auto-derivation.md +146 -0
  22. data/docs/blueprint.md +1091 -0
  23. data/docs/endpoint-definition.md +211 -0
  24. data/docs/github_pages_fix.md +52 -0
  25. data/docs/github_pages_setup.md +49 -0
  26. data/docs/implementation-status.md +357 -0
  27. data/docs/observability.md +647 -0
  28. data/docs/phase3-plan.md +108 -0
  29. data/docs/sinatra_rapitapir.md +87 -0
  30. data/docs/type_shortcuts.md +146 -0
  31. data/examples/README_ENTERPRISE.md +202 -0
  32. data/examples/authentication_example.rb +192 -0
  33. data/examples/auto_derivation_ruby_friendly.rb +163 -0
  34. data/examples/cli/user_api_endpoints.rb +56 -0
  35. data/examples/client/typescript_client_example.rb +102 -0
  36. data/examples/client/user-api-client.ts +193 -0
  37. data/examples/demo_api.rb +41 -0
  38. data/examples/docs/documentation_example.rb +112 -0
  39. data/examples/docs/user-api-docs.html +789 -0
  40. data/examples/docs/user-api-docs.md +403 -0
  41. data/examples/enhanced_auto_derivation_test.rb +83 -0
  42. data/examples/enterprise_extension_demo.rb +417 -0
  43. data/examples/enterprise_rapitapir_api.rb +662 -0
  44. data/examples/getting_started_extension.rb +218 -0
  45. data/examples/hello_world.rb +74 -0
  46. data/examples/oauth2/.env.example +19 -0
  47. data/examples/oauth2/README.md +205 -0
  48. data/examples/oauth2/generic_oauth2_api.rb +226 -0
  49. data/examples/oauth2/get_token.rb +72 -0
  50. data/examples/oauth2/songs_api_with_auth0.rb +320 -0
  51. data/examples/oauth2/test_api.sh +16 -0
  52. data/examples/oauth2/test_songs_api.sh +110 -0
  53. data/examples/observability/.env.example +35 -0
  54. data/examples/observability/README.md +230 -0
  55. data/examples/observability/README_HONEYCOMB.md +332 -0
  56. data/examples/observability/advanced_setup.rb +384 -0
  57. data/examples/observability/basic_setup.rb +192 -0
  58. data/examples/observability/complete_test.rb +121 -0
  59. data/examples/observability/honeycomb_example.rb +523 -0
  60. data/examples/observability/honeycomb_rapitapir_clean.rb +488 -0
  61. data/examples/observability/honeycomb_rapitapir_example.rb +523 -0
  62. data/examples/observability/honeycomb_working_example.rb +489 -0
  63. data/examples/observability/quick_test.rb +78 -0
  64. data/examples/observability/simple_test.rb +14 -0
  65. data/examples/observability/test_honeycomb_demo.rb +354 -0
  66. data/examples/observability/test_live_honeycomb.rb +111 -0
  67. data/examples/observability/test_validation.rb +78 -0
  68. data/examples/observability/test_working_validation.rb +66 -0
  69. data/examples/openapi/user_api_schema.rb +132 -0
  70. data/examples/production_ready_example.rb +105 -0
  71. data/examples/rails/users_controller.rb +146 -0
  72. data/examples/readme/basic_sinatra_example.rb +128 -0
  73. data/examples/server/user_api.rb +179 -0
  74. data/examples/simple_auto_derivation_demo.rb +44 -0
  75. data/examples/simple_demo_api.rb +18 -0
  76. data/examples/sinatra/user_app.rb +127 -0
  77. data/examples/t_shortcut_demo.rb +59 -0
  78. data/examples/user_api.rb +190 -0
  79. data/examples/working_getting_started.rb +184 -0
  80. data/examples/working_simple_example.rb +195 -0
  81. data/lib/rapitapir/auth/configuration.rb +129 -0
  82. data/lib/rapitapir/auth/context.rb +122 -0
  83. data/lib/rapitapir/auth/errors.rb +104 -0
  84. data/lib/rapitapir/auth/middleware.rb +324 -0
  85. data/lib/rapitapir/auth/oauth2.rb +350 -0
  86. data/lib/rapitapir/auth/schemes.rb +420 -0
  87. data/lib/rapitapir/auth.rb +113 -0
  88. data/lib/rapitapir/cli/command.rb +535 -0
  89. data/lib/rapitapir/cli/server.rb +243 -0
  90. data/lib/rapitapir/cli/validator.rb +373 -0
  91. data/lib/rapitapir/client/generator_base.rb +272 -0
  92. data/lib/rapitapir/client/typescript_generator.rb +350 -0
  93. data/lib/rapitapir/core/endpoint.rb +158 -0
  94. data/lib/rapitapir/core/enhanced_endpoint.rb +235 -0
  95. data/lib/rapitapir/core/input.rb +182 -0
  96. data/lib/rapitapir/core/output.rb +164 -0
  97. data/lib/rapitapir/core/request.rb +19 -0
  98. data/lib/rapitapir/core/response.rb +17 -0
  99. data/lib/rapitapir/docs/html_generator.rb +780 -0
  100. data/lib/rapitapir/docs/markdown_generator.rb +464 -0
  101. data/lib/rapitapir/dsl/endpoint_dsl.rb +116 -0
  102. data/lib/rapitapir/dsl/enhanced_endpoint_dsl.rb +62 -0
  103. data/lib/rapitapir/dsl/enhanced_input.rb +73 -0
  104. data/lib/rapitapir/dsl/enhanced_output.rb +63 -0
  105. data/lib/rapitapir/dsl/enhanced_structures.rb +393 -0
  106. data/lib/rapitapir/dsl/fluent_dsl.rb +72 -0
  107. data/lib/rapitapir/dsl/fluent_endpoint_builder.rb +316 -0
  108. data/lib/rapitapir/dsl/http_verbs.rb +77 -0
  109. data/lib/rapitapir/dsl/input_methods.rb +47 -0
  110. data/lib/rapitapir/dsl/observability_methods.rb +81 -0
  111. data/lib/rapitapir/dsl/output_methods.rb +43 -0
  112. data/lib/rapitapir/dsl/type_resolution.rb +43 -0
  113. data/lib/rapitapir/observability/configuration.rb +108 -0
  114. data/lib/rapitapir/observability/health_check.rb +236 -0
  115. data/lib/rapitapir/observability/logging.rb +270 -0
  116. data/lib/rapitapir/observability/metrics.rb +203 -0
  117. data/lib/rapitapir/observability/middleware.rb +243 -0
  118. data/lib/rapitapir/observability/tracing.rb +143 -0
  119. data/lib/rapitapir/observability.rb +28 -0
  120. data/lib/rapitapir/openapi/schema_generator.rb +403 -0
  121. data/lib/rapitapir/schema.rb +136 -0
  122. data/lib/rapitapir/server/enhanced_rack_adapter.rb +379 -0
  123. data/lib/rapitapir/server/middleware.rb +120 -0
  124. data/lib/rapitapir/server/path_matcher.rb +45 -0
  125. data/lib/rapitapir/server/rack_adapter.rb +215 -0
  126. data/lib/rapitapir/server/rails_adapter.rb +17 -0
  127. data/lib/rapitapir/server/rails_adapter_class.rb +53 -0
  128. data/lib/rapitapir/server/rails_controller.rb +72 -0
  129. data/lib/rapitapir/server/rails_input_processor.rb +73 -0
  130. data/lib/rapitapir/server/rails_response_handler.rb +29 -0
  131. data/lib/rapitapir/server/sinatra_adapter.rb +200 -0
  132. data/lib/rapitapir/server/sinatra_integration.rb +93 -0
  133. data/lib/rapitapir/sinatra/configuration.rb +91 -0
  134. data/lib/rapitapir/sinatra/extension.rb +214 -0
  135. data/lib/rapitapir/sinatra/oauth2_helpers.rb +236 -0
  136. data/lib/rapitapir/sinatra/resource_builder.rb +152 -0
  137. data/lib/rapitapir/sinatra/swagger_ui_generator.rb +166 -0
  138. data/lib/rapitapir/sinatra_rapitapir.rb +40 -0
  139. data/lib/rapitapir/types/array.rb +163 -0
  140. data/lib/rapitapir/types/auto_derivation.rb +265 -0
  141. data/lib/rapitapir/types/base.rb +146 -0
  142. data/lib/rapitapir/types/boolean.rb +46 -0
  143. data/lib/rapitapir/types/date.rb +92 -0
  144. data/lib/rapitapir/types/datetime.rb +98 -0
  145. data/lib/rapitapir/types/email.rb +32 -0
  146. data/lib/rapitapir/types/float.rb +134 -0
  147. data/lib/rapitapir/types/hash.rb +161 -0
  148. data/lib/rapitapir/types/integer.rb +143 -0
  149. data/lib/rapitapir/types/object.rb +156 -0
  150. data/lib/rapitapir/types/optional.rb +65 -0
  151. data/lib/rapitapir/types/string.rb +185 -0
  152. data/lib/rapitapir/types/uuid.rb +32 -0
  153. data/lib/rapitapir/types.rb +155 -0
  154. data/lib/rapitapir/version.rb +5 -0
  155. data/lib/rapitapir.rb +173 -0
  156. data/rapitapir.gemspec +66 -0
  157. metadata +387 -0
@@ -0,0 +1,420 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'json'
5
+ require 'openssl'
6
+ require_relative 'configuration'
7
+ require_relative 'errors'
8
+ require_relative 'context'
9
+
10
+ module RapiTapir
11
+ module Auth
12
+ module Schemes
13
+ # Base class for all authentication schemes
14
+ # Defines the interface that all authentication schemes must implement
15
+ class Base
16
+ attr_reader :name, :config
17
+
18
+ def initialize(name, config = {})
19
+ @name = name
20
+ @config = config
21
+ end
22
+
23
+ def authenticate(request)
24
+ raise NotImplementedError, 'Subclasses must implement #authenticate'
25
+ end
26
+
27
+ def challenge
28
+ raise NotImplementedError, 'Subclasses must implement #challenge'
29
+ end
30
+
31
+ protected
32
+
33
+ def create_context(user: nil, scopes: [], token: nil, metadata: {})
34
+ Context.new(
35
+ user: user,
36
+ scopes: scopes,
37
+ token: token,
38
+ metadata: metadata.merge(scheme: @name)
39
+ )
40
+ end
41
+ end
42
+
43
+ # Bearer token authentication scheme
44
+ # Authenticates using tokens in the Authorization header
45
+ class BearerToken < Base
46
+ def initialize(name, config = {})
47
+ super
48
+ @token_validator = config[:token_validator] || method(:default_token_validator)
49
+ @realm = config[:realm] || 'API'
50
+ end
51
+
52
+ def authenticate(request)
53
+ auth_header = request.env['HTTP_AUTHORIZATION']
54
+ return nil unless auth_header
55
+
56
+ token = extract_bearer_token(auth_header)
57
+ return nil unless token
58
+
59
+ user_data = @token_validator.call(token)
60
+ return nil unless user_data
61
+
62
+ create_context(
63
+ user: user_data[:user],
64
+ scopes: user_data[:scopes] || [],
65
+ token: token,
66
+ metadata: { token_type: 'bearer' }
67
+ )
68
+ rescue InvalidTokenError
69
+ nil
70
+ end
71
+
72
+ def challenge
73
+ "Bearer realm=\"#{@realm}\""
74
+ end
75
+
76
+ private
77
+
78
+ def extract_bearer_token(auth_header)
79
+ match = auth_header.match(/\ABearer\s+(.+)\z/i)
80
+ match ? match[1] : nil
81
+ end
82
+
83
+ def default_token_validator(token)
84
+ # Default implementation - should be overridden
85
+ return nil if token.nil? || token.empty?
86
+
87
+ {
88
+ user: { id: 'default_user', name: 'Default User' },
89
+ scopes: ['read']
90
+ }
91
+ end
92
+ end
93
+
94
+ # API key authentication scheme
95
+ # Authenticates using API keys in headers or query parameters
96
+ class ApiKey < Base
97
+ def initialize(name, config = {})
98
+ super
99
+ @key_validator = config[:key_validator] || method(:default_key_validator)
100
+ @header_name = config[:header_name] || 'X-API-Key'
101
+ @query_param = config[:query_param] || 'api_key'
102
+ @location = config[:location] || :header # :header, :query, or :both
103
+ end
104
+
105
+ def authenticate(request)
106
+ api_key = extract_api_key(request)
107
+ return nil unless api_key
108
+
109
+ user_data = @key_validator.call(api_key)
110
+ return nil unless user_data
111
+
112
+ create_context(
113
+ user: user_data[:user],
114
+ scopes: user_data[:scopes] || [],
115
+ token: api_key,
116
+ metadata: {
117
+ token_type: 'api_key',
118
+ location: @location
119
+ }
120
+ )
121
+ rescue InvalidTokenError
122
+ nil
123
+ end
124
+
125
+ def challenge
126
+ 'ApiKey'
127
+ end
128
+
129
+ private
130
+
131
+ def extract_api_key(request)
132
+ case @location
133
+ when :header
134
+ request.env["HTTP_#{@header_name.upcase.tr('-', '_')}"]
135
+ when :query
136
+ request.params[@query_param]
137
+ when :both
138
+ request.env["HTTP_#{@header_name.upcase.tr('-', '_')}"] ||
139
+ request.params[@query_param]
140
+ end
141
+ end
142
+
143
+ def default_key_validator(key)
144
+ # Default implementation - should be overridden
145
+ return nil if key.nil? || key.empty?
146
+
147
+ {
148
+ user: { id: 'api_user', name: 'API User' },
149
+ scopes: ['api']
150
+ }
151
+ end
152
+ end
153
+
154
+ # HTTP Basic authentication scheme
155
+ # Authenticates using username and password in the Authorization header
156
+ class BasicAuth < Base
157
+ def initialize(name, config = {})
158
+ super
159
+ @credential_validator = config[:credential_validator] || method(:default_credential_validator)
160
+ @realm = config[:realm] || 'API'
161
+ end
162
+
163
+ def authenticate(request)
164
+ auth_header = request.env['HTTP_AUTHORIZATION']
165
+ return nil unless auth_header
166
+
167
+ credentials = extract_basic_credentials(auth_header)
168
+ return nil unless credentials
169
+
170
+ user_data = @credential_validator.call(credentials[:username], credentials[:password])
171
+ return nil unless user_data
172
+
173
+ create_context(
174
+ user: user_data[:user],
175
+ scopes: user_data[:scopes] || [],
176
+ metadata: {
177
+ token_type: 'basic',
178
+ username: credentials[:username]
179
+ }
180
+ )
181
+ rescue AuthenticationError
182
+ nil
183
+ end
184
+
185
+ def challenge
186
+ "Basic realm=\"#{@realm}\""
187
+ end
188
+
189
+ private
190
+
191
+ def extract_basic_credentials(auth_header)
192
+ match = auth_header.match(/\ABasic\s+(.+)\z/i)
193
+ return nil unless match
194
+
195
+ decoded = Base64.decode64(match[1])
196
+ username, password = decoded.split(':', 2)
197
+
198
+ return nil if username.nil? || password.nil?
199
+
200
+ { username: username, password: password }
201
+ rescue ArgumentError
202
+ nil
203
+ end
204
+
205
+ def default_credential_validator(username, password)
206
+ # Default implementation - should be overridden
207
+ return nil if username.nil? || password.nil? || username.empty? || password.empty?
208
+
209
+ {
210
+ user: { id: username, name: username.capitalize },
211
+ scopes: ['basic']
212
+ }
213
+ end
214
+ end
215
+
216
+ # OAuth2 authentication scheme
217
+ # Authenticates using OAuth2 tokens with optional token introspection
218
+ class OAuth2 < Base
219
+ def initialize(name, config = {})
220
+ super
221
+ @token_validator = config[:token_validator] || method(:default_oauth2_validator)
222
+ @introspection_endpoint = config[:introspection_endpoint]
223
+ @client_id = config[:client_id]
224
+ @client_secret = config[:client_secret]
225
+ @realm = config[:realm] || 'API'
226
+ end
227
+
228
+ def authenticate(request)
229
+ auth_header = request.env['HTTP_AUTHORIZATION']
230
+ return nil unless auth_header
231
+
232
+ token = extract_bearer_token(auth_header)
233
+ return nil unless token
234
+
235
+ user_data = validate_oauth2_token(token)
236
+ return nil unless user_data
237
+
238
+ create_context(
239
+ user: user_data[:user],
240
+ scopes: user_data[:scopes] || [],
241
+ token: token,
242
+ metadata: {
243
+ token_type: 'oauth2',
244
+ client_id: user_data[:client_id],
245
+ expires_at: user_data[:expires_at]
246
+ }
247
+ )
248
+ rescue InvalidTokenError, TokenExpiredError
249
+ nil
250
+ end
251
+
252
+ def challenge
253
+ "Bearer realm=\"#{@realm}\""
254
+ end
255
+
256
+ private
257
+
258
+ def extract_bearer_token(auth_header)
259
+ match = auth_header.match(/\ABearer\s+(.+)\z/i)
260
+ match ? match[1] : nil
261
+ end
262
+
263
+ def validate_oauth2_token(token)
264
+ if @introspection_endpoint
265
+ introspect_token(token)
266
+ else
267
+ @token_validator.call(token)
268
+ end
269
+ end
270
+
271
+ def introspect_token(token)
272
+ # OAuth2 token introspection (RFC 7662)
273
+ # This would typically make an HTTP request to the introspection endpoint
274
+ # For now, we'll use the configured validator
275
+ @token_validator.call(token)
276
+ end
277
+
278
+ def default_oauth2_validator(token)
279
+ # Default implementation - should be overridden
280
+ return nil if token.nil? || token.empty?
281
+
282
+ {
283
+ user: { id: 'oauth_user', name: 'OAuth User' },
284
+ scopes: %w[read write],
285
+ client_id: 'default_client',
286
+ expires_at: Time.now + 3600
287
+ }
288
+ end
289
+ end
290
+
291
+ # JWT (JSON Web Token) authentication scheme
292
+ # Authenticates using signed JWT tokens with verification
293
+ class JWT < Base
294
+ def initialize(name, config = {})
295
+ super
296
+ @secret = config[:secret] || raise(ArgumentError, 'JWT secret is required')
297
+ @algorithm = config[:algorithm] || 'HS256'
298
+ @verify_expiration = config.fetch(:verify_expiration, true)
299
+ @verify_issuer = config[:verify_issuer]
300
+ @verify_audience = config[:verify_audience]
301
+ @realm = config[:realm] || 'API'
302
+ end
303
+
304
+ def authenticate(request)
305
+ auth_header = request.env['HTTP_AUTHORIZATION']
306
+ return nil unless auth_header
307
+
308
+ token = extract_bearer_token(auth_header)
309
+ return nil unless token
310
+
311
+ payload = decode_jwt_token(token)
312
+ return nil unless payload
313
+
314
+ create_context(
315
+ user: extract_user_from_payload(payload),
316
+ scopes: extract_scopes_from_payload(payload),
317
+ token: token,
318
+ metadata: {
319
+ token_type: 'jwt',
320
+ payload: payload
321
+ }
322
+ )
323
+ rescue InvalidTokenError, TokenExpiredError
324
+ nil
325
+ end
326
+
327
+ def challenge
328
+ "Bearer realm=\"#{@realm}\""
329
+ end
330
+
331
+ private
332
+
333
+ def extract_bearer_token(auth_header)
334
+ match = auth_header.match(/\ABearer\s+(.+)\z/i)
335
+ match ? match[1] : nil
336
+ end
337
+
338
+ def decode_jwt_token(token)
339
+ # This is a simplified JWT decoder
340
+ # In a real implementation, you'd use a library like ruby-jwt
341
+ parts = split_jwt_token(token)
342
+ return nil unless parts
343
+
344
+ begin
345
+ _, payload, signature = parse_jwt_parts(parts)
346
+ return nil unless valid_jwt_signature?(parts, signature)
347
+ return nil unless valid_jwt_claims?(payload)
348
+
349
+ payload
350
+ rescue JSON::ParserError, ArgumentError
351
+ nil
352
+ end
353
+ end
354
+
355
+ def split_jwt_token(token)
356
+ parts = token.split('.')
357
+ parts.length == 3 ? parts : nil
358
+ end
359
+
360
+ def parse_jwt_parts(parts)
361
+ header_b64 = add_base64_padding(parts[0])
362
+ payload_b64 = add_base64_padding(parts[1])
363
+
364
+ header = JSON.parse(Base64.urlsafe_decode64(header_b64))
365
+ payload = JSON.parse(Base64.urlsafe_decode64(payload_b64))
366
+ signature = parts[2]
367
+
368
+ [header, payload, signature]
369
+ end
370
+
371
+ def add_base64_padding(base64_string)
372
+ missing_padding = 4 - (base64_string.length % 4)
373
+ base64_string += '=' * missing_padding if missing_padding != 4
374
+ base64_string
375
+ end
376
+
377
+ def valid_jwt_signature?(parts, signature)
378
+ expected_signature = Base64.urlsafe_encode64(
379
+ OpenSSL::HMAC.digest('SHA256', @secret, "#{parts[0]}.#{parts[1]}")
380
+ ).tr('=', '')
381
+
382
+ signature == expected_signature
383
+ end
384
+
385
+ def valid_jwt_claims?(payload)
386
+ return false if @verify_expiration && jwt_expired?(payload)
387
+ return false if @verify_issuer && payload['iss'] != @verify_issuer
388
+ return false if @verify_audience && payload['aud'] != @verify_audience
389
+
390
+ true
391
+ end
392
+
393
+ def jwt_expired?(payload)
394
+ payload['exp'] && (Time.at(payload['exp']) < Time.now)
395
+ end
396
+
397
+ def extract_user_from_payload(payload)
398
+ user_data = payload['user'] || payload['sub']
399
+ return { id: user_data, name: user_data } if user_data.is_a?(String)
400
+
401
+ user_data || { id: payload['sub'], name: payload['name'] || payload['sub'] }
402
+ end
403
+
404
+ def extract_scopes_from_payload(payload)
405
+ scopes = payload['scopes'] || payload['scope']
406
+ return [] unless scopes
407
+
408
+ case scopes
409
+ when Array
410
+ scopes
411
+ when String
412
+ scopes.split
413
+ else
414
+ []
415
+ end
416
+ end
417
+ end
418
+ end
419
+ end
420
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'auth/configuration'
4
+ require_relative 'auth/errors'
5
+ require_relative 'auth/context'
6
+ require_relative 'auth/schemes'
7
+ require_relative 'auth/middleware'
8
+ require_relative 'auth/oauth2'
9
+
10
+ module RapiTapir
11
+ # Authentication and authorization module for RapiTapir
12
+ #
13
+ # Provides comprehensive authentication schemes including Bearer tokens, API keys,
14
+ # JWT, OAuth2, and basic authentication. Also includes authorization middleware,
15
+ # rate limiting, CORS support, and security features.
16
+ #
17
+ # @example Configure authentication
18
+ # RapiTapir::Auth.configure do |config|
19
+ # config.jwt_secret = 'your-secret'
20
+ # config.rate_limiting.requests_per_minute = 100
21
+ # end
22
+ #
23
+ # @example Create authentication schemes
24
+ # bearer_auth = RapiTapir::Auth.bearer_token(:bearer, { token_validator: proc { |token| ... } })
25
+ module Auth
26
+ class << self
27
+ attr_accessor :configuration
28
+
29
+ def configure
30
+ self.configuration ||= Configuration.new
31
+ yield(configuration) if block_given?
32
+ configuration
33
+ end
34
+
35
+ def config
36
+ self.configuration ||= Configuration.new
37
+ end
38
+
39
+ # DSL methods for creating authentication schemes
40
+ def bearer_token(name = :bearer, config = {})
41
+ Schemes::BearerToken.new(name, config)
42
+ end
43
+
44
+ def api_key(name = :api_key, config = {})
45
+ Schemes::ApiKey.new(name, config)
46
+ end
47
+
48
+ def basic_auth(name = :basic, config = {})
49
+ Schemes::BasicAuth.new(name, config)
50
+ end
51
+
52
+ def oauth2(name = :oauth2, config = {})
53
+ OAuth2::GenericScheme.new(name, config)
54
+ end
55
+
56
+ def oauth2_auth0(name = :oauth2_auth0, config = {})
57
+ OAuth2::Auth0Scheme.new(name, config)
58
+ end
59
+
60
+ # Alias methods for consistency with Sinatra helpers
61
+ def auth0_oauth2(name = :oauth2, config = {})
62
+ OAuth2::Auth0Scheme.new(name, config)
63
+ end
64
+
65
+ def oauth2_introspection(name = :oauth2, config = {})
66
+ OAuth2::GenericScheme.new(name, config)
67
+ end
68
+
69
+ def jwt(name = :jwt, config = {})
70
+ Schemes::JWT.new(name, config)
71
+ end
72
+
73
+ # Middleware factory methods
74
+ def authentication_middleware(auth_schemes = {})
75
+ Middleware::AuthenticationMiddleware.new(nil, auth_schemes)
76
+ end
77
+
78
+ def authorization_middleware(required_scopes: [], require_all: true)
79
+ Middleware::AuthorizationMiddleware.new(nil, required_scopes: required_scopes, require_all: require_all)
80
+ end
81
+
82
+ def rate_limiting_middleware(config = {})
83
+ Middleware::RateLimitingMiddleware.new(nil, config)
84
+ end
85
+
86
+ def cors_middleware(config = {})
87
+ Middleware::CorsMiddleware.new(nil, config)
88
+ end
89
+
90
+ def security_headers_middleware(config = {})
91
+ Middleware::SecurityHeadersMiddleware.new(nil, config)
92
+ end
93
+
94
+ # Context access
95
+ def current_context
96
+ ContextStore.current
97
+ end
98
+
99
+ def current_user
100
+ current_context&.user
101
+ end
102
+
103
+ def authenticated?
104
+ current_context&.authenticated? || false
105
+ end
106
+
107
+ def scope?(scope)
108
+ current_context&.scope?(scope) || false
109
+ end
110
+ alias has_scope? scope?
111
+ end
112
+ end
113
+ end