better_auth 0.3.0 → 0.5.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/README.md +24 -0
  4. data/lib/better_auth/adapters/internal_adapter.rb +10 -7
  5. data/lib/better_auth/adapters/memory.rb +57 -11
  6. data/lib/better_auth/adapters/sql.rb +123 -20
  7. data/lib/better_auth/api.rb +114 -9
  8. data/lib/better_auth/async.rb +70 -0
  9. data/lib/better_auth/configuration.rb +97 -7
  10. data/lib/better_auth/context.rb +165 -12
  11. data/lib/better_auth/cookies.rb +6 -4
  12. data/lib/better_auth/core.rb +2 -0
  13. data/lib/better_auth/crypto/jwe.rb +27 -5
  14. data/lib/better_auth/crypto.rb +32 -0
  15. data/lib/better_auth/database_hooks.rb +8 -8
  16. data/lib/better_auth/deprecate.rb +28 -0
  17. data/lib/better_auth/endpoint.rb +92 -5
  18. data/lib/better_auth/error.rb +8 -1
  19. data/lib/better_auth/host.rb +166 -0
  20. data/lib/better_auth/instrumentation.rb +74 -0
  21. data/lib/better_auth/logger.rb +31 -0
  22. data/lib/better_auth/middleware/origin_check.rb +2 -2
  23. data/lib/better_auth/oauth2.rb +94 -0
  24. data/lib/better_auth/plugins/admin/schema.rb +2 -2
  25. data/lib/better_auth/plugins/admin.rb +344 -16
  26. data/lib/better_auth/plugins/anonymous.rb +37 -3
  27. data/lib/better_auth/plugins/device_authorization.rb +102 -5
  28. data/lib/better_auth/plugins/dub.rb +148 -0
  29. data/lib/better_auth/plugins/email_otp.rb +261 -19
  30. data/lib/better_auth/plugins/expo.rb +17 -1
  31. data/lib/better_auth/plugins/generic_oauth.rb +67 -35
  32. data/lib/better_auth/plugins/jwt.rb +37 -4
  33. data/lib/better_auth/plugins/last_login_method.rb +2 -2
  34. data/lib/better_auth/plugins/magic_link.rb +66 -3
  35. data/lib/better_auth/plugins/mcp/authorization.rb +111 -0
  36. data/lib/better_auth/plugins/mcp/config.rb +51 -0
  37. data/lib/better_auth/plugins/mcp/consent.rb +31 -0
  38. data/lib/better_auth/plugins/mcp/legacy_aliases.rb +39 -0
  39. data/lib/better_auth/plugins/mcp/metadata.rb +81 -0
  40. data/lib/better_auth/plugins/mcp/registration.rb +31 -0
  41. data/lib/better_auth/plugins/mcp/resource_handler.rb +37 -0
  42. data/lib/better_auth/plugins/mcp/schema.rb +91 -0
  43. data/lib/better_auth/plugins/mcp/token.rb +108 -0
  44. data/lib/better_auth/plugins/mcp/userinfo.rb +37 -0
  45. data/lib/better_auth/plugins/mcp.rb +111 -263
  46. data/lib/better_auth/plugins/multi_session.rb +61 -3
  47. data/lib/better_auth/plugins/oauth_protocol.rb +173 -30
  48. data/lib/better_auth/plugins/oauth_proxy.rb +26 -6
  49. data/lib/better_auth/plugins/oidc_provider.rb +118 -14
  50. data/lib/better_auth/plugins/one_tap.rb +7 -2
  51. data/lib/better_auth/plugins/one_time_token.rb +42 -2
  52. data/lib/better_auth/plugins/open_api.rb +163 -318
  53. data/lib/better_auth/plugins/organization/schema.rb +6 -0
  54. data/lib/better_auth/plugins/organization.rb +186 -56
  55. data/lib/better_auth/plugins/phone_number.rb +141 -6
  56. data/lib/better_auth/plugins/siwe.rb +69 -3
  57. data/lib/better_auth/plugins/two_factor.rb +118 -41
  58. data/lib/better_auth/plugins/username.rb +57 -2
  59. data/lib/better_auth/rate_limiter.rb +38 -0
  60. data/lib/better_auth/request_state.rb +44 -0
  61. data/lib/better_auth/response.rb +42 -0
  62. data/lib/better_auth/router.rb +7 -1
  63. data/lib/better_auth/routes/account.rb +220 -42
  64. data/lib/better_auth/routes/email_verification.rb +98 -14
  65. data/lib/better_auth/routes/password.rb +126 -8
  66. data/lib/better_auth/routes/session.rb +128 -13
  67. data/lib/better_auth/routes/sign_in.rb +26 -2
  68. data/lib/better_auth/routes/sign_out.rb +13 -1
  69. data/lib/better_auth/routes/sign_up.rb +70 -4
  70. data/lib/better_auth/routes/social.rb +132 -7
  71. data/lib/better_auth/routes/user.rb +228 -20
  72. data/lib/better_auth/routes/validation.rb +50 -0
  73. data/lib/better_auth/secret_config.rb +115 -0
  74. data/lib/better_auth/session.rb +13 -2
  75. data/lib/better_auth/url_helpers.rb +206 -0
  76. data/lib/better_auth/version.rb +1 -1
  77. data/lib/better_auth.rb +12 -0
  78. metadata +23 -1
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module Plugins
5
+ module_function
6
+
7
+ def dub(options = {})
8
+ config = normalize_hash(options)
9
+ oauth_plugin = dub_oauth_plugin(config[:oauth])
10
+ endpoints = {dub_link: dub_link_endpoint(oauth_plugin)}
11
+ endpoints[:dub_o_auth2_callback] = oauth_plugin.endpoints.fetch(:o_auth2_callback) if oauth_plugin
12
+
13
+ Plugin.new(
14
+ id: "dub",
15
+ endpoints: endpoints,
16
+ init: ->(_context) {
17
+ {
18
+ options: {
19
+ database_hooks: {
20
+ user: {
21
+ create: {
22
+ after: ->(user, ctx) { dub_track_lead(config, user, ctx) }
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
28
+ },
29
+ options: config
30
+ )
31
+ end
32
+
33
+ def dub_link_endpoint(oauth_plugin)
34
+ Endpoint.new(
35
+ path: "/dub/link",
36
+ method: "POST",
37
+ metadata: {
38
+ openapi: {
39
+ operationId: "dubLink",
40
+ description: "Link a Dub OAuth account",
41
+ responses: {
42
+ "200" => OpenAPI.json_response(
43
+ "Authorization URL generated successfully for linking a Dub account",
44
+ OpenAPI.object_schema(
45
+ {
46
+ url: {type: "string"},
47
+ redirect: {type: "boolean"}
48
+ },
49
+ required: ["url", "redirect"]
50
+ )
51
+ )
52
+ }
53
+ }
54
+ }
55
+ ) do |ctx|
56
+ unless oauth_plugin
57
+ raise APIError.new("NOT_FOUND", message: "Dub OAuth is not configured")
58
+ end
59
+
60
+ body = normalize_hash(ctx.body)
61
+ callback_url = body[:callback_url] || body[:callbackURL]
62
+ if callback_url.to_s.empty?
63
+ raise APIError.new("BAD_REQUEST", message: BASE_ERROR_CODES["VALIDATION_ERROR"])
64
+ end
65
+ Routes.validate_auth_callback_url!(ctx.context, callback_url, "callbackURL")
66
+
67
+ ctx.body = body.merge(provider_id: "dub", callback_url: callback_url)
68
+ oauth_plugin.endpoints.fetch(:o_auth2_link_account).call(ctx)
69
+ end
70
+ end
71
+
72
+ def dub_oauth_plugin(oauth_options)
73
+ oauth = normalize_hash(oauth_options || {})
74
+ return nil if oauth.empty?
75
+
76
+ generic_oauth(
77
+ config: [
78
+ {
79
+ provider_id: "dub",
80
+ authorization_url: "https://app.dub.co/oauth/authorize",
81
+ token_url: "https://api.dub.co/oauth/token",
82
+ client_id: oauth[:client_id],
83
+ client_secret: oauth[:client_secret],
84
+ pkce: oauth.key?(:pkce) ? oauth[:pkce] : true
85
+ }
86
+ ]
87
+ )
88
+ end
89
+
90
+ def dub_track_lead(config, user, ctx)
91
+ return unless ctx
92
+
93
+ dub_id = ctx.get_cookie("dub_id")
94
+ return if dub_id.to_s.empty?
95
+ return if config[:disable_lead_tracking]
96
+
97
+ custom = config[:custom_lead_track]
98
+ if custom.respond_to?(:call)
99
+ custom.call(user, ctx)
100
+ else
101
+ dub_default_lead_track(config, user, dub_id, ctx)
102
+ end
103
+
104
+ ctx.set_cookie("dub_id", "", expires: Time.at(0), max_age: 0)
105
+ end
106
+
107
+ def dub_default_lead_track(config, user, dub_id, ctx)
108
+ track = config[:dub_client]&.track
109
+ return unless track&.respond_to?(:lead)
110
+
111
+ dub_invoke_lead(
112
+ track,
113
+ click_id: dub_id,
114
+ event_name: config[:lead_event_name] || "Sign Up",
115
+ customer_external_id: fetch_value(user, "id"),
116
+ customer_name: fetch_value(user, "name"),
117
+ customer_email: fetch_value(user, "email"),
118
+ customer_avatar: fetch_value(user, "image")
119
+ )
120
+ rescue => error
121
+ dub_log_error(ctx, error)
122
+ end
123
+
124
+ def dub_log_error(ctx, error)
125
+ logger = ctx.context.logger
126
+ if logger.respond_to?(:error)
127
+ logger.error(error)
128
+ elsif logger.respond_to?(:call)
129
+ logger.call(:error, error)
130
+ end
131
+ end
132
+
133
+ def dub_invoke_lead(track, payload)
134
+ if track.method(:lead).parameters.any? { |type, name| [:key, :keyreq].include?(type) && name == :request }
135
+ track.lead(request: dub_lead_request_body(payload))
136
+ else
137
+ track.lead(payload)
138
+ end
139
+ end
140
+
141
+ def dub_lead_request_body(payload)
142
+ klass = defined?(::OpenApiSDK::Models::Operations::TrackLeadRequestBody) && ::OpenApiSDK::Models::Operations::TrackLeadRequestBody
143
+ return payload unless klass
144
+
145
+ klass.new(**payload)
146
+ end
147
+ end
148
+ end
@@ -77,7 +77,28 @@ module BetterAuth
77
77
  end
78
78
 
79
79
  def send_verification_otp_endpoint(config)
80
- Endpoint.new(path: "/email-otp/send-verification-otp", method: "POST") do |ctx|
80
+ Endpoint.new(
81
+ path: "/email-otp/send-verification-otp",
82
+ method: "POST",
83
+ metadata: {
84
+ openapi: {
85
+ operationId: "sendVerificationOTP",
86
+ description: "Send an email verification OTP",
87
+ requestBody: OpenAPI.json_request_body(
88
+ OpenAPI.object_schema(
89
+ {
90
+ email: {type: "string"},
91
+ type: {type: "string", enum: ["email-verification", "sign-in", "forget-password"]}
92
+ },
93
+ required: ["email", "type"]
94
+ )
95
+ ),
96
+ responses: {
97
+ "200" => OpenAPI.json_response("OTP sent", OpenAPI.success_response_schema)
98
+ }
99
+ }
100
+ }
101
+ ) do |ctx|
81
102
  body = normalize_hash(ctx.body)
82
103
  email = body[:email].to_s.downcase
83
104
  type = body[:type].to_s
@@ -111,7 +132,30 @@ module BetterAuth
111
132
  end
112
133
 
113
134
  def get_verification_otp_endpoint(config)
114
- Endpoint.new(path: "/email-otp/get-verification-otp", method: "GET") do |ctx|
135
+ Endpoint.new(
136
+ path: "/email-otp/get-verification-otp",
137
+ method: "GET",
138
+ metadata: {
139
+ openapi: {
140
+ operationId: "getVerificationOTP",
141
+ description: "Get a stored verification OTP when storage allows plaintext access",
142
+ parameters: [
143
+ {name: "email", in: "query", required: true, schema: {type: "string"}},
144
+ {name: "type", in: "query", required: true, schema: {type: "string"}}
145
+ ],
146
+ responses: {
147
+ "200" => OpenAPI.json_response(
148
+ "Stored OTP",
149
+ OpenAPI.object_schema(
150
+ {
151
+ otp: {type: ["string", "null"]}
152
+ }
153
+ )
154
+ )
155
+ }
156
+ }
157
+ }
158
+ ) do |ctx|
115
159
  query = normalize_hash(ctx.query)
116
160
  email = query[:email].to_s.downcase
117
161
  type = query[:type].to_s
@@ -124,7 +168,7 @@ module BetterAuth
124
168
  when "hashed"
125
169
  raise APIError.new("BAD_REQUEST", message: "OTP is hashed, cannot return the plain text OTP")
126
170
  when "encrypted"
127
- next ctx.json({otp: Crypto.symmetric_decrypt(key: ctx.context.secret, data: stored_otp)})
171
+ next ctx.json({otp: Crypto.symmetric_decrypt(key: ctx.context.secret_config, data: stored_otp)})
128
172
  end
129
173
 
130
174
  storage = config[:store_otp]
@@ -139,7 +183,29 @@ module BetterAuth
139
183
  end
140
184
 
141
185
  def check_verification_otp_endpoint(config)
142
- Endpoint.new(path: "/email-otp/check-verification-otp", method: "POST") do |ctx|
186
+ Endpoint.new(
187
+ path: "/email-otp/check-verification-otp",
188
+ method: "POST",
189
+ metadata: {
190
+ openapi: {
191
+ operationId: "checkVerificationOTP",
192
+ description: "Check an email verification OTP without consuming it",
193
+ requestBody: OpenAPI.json_request_body(
194
+ OpenAPI.object_schema(
195
+ {
196
+ email: {type: "string"},
197
+ type: {type: "string"},
198
+ otp: {type: "string"}
199
+ },
200
+ required: ["email", "type", "otp"]
201
+ )
202
+ ),
203
+ responses: {
204
+ "200" => OpenAPI.json_response("OTP is valid", OpenAPI.success_response_schema)
205
+ }
206
+ }
207
+ }
208
+ ) do |ctx|
143
209
  body = normalize_hash(ctx.body)
144
210
  email = body[:email].to_s.downcase
145
211
  type = body[:type].to_s
@@ -154,7 +220,38 @@ module BetterAuth
154
220
  end
155
221
 
156
222
  def verify_email_otp_endpoint(config)
157
- Endpoint.new(path: "/email-otp/verify-email", method: "POST") do |ctx|
223
+ Endpoint.new(
224
+ path: "/email-otp/verify-email",
225
+ method: "POST",
226
+ metadata: {
227
+ openapi: {
228
+ operationId: "verifyEmailOTP",
229
+ description: "Verify an email address with an OTP",
230
+ requestBody: OpenAPI.json_request_body(
231
+ OpenAPI.object_schema(
232
+ {
233
+ email: {type: "string"},
234
+ otp: {type: "string"}
235
+ },
236
+ required: ["email", "otp"]
237
+ )
238
+ ),
239
+ responses: {
240
+ "200" => OpenAPI.json_response(
241
+ "Email verified",
242
+ OpenAPI.object_schema(
243
+ {
244
+ status: {type: "boolean"},
245
+ token: {type: ["string", "null"]},
246
+ user: {type: "object", "$ref": "#/components/schemas/User"}
247
+ },
248
+ required: ["status", "user"]
249
+ )
250
+ )
251
+ }
252
+ }
253
+ }
254
+ ) do |ctx|
158
255
  body = normalize_hash(ctx.body)
159
256
  email = body[:email].to_s.downcase
160
257
  otp = body[:otp].to_s
@@ -183,7 +280,37 @@ module BetterAuth
183
280
  end
184
281
 
185
282
  def sign_in_email_otp_endpoint(config)
186
- Endpoint.new(path: "/sign-in/email-otp", method: "POST") do |ctx|
283
+ Endpoint.new(
284
+ path: "/sign-in/email-otp",
285
+ method: "POST",
286
+ metadata: {
287
+ openapi: {
288
+ operationId: "signInEmailOTP",
289
+ description: "Sign in with an email OTP",
290
+ requestBody: OpenAPI.json_request_body(
291
+ OpenAPI.object_schema(
292
+ {
293
+ email: {type: "string"},
294
+ otp: {type: "string"}
295
+ },
296
+ required: ["email", "otp"]
297
+ )
298
+ ),
299
+ responses: {
300
+ "200" => OpenAPI.json_response(
301
+ "Signed in",
302
+ OpenAPI.object_schema(
303
+ {
304
+ token: {type: "string"},
305
+ user: {type: "object", "$ref": "#/components/schemas/User"}
306
+ },
307
+ required: ["token", "user"]
308
+ )
309
+ )
310
+ }
311
+ }
312
+ }
313
+ ) do |ctx|
187
314
  body = normalize_hash(ctx.body)
188
315
  email = body[:email].to_s.downcase
189
316
  otp = body[:otp].to_s
@@ -193,9 +320,9 @@ module BetterAuth
193
320
  user = if found
194
321
  found[:user]
195
322
  else
196
- raise APIError.new("BAD_REQUEST", message: BASE_ERROR_CODES["USER_NOT_FOUND"]) if config[:disable_sign_up]
323
+ raise APIError.new("BAD_REQUEST", message: EMAIL_OTP_ERROR_CODES["INVALID_OTP"]) if config[:disable_sign_up]
197
324
 
198
- ctx.context.internal_adapter.create_user(email_otp_sign_up_user_data(body, email))
325
+ ctx.context.internal_adapter.create_user(email_otp_sign_up_user_data(ctx, body, email), context: ctx)
199
326
  end
200
327
 
201
328
  unless user["emailVerified"]
@@ -209,7 +336,28 @@ module BetterAuth
209
336
  end
210
337
 
211
338
  def request_email_change_email_otp_endpoint(config)
212
- Endpoint.new(path: "/email-otp/request-email-change", method: "POST") do |ctx|
339
+ Endpoint.new(
340
+ path: "/email-otp/request-email-change",
341
+ method: "POST",
342
+ metadata: {
343
+ openapi: {
344
+ operationId: "requestEmailChangeOTP",
345
+ description: "Request an OTP to change the current user's email",
346
+ requestBody: OpenAPI.json_request_body(
347
+ OpenAPI.object_schema(
348
+ {
349
+ newEmail: {type: "string"},
350
+ otp: {type: ["string", "null"]}
351
+ },
352
+ required: ["newEmail"]
353
+ )
354
+ ),
355
+ responses: {
356
+ "200" => OpenAPI.json_response("Change email OTP sent", OpenAPI.success_response_schema)
357
+ }
358
+ }
359
+ }
360
+ ) do |ctx|
213
361
  email_otp_change_email_enabled!(config)
214
362
  session = Routes.current_session(ctx)
215
363
  body = normalize_hash(ctx.body)
@@ -235,7 +383,28 @@ module BetterAuth
235
383
  end
236
384
 
237
385
  def change_email_email_otp_endpoint(config)
238
- Endpoint.new(path: "/email-otp/change-email", method: "POST") do |ctx|
386
+ Endpoint.new(
387
+ path: "/email-otp/change-email",
388
+ method: "POST",
389
+ metadata: {
390
+ openapi: {
391
+ operationId: "changeEmailWithEmailOTP",
392
+ description: "Change the current user's email with an OTP",
393
+ requestBody: OpenAPI.json_request_body(
394
+ OpenAPI.object_schema(
395
+ {
396
+ newEmail: {type: "string"},
397
+ otp: {type: "string"}
398
+ },
399
+ required: ["newEmail", "otp"]
400
+ )
401
+ ),
402
+ responses: {
403
+ "200" => OpenAPI.json_response("Email changed", OpenAPI.success_response_schema)
404
+ }
405
+ }
406
+ }
407
+ ) do |ctx|
239
408
  email_otp_change_email_enabled!(config)
240
409
  session = Routes.current_session(ctx)
241
410
  body = normalize_hash(ctx.body)
@@ -259,19 +428,81 @@ module BetterAuth
259
428
  end
260
429
 
261
430
  def request_password_reset_email_otp_endpoint(config)
262
- Endpoint.new(path: "/email-otp/request-password-reset", method: "POST") do |ctx|
431
+ Endpoint.new(
432
+ path: "/email-otp/request-password-reset",
433
+ method: "POST",
434
+ metadata: {
435
+ openapi: {
436
+ operationId: "requestPasswordResetEmailOTP",
437
+ description: "Request a password reset OTP by email",
438
+ requestBody: OpenAPI.json_request_body(
439
+ OpenAPI.object_schema(
440
+ {
441
+ email: {type: "string"}
442
+ },
443
+ required: ["email"]
444
+ )
445
+ ),
446
+ responses: {
447
+ "200" => OpenAPI.json_response("Password reset OTP requested", OpenAPI.status_response_schema)
448
+ }
449
+ }
450
+ }
451
+ ) do |ctx|
263
452
  email_otp_password_reset_request(ctx, config)
264
453
  end
265
454
  end
266
455
 
267
456
  def forget_password_email_otp_endpoint(config)
268
- Endpoint.new(path: "/forget-password/email-otp", method: "POST") do |ctx|
457
+ Endpoint.new(
458
+ path: "/forget-password/email-otp",
459
+ method: "POST",
460
+ metadata: {
461
+ openapi: {
462
+ operationId: "forgetPasswordEmailOTP",
463
+ description: "Request a password reset OTP by email",
464
+ requestBody: OpenAPI.json_request_body(
465
+ OpenAPI.object_schema(
466
+ {
467
+ email: {type: "string"}
468
+ },
469
+ required: ["email"]
470
+ )
471
+ ),
472
+ responses: {
473
+ "200" => OpenAPI.json_response("Password reset OTP requested", OpenAPI.status_response_schema)
474
+ }
475
+ }
476
+ }
477
+ ) do |ctx|
269
478
  email_otp_password_reset_request(ctx, config)
270
479
  end
271
480
  end
272
481
 
273
482
  def reset_password_email_otp_endpoint(config)
274
- Endpoint.new(path: "/email-otp/reset-password", method: "POST") do |ctx|
483
+ Endpoint.new(
484
+ path: "/email-otp/reset-password",
485
+ method: "POST",
486
+ metadata: {
487
+ openapi: {
488
+ operationId: "resetPasswordEmailOTP",
489
+ description: "Reset a password with an email OTP",
490
+ requestBody: OpenAPI.json_request_body(
491
+ OpenAPI.object_schema(
492
+ {
493
+ email: {type: "string"},
494
+ otp: {type: "string"},
495
+ password: {type: "string"}
496
+ },
497
+ required: ["email", "otp", "password"]
498
+ )
499
+ ),
500
+ responses: {
501
+ "200" => OpenAPI.json_response("Password reset", OpenAPI.status_response_schema)
502
+ }
503
+ }
504
+ }
505
+ ) do |ctx|
275
506
  body = normalize_hash(ctx.body)
276
507
  email = body[:email].to_s.downcase
277
508
  otp = body[:otp].to_s
@@ -419,10 +650,21 @@ module BetterAuth
419
650
  Array.new(config[:otp_length].to_i) { SecureRandom.random_number(10).to_s }.join
420
651
  end
421
652
 
422
- def email_otp_sign_up_user_data(body, email)
653
+ def email_otp_sign_up_user_data(ctx, body, email)
423
654
  reserved = %i[email otp name image callback_url callbackURL callbackUrl]
424
- additional = body.reject { |key, _value| reserved.include?(key.to_sym) }
425
- additional = additional.each_with_object({}) { |(key, value), result| result[Schema.storage_key(key)] = value }
655
+ user_fields = Schema.auth_tables(ctx.context.options).fetch("user").fetch(:fields)
656
+ core_fields = %w[id name email emailVerified image createdAt updatedAt]
657
+ additional = body.each_with_object({}) do |(key, value), result|
658
+ next if reserved.include?(key.to_sym)
659
+
660
+ field = Schema.storage_key(key)
661
+ attributes = user_fields[field]
662
+ next unless attributes
663
+ next if core_fields.include?(field)
664
+ next if attributes[:input] == false
665
+
666
+ result[field] = value
667
+ end
426
668
  additional.merge(
427
669
  "email" => email,
428
670
  "emailVerified" => true,
@@ -434,7 +676,7 @@ module BetterAuth
434
676
  def email_otp_stored_value(ctx, config, otp)
435
677
  storage = config[:store_otp]
436
678
  return Crypto.sha256(otp, encoding: :base64url) if storage.to_s == "hashed"
437
- return Crypto.symmetric_encrypt(key: ctx.context.secret, data: otp) if storage.to_s == "encrypted"
679
+ return Crypto.symmetric_encrypt(key: ctx.context.secret_config, data: otp) if storage.to_s == "encrypted"
438
680
 
439
681
  if storage.is_a?(Hash)
440
682
  return storage[:hash].call(otp) if storage[:hash].respond_to?(:call)
@@ -449,7 +691,7 @@ module BetterAuth
449
691
  actual, expected = if storage.to_s == "hashed"
450
692
  [Crypto.sha256(otp, encoding: :base64url), stored_otp]
451
693
  elsif storage.to_s == "encrypted"
452
- [Crypto.symmetric_decrypt(key: ctx.context.secret, data: stored_otp), otp]
694
+ [Crypto.symmetric_decrypt(key: ctx.context.secret_config, data: stored_otp), otp]
453
695
  elsif storage.is_a?(Hash) && storage[:hash].respond_to?(:call)
454
696
  [storage[:hash].call(otp), stored_otp]
455
697
  elsif storage.is_a?(Hash) && storage[:decrypt].respond_to?(:call)
@@ -466,7 +708,7 @@ module BetterAuth
466
708
  def email_otp_plain_value(ctx, config, stored_otp)
467
709
  storage = config[:store_otp]
468
710
  return stored_otp if storage.to_s == "plain" || storage.nil?
469
- return Crypto.symmetric_decrypt(key: ctx.context.secret, data: stored_otp) if storage.to_s == "encrypted"
711
+ return Crypto.symmetric_decrypt(key: ctx.context.secret_config, data: stored_otp) if storage.to_s == "encrypted"
470
712
  return storage[:decrypt].call(stored_otp) if storage.is_a?(Hash) && storage[:decrypt].respond_to?(:call)
471
713
 
472
714
  nil
@@ -29,7 +29,23 @@ module BetterAuth
29
29
  end
30
30
 
31
31
  def expo_authorization_proxy_endpoint
32
- Endpoint.new(path: "/expo-authorization-proxy", method: "GET") do |ctx|
32
+ Endpoint.new(
33
+ path: "/expo-authorization-proxy",
34
+ method: "GET",
35
+ metadata: {
36
+ openapi: {
37
+ operationId: "expoAuthorizationProxy",
38
+ description: "Proxy an Expo authorization redirect",
39
+ parameters: [
40
+ {in: "query", name: "authorizationURL", required: true, schema: {type: "string", format: "uri"}},
41
+ {in: "query", name: "oauthState", required: false, schema: {type: "string"}}
42
+ ],
43
+ responses: {
44
+ "302" => {description: "Redirects to the authorization URL"}
45
+ }
46
+ }
47
+ }
48
+ ) do |ctx|
33
49
  authorization_url = ctx.query[:authorizationURL] || ctx.query["authorizationURL"] || ctx.query[:authorization_url] || ctx.query["authorization_url"]
34
50
  oauth_state = ctx.query[:oauthState] || ctx.query["oauthState"] || ctx.query[:oauth_state] || ctx.query["oauth_state"]
35
51
  raise APIError.new("BAD_REQUEST", message: "Unexpected error") if authorization_url.to_s.empty?