better_auth 0.8.0 → 0.11.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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +142 -95
  5. data/lib/better_auth/adapters/base.rb +64 -1
  6. data/lib/better_auth/adapters/internal_adapter.rb +197 -11
  7. data/lib/better_auth/adapters/join_support.rb +10 -0
  8. data/lib/better_auth/adapters/memory.rb +303 -54
  9. data/lib/better_auth/adapters/mssql.rb +7 -3
  10. data/lib/better_auth/adapters/sql.rb +390 -63
  11. data/lib/better_auth/api.rb +3 -24
  12. data/lib/better_auth/configuration.rb +48 -47
  13. data/lib/better_auth/context.rb +43 -67
  14. data/lib/better_auth/cookies.rb +119 -15
  15. data/lib/better_auth/core.rb +0 -2
  16. data/lib/better_auth/crypto.rb +4 -3
  17. data/lib/better_auth/doctor.rb +108 -0
  18. data/lib/better_auth/endpoint.rb +88 -5
  19. data/lib/better_auth/error.rb +3 -1
  20. data/lib/better_auth/http_client.rb +46 -0
  21. data/lib/better_auth/logger.rb +1 -1
  22. data/lib/better_auth/middleware/origin_check.rb +9 -1
  23. data/lib/better_auth/migration_plan.rb +15 -0
  24. data/lib/better_auth/oauth2.rb +1 -1
  25. data/lib/better_auth/oauth_state.rb +147 -0
  26. data/lib/better_auth/plugin_loader.rb +220 -0
  27. data/lib/better_auth/plugins/access.rb +10 -13
  28. data/lib/better_auth/plugins/admin.rb +85 -29
  29. data/lib/better_auth/plugins/anonymous.rb +2 -0
  30. data/lib/better_auth/plugins/api_key.rb +4 -7
  31. data/lib/better_auth/plugins/bearer.rb +8 -22
  32. data/lib/better_auth/plugins/captcha.rb +24 -6
  33. data/lib/better_auth/plugins/device_authorization.rb +77 -5
  34. data/lib/better_auth/plugins/dub.rb +10 -2
  35. data/lib/better_auth/plugins/email_otp.rb +30 -47
  36. data/lib/better_auth/plugins/generic_oauth.rb +162 -140
  37. data/lib/better_auth/plugins/have_i_been_pwned.rb +1 -1
  38. data/lib/better_auth/plugins/i18n.rb +215 -0
  39. data/lib/better_auth/plugins/jwt.rb +44 -19
  40. data/lib/better_auth/plugins/magic_link.rb +18 -21
  41. data/lib/better_auth/plugins/oauth_popup.rb +330 -0
  42. data/lib/better_auth/plugins/oauth_protocol.rb +233 -46
  43. data/lib/better_auth/plugins/oauth_provider.rb +4 -7
  44. data/lib/better_auth/plugins/oauth_proxy.rb +1 -1
  45. data/lib/better_auth/plugins/one_tap.rb +78 -65
  46. data/lib/better_auth/plugins/one_time_token.rb +1 -4
  47. data/lib/better_auth/plugins/open_api.rb +77 -9
  48. data/lib/better_auth/plugins/organization.rb +546 -102
  49. data/lib/better_auth/plugins/passkey.rb +4 -7
  50. data/lib/better_auth/plugins/phone_number.rb +25 -27
  51. data/lib/better_auth/plugins/scim.rb +4 -7
  52. data/lib/better_auth/plugins/siwe.rb +126 -16
  53. data/lib/better_auth/plugins/sso.rb +4 -7
  54. data/lib/better_auth/plugins/stripe.rb +4 -7
  55. data/lib/better_auth/plugins/two_factor.rb +205 -19
  56. data/lib/better_auth/plugins/username.rb +56 -13
  57. data/lib/better_auth/plugins.rb +57 -0
  58. data/lib/better_auth/rate_limiter.rb +300 -133
  59. data/lib/better_auth/request_ip.rb +123 -13
  60. data/lib/better_auth/router.rb +3 -3
  61. data/lib/better_auth/routes/account.rb +44 -8
  62. data/lib/better_auth/routes/email_verification.rb +93 -6
  63. data/lib/better_auth/routes/password.rb +6 -6
  64. data/lib/better_auth/routes/session.rb +7 -2
  65. data/lib/better_auth/routes/sign_in.rb +10 -1
  66. data/lib/better_auth/routes/sign_up.rb +45 -22
  67. data/lib/better_auth/routes/social.rb +197 -54
  68. data/lib/better_auth/routes/user.rb +17 -10
  69. data/lib/better_auth/schema/sql.rb +189 -34
  70. data/lib/better_auth/schema.rb +159 -5
  71. data/lib/better_auth/session.rb +29 -10
  72. data/lib/better_auth/session_store.rb +1 -1
  73. data/lib/better_auth/social_providers/apple.rb +6 -1
  74. data/lib/better_auth/social_providers/atlassian.rb +1 -0
  75. data/lib/better_auth/social_providers/base.rb +45 -23
  76. data/lib/better_auth/social_providers/cognito.rb +75 -5
  77. data/lib/better_auth/social_providers/facebook.rb +95 -1
  78. data/lib/better_auth/social_providers/figma.rb +2 -0
  79. data/lib/better_auth/social_providers/github.rb +2 -0
  80. data/lib/better_auth/social_providers/google.rb +12 -1
  81. data/lib/better_auth/social_providers/line.rb +35 -1
  82. data/lib/better_auth/social_providers/linear.rb +1 -0
  83. data/lib/better_auth/social_providers/microsoft_entra_id.rb +9 -0
  84. data/lib/better_auth/social_providers/naver.rb +24 -1
  85. data/lib/better_auth/social_providers/notion.rb +1 -0
  86. data/lib/better_auth/social_providers/paybin.rb +25 -1
  87. data/lib/better_auth/social_providers/paypal.rb +118 -2
  88. data/lib/better_auth/social_providers/reddit.rb +25 -4
  89. data/lib/better_auth/social_providers/salesforce.rb +1 -0
  90. data/lib/better_auth/social_providers/twitch.rb +24 -1
  91. data/lib/better_auth/social_providers/twitter.rb +36 -1
  92. data/lib/better_auth/social_providers/vk.rb +34 -1
  93. data/lib/better_auth/social_providers/wechat.rb +6 -4
  94. data/lib/better_auth/sql_migration.rb +566 -0
  95. data/lib/better_auth/version.rb +1 -1
  96. data/lib/better_auth.rb +14 -41
  97. metadata +14 -18
  98. data/lib/better_auth/plugins/mcp/authorization.rb +0 -111
  99. data/lib/better_auth/plugins/mcp/config.rb +0 -51
  100. data/lib/better_auth/plugins/mcp/consent.rb +0 -31
  101. data/lib/better_auth/plugins/mcp/legacy_aliases.rb +0 -43
  102. data/lib/better_auth/plugins/mcp/metadata.rb +0 -81
  103. data/lib/better_auth/plugins/mcp/registration.rb +0 -31
  104. data/lib/better_auth/plugins/mcp/resource_handler.rb +0 -37
  105. data/lib/better_auth/plugins/mcp/schema.rb +0 -91
  106. data/lib/better_auth/plugins/mcp/token.rb +0 -108
  107. data/lib/better_auth/plugins/mcp/userinfo.rb +0 -37
  108. data/lib/better_auth/plugins/mcp.rb +0 -215
  109. data/lib/better_auth/plugins/oidc_provider.rb +0 -710
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98014e245459f3b5a7de74fbd1e41ac5449ff223e20e5f9c8ef9500e15c55d9f
4
- data.tar.gz: 28252ead0ea2f370233bcc73bcc41568604608ff2d4d891a66abf71f49e01497
3
+ metadata.gz: 7d7cf9a11bc4e929dad460e2d1d72cf12e9a34465f73d14259293267f06e591e
4
+ data.tar.gz: 06c018ee8a3fad409045a6a6639c851fb596a854707540dc710f8651e87b0d79
5
5
  SHA512:
6
- metadata.gz: 22e4e6488cebfdc63f77d68747df4283ccf27c82381ab6bb7e372485f0c2d93cb4e37dce4748e75020f6556ff08a675ba42d47cf39a57f90c86f2d4a396c09b4
7
- data.tar.gz: a05af007c3dcd034bc035a37e825991ce76ff8af9199ae33f4a3ecf2250718ac848ae52baa84d2d60c7b5eace5d7f2750de2d40dde779b182640184b50d1a765
6
+ metadata.gz: 7a9cd8c96e722eff39babca55e87e60dffa4c6ee723bf798a3ef4f10cff7b057d7326b39271bfe4ffd07f35937ad3b5697ec273882622d6dc84597ac8fe0315a
7
+ data.tar.gz: 8c9de78cd3169537c5d9bf9c2c4ade7cf53c6e5f9dbb76d8debf58f0bdedb6f0b4bf7e4a19608801bc269988c93b358271c3aff06a53f1dd4229c07062559f7a
data/CHANGELOG.md CHANGED
@@ -5,8 +5,100 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.11.0](https://github.com/salasebas/better-auth-rb/compare/better_auth-v0.10.0...better_auth/v0.11.0) (2026-07-23)
9
+
10
+
11
+ ### ⚠ BREAKING CHANGES
12
+
13
+ * **release:** OpenAuth alias gems, the openauth executable, and the better_auth_rails alias gem and require path are removed.
14
+ * **oauth-provider:** unify OAuth/OIDC/MCP under oauth_provider gem
15
+
16
+ ### Features
17
+
18
+ * **adapters:** add atomic storage primitives ([8d715c9](https://github.com/salasebas/better-auth-rb/commit/8d715c92ba2dbd4aa4b167a3319a9ac94c629519))
19
+ * **auth:** complete API catalog parity ([234fd77](https://github.com/salasebas/better-auth-rb/commit/234fd77983866dbdcb6be8a3d1b2604a7fd0ce60))
20
+ * **auth:** improve OAuth provider parity ([3c1c3c3](https://github.com/salasebas/better-auth-rb/commit/3c1c3c3466dd4782732c988555fe80bc43a1abbd))
21
+ * **better_auth:** lazy-load in-core plugins on demand ([66c3002](https://github.com/salasebas/better-auth-rb/commit/66c300212e6f72bf41284b73ac7207a9b2886af9))
22
+ * **cli:** add config discovery, secret, and info diagnostics ([e522cf6](https://github.com/salasebas/better-auth-rb/commit/e522cf6755512b465dbf2f2589eb2335db6f1af4))
23
+ * **core:** add i18n plugin ([ab6a2f3](https://github.com/salasebas/better-auth-rb/commit/ab6a2f38750c3be141dba21c039c84ec603b2a68))
24
+ * **email-otp:** remove deprecated password reset alias ([bc4614a](https://github.com/salasebas/better-auth-rb/commit/bc4614a23bbd7464e56455777b6b980f20d7e012))
25
+ * **oauth-provider:** align protocol behavior with upstream ([9217273](https://github.com/salasebas/better-auth-rb/commit/9217273069ffa2c18d256a8c1d9d5487a8d316c1))
26
+ * **oauth-provider:** unify OAuth/OIDC/MCP under oauth_provider gem ([7d16def](https://github.com/salasebas/better-auth-rb/commit/7d16def22ea26f753e520e95522568261eca2090))
27
+ * **organization:** align membership limits and lifecycle hooks ([81ab81e](https://github.com/salasebas/better-auth-rb/commit/81ab81ef80aa6d35a6f4bf8a4e54be910fd07aa6))
28
+ * **schema:** align migration plugin schema parity ([c0261dc](https://github.com/salasebas/better-auth-rb/commit/c0261dc1e5fd649557cd8a57b92c0609620c3767))
29
+ * **schema:** honor plugin migration controls ([#48](https://github.com/salasebas/better-auth-rb/issues/48)) ([c67e8bf](https://github.com/salasebas/better-auth-rb/commit/c67e8bf216a2b5f8ad52f53cb9f56e6cfbfa39bb))
30
+
31
+
32
+ ### Bug Fixes
33
+
34
+ * **adapters:** fail closed on singular updates ([c7cf07f](https://github.com/salasebas/better-auth-rb/commit/c7cf07fe19887d682eb94956617532d840beb018))
35
+ * **adapters:** preserve joined records with projected queries ([#47](https://github.com/salasebas/better-auth-rb/issues/47)) ([acd9197](https://github.com/salasebas/better-auth-rb/commit/acd919746715c15a752f0b2861322d09a4f49f20))
36
+ * **auth:** consume single-use state atomically ([07bedc1](https://github.com/salasebas/better-auth-rb/commit/07bedc1c114189e0039a63f2a0cf377658fe457c))
37
+ * **auth:** harden email change verification ([#45](https://github.com/salasebas/better-auth-rb/issues/45)) ([56f631a](https://github.com/salasebas/better-auth-rb/commit/56f631ac9ead26bf7f5719b07ade5ebbac7f3a0a))
38
+ * **auth:** harden token link base URLs ([#36](https://github.com/salasebas/better-auth-rb/issues/36)) ([22d714c](https://github.com/salasebas/better-auth-rb/commit/22d714ce6183b51b555424ebb7e3d2ca0dd36967))
39
+ * **auth:** prevent unverified account takeover ([caae231](https://github.com/salasebas/better-auth-rb/commit/caae23154600a19f637c247466b55404839a2f7a))
40
+ * **auth:** use secure magic-link tokens ([#46](https://github.com/salasebas/better-auth-rb/issues/46)) ([20fcc0b](https://github.com/salasebas/better-auth-rb/commit/20fcc0bbe44371332c2f11187b34d70b3c57e2ce))
41
+ * **auth:** validate social provider identities ([2028af8](https://github.com/salasebas/better-auth-rb/commit/2028af879e4ac61ebc30b3cbc0a4aa32b2d497fe))
42
+ * **ci:** resolve Ruby 3.4 lint, plugin loading, and upstream parity ([ecf5edd](https://github.com/salasebas/better-auth-rb/commit/ecf5edd032eb3695e94456754779656fe017cd7b))
43
+ * **core:** clear session cookies when signed session token is invalid ([d7c2702](https://github.com/salasebas/better-auth-rb/commit/d7c2702529bb6aa0e6fbdc54d1ca5f610c46effb))
44
+ * **core:** harden plugin security and parity ([1cb70f9](https://github.com/salasebas/better-auth-rb/commit/1cb70f940aca6186b39cd2e3f438fe197fbb5495))
45
+ * **core:** prefer adapter execute over Kernel#exec in SQL migrations ([fe99b4f](https://github.com/salasebas/better-auth-rb/commit/fe99b4f4bf6bb20e138691b9e777d64620f8cde4))
46
+ * **core:** restore access-control factory and MSSQL execute signature ([040f583](https://github.com/salasebas/better-auth-rb/commit/040f5839ebe788448cbc39dd403b9ba6a19e50eb))
47
+ * **email-otp:** keep OTP helpers server-only ([#42](https://github.com/salasebas/better-auth-rb/issues/42)) ([99d2469](https://github.com/salasebas/better-auth-rb/commit/99d2469029d6e5b0f439c1b5ed868c544da443f2))
48
+ * harden client IP rate-limit keys ([#39](https://github.com/salasebas/better-auth-rb/issues/39)) ([cb2fefd](https://github.com/salasebas/better-auth-rb/commit/cb2fefd543ef92f8c2c10fedef7f3bd659a90196))
49
+ * load external plugin gems without stub recursion ([4926bae](https://github.com/salasebas/better-auth-rb/commit/4926bae7520c17438de25e99cb2155839c177493))
50
+ * **organization:** enforce membership limit on member creation ([8f7796a](https://github.com/salasebas/better-auth-rb/commit/8f7796aa093c98239914492e66b62e4f1364896b))
51
+ * **organization:** protect creator role updates ([#44](https://github.com/salasebas/better-auth-rb/issues/44)) ([acf03a4](https://github.com/salasebas/better-auth-rb/commit/acf03a4a97d6f4e2a8e4aea88e1fb79cb5197fca))
52
+ * **plugins:** enforce organization and device ownership ([1177216](https://github.com/salasebas/better-auth-rb/commit/117721660a4323926456c5c8b0461c77ff5e651f))
53
+ * **rate-limit:** enforce atomic request limits ([3d7b145](https://github.com/salasebas/better-auth-rb/commit/3d7b145034880459ca7582061ebc4744e12f20a8))
54
+ * **saml:** fail closed without response parser ([#35](https://github.com/salasebas/better-auth-rb/issues/35)) ([0f9a7a4](https://github.com/salasebas/better-auth-rb/commit/0f9a7a4fb841153951b5f00ce42f01eee41b3112))
55
+ * **saml:** verify signed SLO XML messages ([#43](https://github.com/salasebas/better-auth-rb/issues/43)) ([e7730cf](https://github.com/salasebas/better-auth-rb/commit/e7730cf01a683a4b24f1506fd52fda624f4ef028))
56
+ * **scim:** harden provisioning lifecycle ([da956cd](https://github.com/salasebas/better-auth-rb/commit/da956cd117f991f3632ce22611e32b97b1bd1e24))
57
+ * separate canonical and serving origins ([#37](https://github.com/salasebas/better-auth-rb/issues/37)) ([c1bd12e](https://github.com/salasebas/better-auth-rb/commit/c1bd12e81a1cb21a8d0ba186cefd554753e565f6))
58
+ * **session:** enforce authoritative session checks ([0eebb9b](https://github.com/salasebas/better-auth-rb/commit/0eebb9b1a10f9cceaaa05401084fb0d07065c1e3))
59
+ * **siwe:** add get-nonce compatibility alias ([#49](https://github.com/salasebas/better-auth-rb/issues/49)) ([543478d](https://github.com/salasebas/better-auth-rb/commit/543478d2423b4411c68c2069d633ef268a1010a5))
60
+ * **sso:** harden OIDC endpoint fetching ([#38](https://github.com/salasebas/better-auth-rb/issues/38)) ([177bf8b](https://github.com/salasebas/better-auth-rb/commit/177bf8b847ba4e2a8478d82338b3e4cc7b91932d))
61
+ * **two-factor:** enforce verification attempt limits ([f0a8bf4](https://github.com/salasebas/better-auth-rb/commit/f0a8bf4789e9105191e951452879ab09589f5592))
62
+ * unblock SAML/OIDC CI and release workflows ([9d7ed4e](https://github.com/salasebas/better-auth-rb/commit/9d7ed4eb9097a8bf05863bf90f9ba00ae5f2ec76))
63
+
64
+
65
+ ### Miscellaneous Chores
66
+
67
+ * **release:** retire aliases and refresh parity tooling ([e099464](https://github.com/salasebas/better-auth-rb/commit/e0994643694267508a3c4d9be020bb1fd0e2e5a3))
68
+
8
69
  ## [Unreleased]
9
70
 
71
+ ### Added
72
+
73
+ - Added the experimental `oauth_popup` server plugin with built-in and Generic
74
+ OAuth state validation, strict opener-origin checks, callback cookie
75
+ preservation, and optional Bearer integration.
76
+
77
+ ### Fixed
78
+
79
+ - Hardened existing-identity linking, authoritative session checks, single-use
80
+ state consumption, and rate-limit behavior with regression coverage.
81
+ - Made verification state atomic where the configured adapter supports the
82
+ required primitives, while retaining documented adapter compatibility paths.
83
+ - Aligned OAuth Popup state confidentiality and single-use callback behavior,
84
+ duplicate session-cookie handling, and hidden endpoint metadata with upstream
85
+ v1.6.23.
86
+
87
+ ### Changed
88
+
89
+ - Added a checked upstream server-test inventory for the pinned v1.6.23
90
+ reference; it distinguishes covered, adapted, not-applicable, and planned
91
+ behavior rather than asserting blanket version parity.
92
+
93
+ ## [0.10.0] - 2026-05-21
94
+
95
+ ### Fixed
96
+
97
+ - Fixed organization owner counting to page through adapter results instead of
98
+ relying on a single uncapped `find_many` call.
99
+ - Improved SQL, memory, cookie, rate-limit, plugin schema, social login, and
100
+ auth response edge cases for more consistent behavior across adapters.
101
+
10
102
  ## [0.7.0] - 2026-05-05
11
103
 
12
104
  ### Added
data/LICENSE.md CHANGED
@@ -1,5 +1,5 @@
1
1
  The MIT License (MIT)
2
- Copyright (c) 2024 - present, Bereket Engida
2
+ Copyright (c) 2024 - present
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy of
5
5
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -13,11 +13,11 @@
13
13
  ·
14
14
  <a href="https://better-auth.com">Website</a>
15
15
  ·
16
- <a href="https://github.com/sebasxsala/better-auth/issues">Issues</a>
16
+ <a href="https://github.com/sebasxsala/better-auth-rb/issues">Issues</a>
17
17
  </p>
18
18
 
19
19
  [![Gem](https://img.shields.io/gem/v/better_auth?style=flat&colorA=000000&colorB=000000)](https://rubygems.org/gems/better_auth)
20
- [![GitHub stars](https://img.shields.io/github/stars/sebasxsala/better-auth?style=flat&colorA=000000&colorB=000000)](https://github.com/sebasxsala/better-auth/stargazers)
20
+ [![GitHub stars](https://img.shields.io/github/stars/sebasxsala/better-auth?style=flat&colorA=000000&colorB=000000)](https://github.com/sebasxsala/better-auth-rb/stargazers)
21
21
  </p>
22
22
 
23
23
  ## About the Project
@@ -61,11 +61,20 @@ gem install better_auth
61
61
  require 'better_auth'
62
62
 
63
63
  auth = BetterAuth.auth(
64
+ base_url: ENV.fetch("BETTER_AUTH_URL"),
64
65
  secret: ENV.fetch("BETTER_AUTH_SECRET"),
65
66
  database: :memory
66
67
  )
67
68
  ```
68
69
 
70
+ In production, token-bearing email links require either a static `base_url`
71
+ (including `BETTER_AUTH_URL`) or a dynamic `base_url` with `allowed_hosts`.
72
+ Request-host inference remains available for other routes and in development.
73
+ Legacy deployments can opt out with
74
+ `advanced: { allow_unsafe_token_link_base_url_inference: true }`, but doing so
75
+ allows request headers to select the origin used in security-token links and is
76
+ not recommended.
77
+
69
78
  ### Secret Rotation
70
79
 
71
80
  Better Auth Ruby supports upstream-style non-destructive rotation for encrypted data through versioned secrets. The first entry is used for new encrypted payloads; older entries remain available for decrypting existing data.
@@ -143,6 +152,21 @@ auth = BetterAuth.auth(
143
152
  )
144
153
  ```
145
154
 
155
+ Custom adapters must implement
156
+ `create_if_absent(model:, data:, conflict_field: "id", force_allow_id: true)`
157
+ as a targeted first-writer-wins insert returning a boolean,
158
+ `consume_one(model:, where:)` as an atomic
159
+ delete-and-return of at most one row, and
160
+ `increment_one(model:, where:, increment:, set: nil)` as an atomic guarded
161
+ numeric update returning the resulting row or `nil`. Concurrent consumers of
162
+ one row must have exactly one winner; increments must apply every signed delta
163
+ without lost updates. `transaction` must open a real transaction and reuse its
164
+ active adapter for nested calls. The base class's compatibility yield is
165
+ explicitly non-atomic and must not back either primitive.
166
+ Only mutable schema fields declared with `type: "number"` may be incremented;
167
+ IDs and string, boolean, date, array, immutable, or unknown fields are rejected.
168
+ The base adapter intentionally has no check-then-create fallback.
169
+
146
170
  ### Social Providers
147
171
 
148
172
  ```ruby
@@ -163,6 +187,55 @@ auth = BetterAuth.auth(
163
187
  )
164
188
  ```
165
189
 
190
+ ### i18n
191
+
192
+ Translate server error messages by locale. This plugin is server-side only: clients receive translated error JSON from the existing HTTP routes, and there is no Ruby browser client plugin.
193
+
194
+ ```ruby
195
+ auth = BetterAuth.auth(
196
+ secret: ENV.fetch("BETTER_AUTH_SECRET"),
197
+ database: :memory,
198
+ plugins: [
199
+ BetterAuth::Plugins.i18n(
200
+ translations: {
201
+ "fr" => {
202
+ "INVALID_EMAIL_OR_PASSWORD" => "Identifiants invalides"
203
+ }
204
+ },
205
+ detection: ["cookie", "header"],
206
+ locale_cookie: "lang"
207
+ )
208
+ ]
209
+ )
210
+ ```
211
+
212
+ ### Organizations
213
+
214
+ The organization plugin supports upstream-style organizations, members,
215
+ invitations, teams, roles, membership limits, and organization lifecycle hooks.
216
+ Ruby option and hook names use snake_case.
217
+
218
+ ```ruby
219
+ auth = BetterAuth.auth(
220
+ secret: ENV.fetch("BETTER_AUTH_SECRET"),
221
+ database: :memory,
222
+ plugins: [
223
+ BetterAuth::Plugins.organization(
224
+ membership_limit: 25,
225
+ disable_organization_deletion: true,
226
+ organization_hooks: {
227
+ before_update_organization: ->(data, _ctx) { { data: { name: data[:organization][:name].strip } } },
228
+ after_add_member: ->(data, _ctx) { Audit.log("member added", data[:member]) }
229
+ }
230
+ )
231
+ ]
232
+ )
233
+ ```
234
+
235
+ Direct member adds and invitation acceptance enforce `membership_limit`.
236
+ Deleting or leaving the active organization clears the active organization and
237
+ team session fields.
238
+
166
239
  ### JavaScript Client
167
240
 
168
241
  Ruby Better Auth exposes the same HTTP route surface. Frontend apps should use the upstream Better Auth JavaScript client and point it at the Ruby server:
@@ -176,6 +249,25 @@ export const authClient = createAuthClient({
176
249
  });
177
250
  ```
178
251
 
252
+ For experimental popup-based OAuth in an embedded application, register the
253
+ server plugin with Bearer and configure the opener as a trusted origin:
254
+
255
+ ```ruby
256
+ auth = BetterAuth.auth(
257
+ base_url: "https://auth.example.com",
258
+ secret: ENV.fetch("BETTER_AUTH_SECRET"),
259
+ trusted_origins: ["https://app.example.com"],
260
+ plugins: [
261
+ BetterAuth::Plugins.oauth_popup,
262
+ BetterAuth::Plugins.bearer
263
+ ]
264
+ )
265
+ ```
266
+
267
+ Use the upstream JavaScript `oauthPopupClient` against this Ruby server. The
268
+ Ruby package provides the server route and callback handling, not a browser
269
+ client. Prefer the ordinary redirect flow for top-level applications.
270
+
179
271
  ### Rails Integration
180
272
 
181
273
  Add to your Gemfile:
@@ -206,13 +298,13 @@ end
206
298
 
207
299
  ## Development
208
300
 
209
- Full documentation is being adapted in the root [`docs/`](/Users/sebastiansala/projects/better-auth/docs/README.md) app. Start with the Ruby-first installation, basic usage, Rack, Rails, PostgreSQL, and MySQL pages there; pages with a Ruby port warning still contain upstream TypeScript examples for reference.
301
+ Full documentation is being adapted in the root [`docs-site/`](../../docs-site/README.md) app. Start with the Ruby-first installation, basic usage, Rack, Rails, PostgreSQL, and MySQL pages there; pages with a Ruby port warning still contain upstream TypeScript examples for reference.
210
302
 
211
303
  ### Quick Start
212
304
 
213
305
  ```bash
214
306
  # 1. Clone the repository
215
- git clone https://github.com/sebasxsala/better-auth.git
307
+ git clone https://github.com/sebasxsala/better-auth-rb.git
216
308
  cd better-auth/packages/better_auth
217
309
 
218
310
  # 2. Install dependencies
@@ -249,106 +341,37 @@ make db-down # Stop containers
249
341
 
250
342
  ### Branch Workflow
251
343
 
252
- This project uses a branch model similar to the upstream:
253
-
254
- **Main Branches:**
255
-
256
- - **`main`**: Stable code, ready for production
257
- - **`canary`**: Development/integration branch (like "development" but specific name)
258
- - "Canary" comes from "canary in a coal mine" - where changes are tested before production
259
- - Feature PRs go to `canary`
260
- - When `canary` is stable, merge to `main` for release
261
-
262
- **Typical workflow:**
344
+ Development happens on `main`. Create feature branches from `main` and open PRs
345
+ back to `main`.
263
346
 
264
347
  ```bash
265
- # 1. Create your feature branch from canary
266
- git checkout canary
267
- git pull origin canary
348
+ git checkout main
349
+ git pull origin main
268
350
  git checkout -b feat/new-feature
269
-
270
- # 2. Make your changes and commits
271
- # ... code ...
272
- git add .
273
- git commit -m "feat(core): add support for X"
274
-
275
- # 3. Push and create PR towards canary
351
+ # ... work ...
276
352
  git push origin feat/new-feature
277
- # Create PR on GitHub towards canary
278
-
279
- # 4. Once merged to canary and tested,
280
- # merge canary → main for release
281
353
  ```
282
354
 
283
- **Why canary instead of development?**
284
-
285
- - Common name in projects with frequent releases
286
- - Suggests it's an "experimental" version that might break
287
- - Allows multiple levels: feature → canary → main
288
-
289
355
  ### How CI/CD Works
290
356
 
291
357
  **Pull Requests:**
292
358
  - Each PR runs: lint + tests on Ruby 3.2 and 3.3
293
359
  - Everything must pass before merging
294
360
 
295
- **Automatic Release (GitHub Actions):**
361
+ **Synchronized releases:**
296
362
 
297
- Release is triggered by package-prefixed tags so each gem can ship independently.
363
+ Release Please maintains one combined version/changelog PR for the 19 linked
364
+ Better Auth gems. After that PR merges, Release Please creates `<gem>/vX.Y.Z`
365
+ tags and GitHub Releases. An environment-approved job validates every tag and
366
+ builds every artifact before configuring RubyGems Trusted Publishing. It then
367
+ publishes in dependency order, skipping an existing immutable gem only when its
368
+ SHA-256 checksum matches. See the repository-root `RELEASING.md` for setup,
369
+ recovery, and operations.
298
370
 
299
- ```bash
300
- # STEP 1: Update the target package version file
301
- # Example: VERSION = "0.1.1"
302
-
303
- # STEP 2: Commit and push to main
304
- git add lib/better_auth/version.rb
305
- git commit -m "chore: bump version to 0.1.1"
306
- git push origin main
307
-
308
- # STEP 3: Create the tag for the gem you want to publish
309
- git tag better_auth-v0.1.1
310
- git push origin better_auth-v0.1.1
311
-
312
- # STEP 4: GitHub Actions automatically:
313
- # - Runs tests
314
- # - Builds the gem
315
- # - Publishes to RubyGems (if version is new)
316
- # - Creates GitHub Release
317
- ```
318
-
319
- Use `better_auth-vX.Y.Z` for the core gem, `better_auth-rails-vX.Y.Z` for Rails, `better_auth-sinatra-vX.Y.Z` for Sinatra, and `better_auth-hanami-vX.Y.Z` for Hanami.
320
-
321
- **Required GitHub Configuration:**
322
-
323
- 1. In RubyGems, configure Trusted Publishing for each gem that should publish from CI.
324
- 2. Use this repository and workflow file: `.github/workflows/release.yml`.
325
- 3. The workflow exchanges GitHub's OIDC token for short-lived RubyGems credentials when the matching package tag is pushed.
326
-
327
- **Dry-run options:**
371
+ **Local packaging dry-run:**
328
372
 
329
373
  ```bash
330
- # Local packaging dry-run
331
374
  make release-check
332
-
333
- # CI dry-run from GitHub Actions
334
- # Actions -> Release -> Run workflow -> dry_run=true
335
- ```
336
-
337
- ### Manual Release (without GitHub Actions)
338
-
339
- Only if you need to do a manual release:
340
-
341
- ```bash
342
- # 1. Update version.rb
343
- # 2. Build the gem
344
- gem build better_auth.gemspec
345
-
346
- # 3. Publish (you need to be logged into RubyGems)
347
- gem push better_auth-*.gem
348
-
349
- # 4. Create and push the tag
350
- git tag -a better_auth-v0.1.1 -m "Release better_auth v0.1.1"
351
- git push origin better_auth-v0.1.1
352
375
  ```
353
376
 
354
377
  ### Project Structure
@@ -370,7 +393,7 @@ test/ # Core tests (Minitest)
370
393
 
371
394
  ## Contributing
372
395
 
373
- Bug reports and pull requests are welcome on GitHub at https://github.com/sebasxsala/better-auth. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/sebasxsala/better-auth/blob/main/CODE_OF_CONDUCT.md).
396
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sebasxsala/better-auth-rb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/sebasxsala/better-auth-rb/blob/main/CODE_OF_CONDUCT.md).
374
397
 
375
398
  ## License
376
399
 
@@ -378,15 +401,39 @@ The gem is available as open source under the terms of the [MIT License](https:/
378
401
 
379
402
  ## Security
380
403
 
404
+ ### Canonical and serving origins
405
+
406
+ `base_url` (or `BETTER_AUTH_URL`) is required and defines the stable canonical
407
+ identity of the auth server. RubyAuth never infers that identity from request
408
+ headers. For an approved multi-domain deployment, configure full origin
409
+ patterns separately:
410
+
411
+ ```ruby
412
+ BetterAuth.auth(
413
+ secret: ENV.fetch("BETTER_AUTH_SECRET"),
414
+ base_url: "https://auth.example.com",
415
+ serving_origins: ["https://tenant.example.com", "https://*.preview.example.com"],
416
+ trusted_origins: ["https://frontend.example.com"]
417
+ )
418
+ ```
419
+
420
+ `serving_origins` may select the request-facing URL used by verification,
421
+ password-reset, deletion, and magic-link capabilities. Unknown hosts fall back
422
+ to the canonical URL. Serving origins are trusted for CSRF and redirect checks,
423
+ but the reverse is intentionally false: a `trusted_origins` entry cannot become
424
+ a serving origin or control token-bearing links.
425
+
426
+ `X-Forwarded-Host` and `X-Forwarded-Proto` are considered only when
427
+ `advanced.trusted_proxy_headers` is explicitly `true`, and the resulting origin
428
+ must still match `serving_origins`. The former hash/dynamic form of `base_url`
429
+ is unsupported; migrate its fallback to `base_url` and its allowlist to full
430
+ `serving_origins` patterns.
431
+
381
432
  ### Trusted origins
382
433
 
383
- `trusted_origins` is merged with the resolved application origin and deployment
384
- configuration rather than acting as an adapter-local CORS switch. Upstream
385
- Better Auth also incorporates environment-driven origins and dynamic
386
- configuration, so an explicit empty array should not be assumed to mean "reject
387
- every browser origin" unless the deployed core configuration documents that full
388
- merge contract. Configure concrete origins for each environment and keep browser
389
- CORS headers in the host Rack stack or reverse proxy. See
434
+ `trusted_origins` is merged with the canonical and serving origins rather than
435
+ acting as an adapter-local CORS switch. Configure concrete origins for each
436
+ environment and keep browser CORS headers in the host Rack stack or reverse proxy. See
390
437
  [`host-app-responsibilities.md`](../../.docs/features/host-app-responsibilities.md)
391
438
  for the boundary between origin validation, CORS, and CSRF ownership.
392
439
 
@@ -5,14 +5,23 @@ module BetterAuth
5
5
  class Base
6
6
  attr_reader :options
7
7
 
8
- def initialize(options)
8
+ TRANSACTION_CONTEXT_KEY = :better_auth_adapter_transaction_context
9
+
10
+ def initialize(options, transaction_context_key: nil)
9
11
  @options = options
12
+ @transaction_context_key = transaction_context_key || Object.new
10
13
  end
11
14
 
12
15
  def create(**)
13
16
  raise NotImplementedError
14
17
  end
15
18
 
19
+ # Atomically insert a row unless +conflict_field+ already exists. Adapter
20
+ # implementations must not emulate this with a check followed by create.
21
+ def create_if_absent(model:, data:, conflict_field: "id", force_allow_id: true)
22
+ raise NotImplementedError
23
+ end
24
+
16
25
  def find_one(**)
17
26
  raise NotImplementedError
18
27
  end
@@ -41,9 +50,63 @@ module BetterAuth
41
50
  raise NotImplementedError
42
51
  end
43
52
 
53
+ # Atomically delete and return at most one matching row. Concurrent
54
+ # consumers of the same row must produce exactly one non-nil result.
55
+ def consume_one(**)
56
+ raise NotImplementedError
57
+ end
58
+
59
+ # Atomically apply signed numeric deltas, with +where+ acting as the
60
+ # selector and guard, and return the resulting row or nil on a miss.
61
+ # Server-managed schema fields remain protected unless a trusted plugin
62
+ # or runtime caller opts in explicitly with +allow_server_managed+.
63
+ # Never derive privileged field names from user-controlled input.
64
+ def increment_one(model:, where:, increment:, set: nil, allow_server_managed: false)
65
+ raise NotImplementedError
66
+ end
67
+
44
68
  def transaction
45
69
  yield self
46
70
  end
71
+
72
+ # The base yield preserves compatibility for non-atomic batch work. It
73
+ # must never be used as proof that a check-then-act fallback is atomic.
74
+ def atomic_transactions?
75
+ false
76
+ end
77
+
78
+ private
79
+
80
+ attr_reader :transaction_context_key
81
+
82
+ def active_transaction_adapter
83
+ Thread.current[TRANSACTION_CONTEXT_KEY]&.fetch(transaction_context_key, nil)
84
+ end
85
+
86
+ def with_transaction_context(transaction_adapter)
87
+ context = Thread.current[TRANSACTION_CONTEXT_KEY] ||= {}
88
+ had_previous = context.key?(transaction_context_key)
89
+ previous = context[transaction_context_key]
90
+ context[transaction_context_key] = transaction_adapter
91
+ yield
92
+ ensure
93
+ if had_previous
94
+ context[transaction_context_key] = previous
95
+ else
96
+ context&.delete(transaction_context_key)
97
+ Thread.current[TRANSACTION_CONTEXT_KEY] = nil if context && context.empty?
98
+ end
99
+ end
100
+
101
+ def grouped_where_clauses(where)
102
+ Array(where).partition { |clause| fetch_key(clause, :connector).to_s.upcase != "OR" }
103
+ end
104
+
105
+ def fetch_key(hash, key)
106
+ return hash[key] if hash.key?(key)
107
+
108
+ hash[key.to_s]
109
+ end
47
110
  end
48
111
  end
49
112
  end