better_auth 0.2.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +5 -3
- data/lib/better_auth/adapters/internal_adapter.rb +173 -20
- data/lib/better_auth/adapters/memory.rb +61 -12
- data/lib/better_auth/adapters/mongodb.rb +5 -365
- data/lib/better_auth/adapters/sql.rb +44 -3
- data/lib/better_auth/api.rb +7 -2
- data/lib/better_auth/async.rb +70 -0
- data/lib/better_auth/context.rb +2 -1
- data/lib/better_auth/database_hooks.rb +3 -3
- data/lib/better_auth/deprecate.rb +28 -0
- data/lib/better_auth/endpoint.rb +5 -2
- data/lib/better_auth/host.rb +166 -0
- data/lib/better_auth/instrumentation.rb +74 -0
- data/lib/better_auth/logger.rb +31 -0
- data/lib/better_auth/middleware/origin_check.rb +2 -2
- data/lib/better_auth/oauth2.rb +94 -0
- data/lib/better_auth/plugin.rb +14 -1
- data/lib/better_auth/plugins/email_otp.rb +16 -5
- data/lib/better_auth/plugins/generic_oauth.rb +14 -28
- data/lib/better_auth/plugins/oauth_protocol.rb +553 -64
- data/lib/better_auth/plugins/organization/schema.rb +6 -0
- data/lib/better_auth/plugins/organization.rb +56 -20
- data/lib/better_auth/plugins/two_factor.rb +53 -18
- data/lib/better_auth/rate_limiter.rb +37 -2
- data/lib/better_auth/request_state.rb +44 -0
- data/lib/better_auth/router.rb +14 -1
- data/lib/better_auth/routes/account.rb +16 -4
- data/lib/better_auth/routes/email_verification.rb +5 -2
- data/lib/better_auth/routes/password.rb +21 -1
- data/lib/better_auth/routes/session.rb +27 -4
- data/lib/better_auth/routes/sign_in.rb +3 -1
- data/lib/better_auth/routes/sign_up.rb +60 -1
- data/lib/better_auth/routes/social.rb +231 -22
- data/lib/better_auth/routes/user.rb +23 -5
- data/lib/better_auth/schema/sql.rb +11 -0
- data/lib/better_auth/schema.rb +16 -0
- data/lib/better_auth/session.rb +12 -1
- data/lib/better_auth/social_providers/apple.rb +44 -8
- data/lib/better_auth/social_providers/atlassian.rb +32 -0
- data/lib/better_auth/social_providers/base.rb +262 -4
- data/lib/better_auth/social_providers/cognito.rb +32 -0
- data/lib/better_auth/social_providers/discord.rb +27 -5
- data/lib/better_auth/social_providers/dropbox.rb +33 -0
- data/lib/better_auth/social_providers/facebook.rb +35 -0
- data/lib/better_auth/social_providers/figma.rb +31 -0
- data/lib/better_auth/social_providers/github.rb +21 -6
- data/lib/better_auth/social_providers/gitlab.rb +16 -3
- data/lib/better_auth/social_providers/google.rb +38 -13
- data/lib/better_auth/social_providers/huggingface.rb +31 -0
- data/lib/better_auth/social_providers/kakao.rb +32 -0
- data/lib/better_auth/social_providers/kick.rb +32 -0
- data/lib/better_auth/social_providers/line.rb +33 -0
- data/lib/better_auth/social_providers/linear.rb +44 -0
- data/lib/better_auth/social_providers/linkedin.rb +30 -0
- data/lib/better_auth/social_providers/microsoft_entra_id.rb +79 -7
- data/lib/better_auth/social_providers/naver.rb +31 -0
- data/lib/better_auth/social_providers/notion.rb +33 -0
- data/lib/better_auth/social_providers/paybin.rb +31 -0
- data/lib/better_auth/social_providers/paypal.rb +36 -0
- data/lib/better_auth/social_providers/polar.rb +31 -0
- data/lib/better_auth/social_providers/railway.rb +49 -0
- data/lib/better_auth/social_providers/reddit.rb +32 -0
- data/lib/better_auth/social_providers/roblox.rb +31 -0
- data/lib/better_auth/social_providers/salesforce.rb +38 -0
- data/lib/better_auth/social_providers/slack.rb +30 -0
- data/lib/better_auth/social_providers/spotify.rb +31 -0
- data/lib/better_auth/social_providers/tiktok.rb +35 -0
- data/lib/better_auth/social_providers/twitch.rb +39 -0
- data/lib/better_auth/social_providers/twitter.rb +32 -0
- data/lib/better_auth/social_providers/vercel.rb +47 -0
- data/lib/better_auth/social_providers/vk.rb +34 -0
- data/lib/better_auth/social_providers/wechat.rb +104 -0
- data/lib/better_auth/social_providers/zoom.rb +31 -0
- data/lib/better_auth/social_providers.rb +29 -0
- data/lib/better_auth/url_helpers.rb +195 -0
- data/lib/better_auth/version.rb +1 -1
- data/lib/better_auth.rb +8 -1
- metadata +38 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 38179f5800613263ca30525a3d878b91c2a5f9057f104b1f24cecbf72a0cc030
|
|
4
|
+
data.tar.gz: 9883d339ce2f1ab8f5a618da3708f4ab5bdde69a09fe98b48889b6069351c7bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b0bdd97ab9d677df601b0eed5d387a09543743aee41d0243f7ed3981935c3391f3ae10dfc110fc41312a5a4b188f29581563f2c99154a8808ed3158cb47663ad
|
|
7
|
+
data.tar.gz: 6b9b76c6969e601e01506a2cd91a28abf13eeccf0446023fad7c58e8c95476f6110b7ac6f3979fc18705687589cff74748f839c685a8ccb791392d0d2e729c15
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-04-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added upstream-parity helpers for async execution, host resolution, instrumentation, request state, URL handling, OAuth2, deprecation warnings, and expanded route behavior.
|
|
15
|
+
- Added two-factor, OAuth protocol, social route, organization, admin, adapter, schema, and session parity coverage.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Aligned core auth, email OTP, generic OAuth, organization, two-factor, OAuth protocol, adapter, router, rate-limiter, logger, and middleware behavior more closely with upstream Better Auth.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Fixed upstream parity gaps in organization handling, generic OAuth user info, email OTP sign-up, database schema behavior, and route/session edge cases.
|
|
24
|
+
|
|
25
|
+
## [0.3.0] - 2026-04-29
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Added upstream-parity social provider support, including provider-specific authorization, token, profile, refresh, and revocation behavior for the expanded provider set.
|
|
30
|
+
- Added OAuth/OIDC protocol hardening for authorization, callback, discovery, metadata, token, and userinfo flows.
|
|
31
|
+
- Added upstream v1.6.9 parity coverage for schema generation, adapter behavior, plugin hooks, session handling, and account/user route edge cases.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Extracted MongoDB adapter support behind the external `better_auth-mongo-adapter` shim while preserving compatibility for existing adapter configuration.
|
|
36
|
+
- Updated auth routes, router behavior, rate limiting, password and email-verification flows, and schema metadata to match upstream semantics more closely.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- Fixed social provider edge cases, magic-link expiration behavior, adapter value coercion, and callback/session handling across Rack integrations.
|
|
41
|
+
|
|
10
42
|
## [0.1.1] - 2026-03-22
|
|
11
43
|
|
|
12
44
|
### Fixed
|
data/README.md
CHANGED
|
@@ -90,7 +90,7 @@ Custom Better Auth-style password callbacks are still supported through `email_a
|
|
|
90
90
|
|
|
91
91
|
### Database Adapters
|
|
92
92
|
|
|
93
|
-
The core gem ships framework-agnostic adapters for memory, PostgreSQL, MySQL, SQLite,
|
|
93
|
+
The core gem ships framework-agnostic adapters for memory, PostgreSQL, MySQL, SQLite, and MSSQL. Driver gems are loaded only when their adapter is instantiated. MongoDB support lives in the external `better_auth-mongo-adapter` package so apps that do not use MongoDB do not install the Mongo driver.
|
|
94
94
|
|
|
95
95
|
```ruby
|
|
96
96
|
auth = BetterAuth.auth(
|
|
@@ -100,6 +100,8 @@ auth = BetterAuth.auth(
|
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
```ruby
|
|
103
|
+
require "better_auth/mongo_adapter"
|
|
104
|
+
|
|
103
105
|
auth = BetterAuth.auth(
|
|
104
106
|
secret: ENV.fetch("BETTER_AUTH_SECRET"),
|
|
105
107
|
database: BetterAuth::Adapters::MongoDB.new(
|
|
@@ -155,7 +157,7 @@ export const authClient = createAuthClient({
|
|
|
155
157
|
Add to your Gemfile:
|
|
156
158
|
|
|
157
159
|
```ruby
|
|
158
|
-
gem
|
|
160
|
+
gem "better_auth-rails"
|
|
159
161
|
```
|
|
160
162
|
|
|
161
163
|
Then in your ApplicationController:
|
|
@@ -170,7 +172,7 @@ Now you have access to `current_user` and authentication methods:
|
|
|
170
172
|
|
|
171
173
|
```ruby
|
|
172
174
|
class PostsController < ApplicationController
|
|
173
|
-
before_action :
|
|
175
|
+
before_action :require_authentication
|
|
174
176
|
|
|
175
177
|
def index
|
|
176
178
|
@posts = current_user.posts
|
|
@@ -59,9 +59,12 @@ module BetterAuth
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def delete_user(user_id)
|
|
62
|
-
|
|
62
|
+
deleted = hooks.delete([{field: "id", value: user_id}], "user")
|
|
63
|
+
return false if deleted == false
|
|
64
|
+
|
|
63
65
|
hooks.delete_many([{field: "userId", value: user_id}], "account")
|
|
64
|
-
|
|
66
|
+
delete_sessions(user_id) if !secondary_storage || options.session[:store_session_in_database]
|
|
67
|
+
deleted
|
|
65
68
|
end
|
|
66
69
|
|
|
67
70
|
def create_session(user_id, dont_remember_me = false, override = nil, override_all = false, context = nil)
|
|
@@ -74,28 +77,31 @@ module BetterAuth
|
|
|
74
77
|
"userId" => user_id,
|
|
75
78
|
"token" => token
|
|
76
79
|
}.merge(timestamps)
|
|
80
|
+
base["id"] = generated_id if secondary_storage
|
|
77
81
|
data = override_all ? base.merge(override) : override.merge(base)
|
|
78
82
|
|
|
79
83
|
custom = secondary_storage && lambda do |session_data|
|
|
80
84
|
actual_session = apply_schema_create("session", session_data)
|
|
81
85
|
store_session(actual_session)
|
|
86
|
+
adapter.create(model: "session", data: actual_session, force_allow_id: true) if options.session[:store_session_in_database]
|
|
82
87
|
actual_session
|
|
83
88
|
end
|
|
84
|
-
|
|
85
|
-
created = hooks.create(data, "session", custom: custom, context: context)
|
|
86
|
-
adapter.create(model: "session", data: data, force_allow_id: true) if secondary_storage && execute_main
|
|
87
|
-
created
|
|
89
|
+
hooks.create(data, "session", custom: custom, context: context)
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
def find_session(token)
|
|
91
93
|
if secondary_storage
|
|
92
94
|
data = parse_storage(secondary_storage.get(token))
|
|
93
|
-
|
|
95
|
+
unless data
|
|
96
|
+
return nil unless options.session[:store_session_in_database] && !options.session[:preserve_session_in_database]
|
|
97
|
+
end
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
if data
|
|
100
|
+
return {
|
|
101
|
+
session: normalize_session_dates(data["session"]),
|
|
102
|
+
user: normalize_user_dates(data["user"])
|
|
103
|
+
}
|
|
104
|
+
end
|
|
99
105
|
end
|
|
100
106
|
|
|
101
107
|
found = find_session_with_user(token)
|
|
@@ -113,7 +119,9 @@ module BetterAuth
|
|
|
113
119
|
data = stringify_keys(session)
|
|
114
120
|
if secondary_storage
|
|
115
121
|
return hooks.update(data, [{field: "token", value: token}], "session", custom: lambda { |actual_data|
|
|
116
|
-
update_stored_session(token, actual_data)
|
|
122
|
+
stored = update_stored_session(token, actual_data)
|
|
123
|
+
db = adapter.update(model: "session", where: [{field: "token", value: token}], update: actual_data) if options.session[:store_session_in_database]
|
|
124
|
+
db || stored
|
|
117
125
|
})
|
|
118
126
|
end
|
|
119
127
|
|
|
@@ -226,30 +234,93 @@ module BetterAuth
|
|
|
226
234
|
end
|
|
227
235
|
|
|
228
236
|
def create_verification_value(data)
|
|
229
|
-
|
|
237
|
+
payload = timestamps.merge(stringify_keys(data))
|
|
238
|
+
stored_identifier = processed_verification_identifier(payload.fetch("identifier"))
|
|
239
|
+
payload["identifier"] = stored_identifier
|
|
240
|
+
|
|
241
|
+
custom = secondary_storage && lambda do |verification_data|
|
|
242
|
+
actual = apply_schema_create("verification", verification_data)
|
|
243
|
+
actual["id"] ||= generated_id
|
|
244
|
+
store_verification(actual)
|
|
245
|
+
adapter.create(model: "verification", data: actual, force_allow_id: true) if verification_store_in_database?
|
|
246
|
+
actual
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
hooks.create(payload, "verification", custom: custom)
|
|
230
250
|
end
|
|
231
251
|
|
|
232
252
|
def find_verification_value(identifier)
|
|
253
|
+
stored_identifier = processed_verification_identifier(identifier)
|
|
254
|
+
storage_option = verification_storage_option(identifier)
|
|
255
|
+
if secondary_storage
|
|
256
|
+
cached = read_verification(stored_identifier)
|
|
257
|
+
cached ||= read_verification(identifier) if storage_option && storage_option.to_s != "plain"
|
|
258
|
+
return cached if cached
|
|
259
|
+
return nil unless verification_store_in_database?
|
|
260
|
+
end
|
|
261
|
+
|
|
233
262
|
values = adapter.find_many(
|
|
234
263
|
model: "verification",
|
|
235
|
-
where: [{field: "identifier", value:
|
|
264
|
+
where: [{field: "identifier", value: stored_identifier}],
|
|
236
265
|
sort_by: {field: "createdAt", direction: "desc"},
|
|
237
266
|
limit: 1
|
|
238
267
|
)
|
|
268
|
+
if values.empty? && storage_option && storage_option.to_s != "plain"
|
|
269
|
+
values = adapter.find_many(
|
|
270
|
+
model: "verification",
|
|
271
|
+
where: [{field: "identifier", value: identifier}],
|
|
272
|
+
sort_by: {field: "createdAt", direction: "desc"},
|
|
273
|
+
limit: 1
|
|
274
|
+
)
|
|
275
|
+
end
|
|
239
276
|
hooks.delete_many([{field: "expiresAt", value: Time.now, operator: "lt"}], "verification") unless options.verification[:disable_cleanup]
|
|
240
277
|
values.first
|
|
241
278
|
end
|
|
242
279
|
|
|
243
280
|
def delete_verification_value(id)
|
|
281
|
+
if secondary_storage
|
|
282
|
+
stored_identifier = secondary_storage.get(verification_id_key(id))
|
|
283
|
+
if stored_identifier
|
|
284
|
+
secondary_storage.delete(verification_key(stored_identifier))
|
|
285
|
+
secondary_storage.delete(verification_id_key(id))
|
|
286
|
+
return nil unless verification_store_in_database?
|
|
287
|
+
elsif !verification_store_in_database?
|
|
288
|
+
return nil
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
244
292
|
hooks.delete([{field: "id", value: id}], "verification")
|
|
245
293
|
end
|
|
246
294
|
|
|
247
295
|
def delete_verification_by_identifier(identifier)
|
|
248
|
-
|
|
296
|
+
stored_identifier = processed_verification_identifier(identifier)
|
|
297
|
+
if secondary_storage
|
|
298
|
+
cached = read_verification(stored_identifier)
|
|
299
|
+
secondary_storage.delete(verification_key(stored_identifier))
|
|
300
|
+
secondary_storage.delete(verification_id_key(cached["id"])) if cached && cached["id"]
|
|
301
|
+
return nil unless verification_store_in_database?
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
hooks.delete([{field: "identifier", value: stored_identifier}], "verification")
|
|
249
305
|
end
|
|
250
306
|
|
|
251
307
|
def update_verification_value(id, data)
|
|
252
|
-
|
|
308
|
+
update = stringify_keys(data)
|
|
309
|
+
if secondary_storage
|
|
310
|
+
stored_identifier = secondary_storage.get(verification_id_key(id))
|
|
311
|
+
if stored_identifier
|
|
312
|
+
cached = read_verification(stored_identifier)
|
|
313
|
+
if cached
|
|
314
|
+
updated = cached.merge(update)
|
|
315
|
+
store_verification(updated)
|
|
316
|
+
return updated unless verification_store_in_database?
|
|
317
|
+
end
|
|
318
|
+
elsif !verification_store_in_database?
|
|
319
|
+
return nil
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
hooks.update(update, [{field: "id", value: id}], "verification")
|
|
253
324
|
end
|
|
254
325
|
|
|
255
326
|
private
|
|
@@ -285,6 +356,14 @@ module BetterAuth
|
|
|
285
356
|
{"createdAt" => now, "updatedAt" => now}
|
|
286
357
|
end
|
|
287
358
|
|
|
359
|
+
def generated_id
|
|
360
|
+
generator = options.advanced.dig(:database, :generate_id)
|
|
361
|
+
return generator.call.to_s if generator.respond_to?(:call)
|
|
362
|
+
return SecureRandom.uuid if generator == "uuid"
|
|
363
|
+
|
|
364
|
+
SecureRandom.hex(16)
|
|
365
|
+
end
|
|
366
|
+
|
|
288
367
|
def stringify_keys(data)
|
|
289
368
|
data.each_with_object({}) do |(key, value), result|
|
|
290
369
|
result[Schema.storage_key(key)] = value
|
|
@@ -295,6 +374,8 @@ module BetterAuth
|
|
|
295
374
|
fields = Schema.auth_tables(options)[model]&.fetch(:fields)
|
|
296
375
|
fields ||= session_additional_fields if model == "session"
|
|
297
376
|
output = stringify_keys(data)
|
|
377
|
+
return output unless fields
|
|
378
|
+
|
|
298
379
|
fields.each do |field, attributes|
|
|
299
380
|
unless output.key?(field)
|
|
300
381
|
if attributes.key?(:default_value)
|
|
@@ -334,7 +415,8 @@ module BetterAuth
|
|
|
334
415
|
.push({"token" => session["token"], "expiresAt" => expires_ms})
|
|
335
416
|
.sort_by { |entry| entry["expiresAt"] }
|
|
336
417
|
write_active_sessions(session["userId"], entries)
|
|
337
|
-
|
|
418
|
+
ttl_seconds = ttl(expires_ms)
|
|
419
|
+
secondary_storage.set(session["token"], JSON.generate({session: session, user: user}), ttl_seconds) if ttl_seconds.positive?
|
|
338
420
|
end
|
|
339
421
|
|
|
340
422
|
def update_stored_session(token, data)
|
|
@@ -345,7 +427,12 @@ module BetterAuth
|
|
|
345
427
|
merged["expiresAt"] = normalize_time(merged["expiresAt"])
|
|
346
428
|
merged["createdAt"] = normalize_time(merged["createdAt"])
|
|
347
429
|
merged["updatedAt"] = normalize_time(merged["updatedAt"])
|
|
348
|
-
|
|
430
|
+
ttl_seconds = ttl(millis(merged["expiresAt"]))
|
|
431
|
+
if ttl_seconds.positive?
|
|
432
|
+
secondary_storage.set(token, JSON.generate({session: merged, user: parsed["user"]}), ttl_seconds)
|
|
433
|
+
else
|
|
434
|
+
secondary_storage.delete(token)
|
|
435
|
+
end
|
|
349
436
|
entries = active_session_entries(merged["userId"])
|
|
350
437
|
.reject { |entry| entry["token"] == token || entry["expiresAt"].to_i <= current_millis }
|
|
351
438
|
.push({"token" => token, "expiresAt" => millis(merged["expiresAt"])})
|
|
@@ -369,7 +456,7 @@ module BetterAuth
|
|
|
369
456
|
raw = secondary_storage.get(active_key(user_id))
|
|
370
457
|
Array(parse_storage(raw)).map do |entry|
|
|
371
458
|
entry.transform_keys(&:to_s)
|
|
372
|
-
end
|
|
459
|
+
end.uniq { |entry| entry["token"] }
|
|
373
460
|
end
|
|
374
461
|
|
|
375
462
|
def write_active_sessions(user_id, entries)
|
|
@@ -377,7 +464,12 @@ module BetterAuth
|
|
|
377
464
|
if future.empty?
|
|
378
465
|
secondary_storage.delete(active_key(user_id))
|
|
379
466
|
else
|
|
380
|
-
|
|
467
|
+
ttl_seconds = ttl(future.last["expiresAt"])
|
|
468
|
+
if ttl_seconds.positive?
|
|
469
|
+
secondary_storage.set(active_key(user_id), JSON.generate(future), ttl_seconds)
|
|
470
|
+
else
|
|
471
|
+
secondary_storage.delete(active_key(user_id))
|
|
472
|
+
end
|
|
381
473
|
end
|
|
382
474
|
end
|
|
383
475
|
|
|
@@ -415,6 +507,67 @@ module BetterAuth
|
|
|
415
507
|
)
|
|
416
508
|
end
|
|
417
509
|
|
|
510
|
+
def normalize_verification_dates(verification)
|
|
511
|
+
return nil unless verification
|
|
512
|
+
|
|
513
|
+
verification.transform_keys(&:to_s).merge(
|
|
514
|
+
"expiresAt" => normalize_time(verification["expiresAt"] || verification[:expiresAt]),
|
|
515
|
+
"createdAt" => normalize_time(verification["createdAt"] || verification[:createdAt]),
|
|
516
|
+
"updatedAt" => normalize_time(verification["updatedAt"] || verification[:updatedAt])
|
|
517
|
+
)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def store_verification(verification)
|
|
521
|
+
normalized = normalize_verification_dates(verification)
|
|
522
|
+
ttl_seconds = ttl(millis(normalized["expiresAt"]))
|
|
523
|
+
return normalized unless ttl_seconds.positive?
|
|
524
|
+
|
|
525
|
+
secondary_storage.set(verification_key(normalized["identifier"]), JSON.generate(normalized), ttl_seconds)
|
|
526
|
+
secondary_storage.set(verification_id_key(normalized["id"]), normalized["identifier"], ttl_seconds) if normalized["id"]
|
|
527
|
+
normalized
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def read_verification(identifier)
|
|
531
|
+
normalize_verification_dates(parse_storage(secondary_storage.get(verification_key(identifier))))
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def verification_key(identifier)
|
|
535
|
+
"verification:#{identifier}"
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def verification_id_key(id)
|
|
539
|
+
"verification-id:#{id}"
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def verification_store_in_database?
|
|
543
|
+
!!options.verification[:store_in_database]
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def processed_verification_identifier(identifier)
|
|
547
|
+
option = verification_storage_option(identifier)
|
|
548
|
+
return identifier.to_s if option.nil? || option.to_s == "plain"
|
|
549
|
+
return Crypto.sha256(identifier.to_s, encoding: :base64url) if option.to_s == "hashed"
|
|
550
|
+
return option[:hash].call(identifier.to_s).to_s if option.is_a?(Hash) && option[:hash].respond_to?(:call)
|
|
551
|
+
return option["hash"].call(identifier.to_s).to_s if option.is_a?(Hash) && option["hash"].respond_to?(:call)
|
|
552
|
+
|
|
553
|
+
identifier.to_s
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def verification_storage_option(identifier)
|
|
557
|
+
config = options.verification[:store_identifier]
|
|
558
|
+
return nil unless config
|
|
559
|
+
|
|
560
|
+
if config.is_a?(Hash) && (config.key?(:default) || config.key?("default"))
|
|
561
|
+
overrides = config[:overrides] || config["overrides"] || {}
|
|
562
|
+
overrides.each do |prefix, option|
|
|
563
|
+
return option if identifier.to_s.start_with?(prefix.to_s)
|
|
564
|
+
end
|
|
565
|
+
return config[:default] || config["default"]
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
config
|
|
569
|
+
end
|
|
570
|
+
|
|
418
571
|
def normalize_time(value)
|
|
419
572
|
return value if value.is_a?(Time)
|
|
420
573
|
return Time.at(value / 1000.0) if value.is_a?(Integer) && value > 10_000_000_000
|
|
@@ -156,33 +156,79 @@ module BetterAuth
|
|
|
156
156
|
value = fetch_key(clause, :value)
|
|
157
157
|
operator = (fetch_key(clause, :operator) || "eq").to_s
|
|
158
158
|
current = record[field]
|
|
159
|
+
comparable = coerce_where_value(record, field, value, operator)
|
|
159
160
|
|
|
160
161
|
case operator
|
|
161
162
|
when "in"
|
|
162
|
-
Array(
|
|
163
|
+
Array(comparable).include?(current)
|
|
163
164
|
when "not_in"
|
|
164
|
-
!Array(
|
|
165
|
+
!Array(comparable).include?(current)
|
|
165
166
|
when "contains"
|
|
166
|
-
current.to_s.include?(
|
|
167
|
+
current.to_s.include?(comparable.to_s)
|
|
167
168
|
when "starts_with"
|
|
168
|
-
current.to_s.start_with?(
|
|
169
|
+
current.to_s.start_with?(comparable.to_s)
|
|
169
170
|
when "ends_with"
|
|
170
|
-
current.to_s.end_with?(
|
|
171
|
+
current.to_s.end_with?(comparable.to_s)
|
|
171
172
|
when "ne"
|
|
172
|
-
current !=
|
|
173
|
+
current != comparable
|
|
173
174
|
when "gt"
|
|
174
|
-
!
|
|
175
|
+
!comparable.nil? && current > comparable
|
|
175
176
|
when "gte"
|
|
176
|
-
!
|
|
177
|
+
!comparable.nil? && current >= comparable
|
|
177
178
|
when "lt"
|
|
178
|
-
!
|
|
179
|
+
!comparable.nil? && current < comparable
|
|
179
180
|
when "lte"
|
|
180
|
-
!
|
|
181
|
+
!comparable.nil? && current <= comparable
|
|
181
182
|
else
|
|
182
|
-
current ==
|
|
183
|
+
current == comparable
|
|
183
184
|
end
|
|
184
185
|
end
|
|
185
186
|
|
|
187
|
+
def coerce_where_value(record, field, value, operator)
|
|
188
|
+
attributes = schema_for_record_field(record, field)
|
|
189
|
+
return value unless attributes
|
|
190
|
+
return Array(value).map { |entry| coerce_scalar_where_value(entry, attributes) } if %w[in not_in].include?(operator)
|
|
191
|
+
|
|
192
|
+
coerce_scalar_where_value(value, attributes)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def schema_for_record_field(record, field)
|
|
196
|
+
db.each_key do |model|
|
|
197
|
+
fields = Schema.auth_tables(options)[model]&.fetch(:fields, nil)
|
|
198
|
+
next unless fields&.key?(field)
|
|
199
|
+
return fields[field] if table_for(model).include?(record)
|
|
200
|
+
end
|
|
201
|
+
nil
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def coerce_scalar_where_value(value, attributes)
|
|
205
|
+
return value if value.nil?
|
|
206
|
+
|
|
207
|
+
case attributes[:type]
|
|
208
|
+
when "boolean"
|
|
209
|
+
return false if value == false || value == 0 || value.to_s.downcase == "false" || value.to_s == "0"
|
|
210
|
+
return true if value == true || value == 1 || value.to_s.downcase == "true" || value.to_s == "1"
|
|
211
|
+
when "number"
|
|
212
|
+
return coerce_number(value)
|
|
213
|
+
when "date"
|
|
214
|
+
return Time.parse(value) if value.is_a?(String)
|
|
215
|
+
when "number[]"
|
|
216
|
+
return Array(value).map { |entry| coerce_number(entry) }
|
|
217
|
+
when "string[]"
|
|
218
|
+
return Array(value).map(&:to_s)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
value
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def coerce_number(value)
|
|
225
|
+
return value unless value.is_a?(String)
|
|
226
|
+
return value.to_i if /\A-?\d+\z/.match?(value)
|
|
227
|
+
return value.to_f if /\A-?\d+\.\d+\z/.match?(value)
|
|
228
|
+
|
|
229
|
+
value
|
|
230
|
+
end
|
|
231
|
+
|
|
186
232
|
def sort_records(model, records, sort_by)
|
|
187
233
|
field = Schema.storage_key(fetch_key(sort_by, :field))
|
|
188
234
|
direction = fetch_key(sort_by, :direction).to_s
|
|
@@ -225,7 +271,10 @@ module BetterAuth
|
|
|
225
271
|
end
|
|
226
272
|
|
|
227
273
|
def fetch_key(hash, key)
|
|
228
|
-
|
|
274
|
+
[key, key.to_s, Schema.storage_key(key), Schema.storage_key(key).to_sym].each do |candidate|
|
|
275
|
+
return hash[candidate] if hash.key?(candidate)
|
|
276
|
+
end
|
|
277
|
+
nil
|
|
229
278
|
end
|
|
230
279
|
end
|
|
231
280
|
end
|