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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +92 -0
- data/LICENSE.md +1 -1
- data/README.md +142 -95
- data/lib/better_auth/adapters/base.rb +64 -1
- data/lib/better_auth/adapters/internal_adapter.rb +197 -11
- data/lib/better_auth/adapters/join_support.rb +10 -0
- data/lib/better_auth/adapters/memory.rb +303 -54
- data/lib/better_auth/adapters/mssql.rb +7 -3
- data/lib/better_auth/adapters/sql.rb +390 -63
- data/lib/better_auth/api.rb +3 -24
- data/lib/better_auth/configuration.rb +48 -47
- data/lib/better_auth/context.rb +43 -67
- data/lib/better_auth/cookies.rb +119 -15
- data/lib/better_auth/core.rb +0 -2
- data/lib/better_auth/crypto.rb +4 -3
- data/lib/better_auth/doctor.rb +108 -0
- data/lib/better_auth/endpoint.rb +88 -5
- data/lib/better_auth/error.rb +3 -1
- data/lib/better_auth/http_client.rb +46 -0
- data/lib/better_auth/logger.rb +1 -1
- data/lib/better_auth/middleware/origin_check.rb +9 -1
- data/lib/better_auth/migration_plan.rb +15 -0
- data/lib/better_auth/oauth2.rb +1 -1
- data/lib/better_auth/oauth_state.rb +147 -0
- data/lib/better_auth/plugin_loader.rb +220 -0
- data/lib/better_auth/plugins/access.rb +10 -13
- data/lib/better_auth/plugins/admin.rb +85 -29
- data/lib/better_auth/plugins/anonymous.rb +2 -0
- data/lib/better_auth/plugins/api_key.rb +4 -7
- data/lib/better_auth/plugins/bearer.rb +8 -22
- data/lib/better_auth/plugins/captcha.rb +24 -6
- data/lib/better_auth/plugins/device_authorization.rb +77 -5
- data/lib/better_auth/plugins/dub.rb +10 -2
- data/lib/better_auth/plugins/email_otp.rb +30 -47
- data/lib/better_auth/plugins/generic_oauth.rb +162 -140
- data/lib/better_auth/plugins/have_i_been_pwned.rb +1 -1
- data/lib/better_auth/plugins/i18n.rb +215 -0
- data/lib/better_auth/plugins/jwt.rb +44 -19
- data/lib/better_auth/plugins/magic_link.rb +18 -21
- data/lib/better_auth/plugins/oauth_popup.rb +330 -0
- data/lib/better_auth/plugins/oauth_protocol.rb +233 -46
- data/lib/better_auth/plugins/oauth_provider.rb +4 -7
- data/lib/better_auth/plugins/oauth_proxy.rb +1 -1
- data/lib/better_auth/plugins/one_tap.rb +78 -65
- data/lib/better_auth/plugins/one_time_token.rb +1 -4
- data/lib/better_auth/plugins/open_api.rb +77 -9
- data/lib/better_auth/plugins/organization.rb +546 -102
- data/lib/better_auth/plugins/passkey.rb +4 -7
- data/lib/better_auth/plugins/phone_number.rb +25 -27
- data/lib/better_auth/plugins/scim.rb +4 -7
- data/lib/better_auth/plugins/siwe.rb +126 -16
- data/lib/better_auth/plugins/sso.rb +4 -7
- data/lib/better_auth/plugins/stripe.rb +4 -7
- data/lib/better_auth/plugins/two_factor.rb +205 -19
- data/lib/better_auth/plugins/username.rb +56 -13
- data/lib/better_auth/plugins.rb +57 -0
- data/lib/better_auth/rate_limiter.rb +300 -133
- data/lib/better_auth/request_ip.rb +123 -13
- data/lib/better_auth/router.rb +3 -3
- data/lib/better_auth/routes/account.rb +44 -8
- data/lib/better_auth/routes/email_verification.rb +93 -6
- data/lib/better_auth/routes/password.rb +6 -6
- data/lib/better_auth/routes/session.rb +7 -2
- data/lib/better_auth/routes/sign_in.rb +10 -1
- data/lib/better_auth/routes/sign_up.rb +45 -22
- data/lib/better_auth/routes/social.rb +197 -54
- data/lib/better_auth/routes/user.rb +17 -10
- data/lib/better_auth/schema/sql.rb +189 -34
- data/lib/better_auth/schema.rb +159 -5
- data/lib/better_auth/session.rb +29 -10
- data/lib/better_auth/session_store.rb +1 -1
- data/lib/better_auth/social_providers/apple.rb +6 -1
- data/lib/better_auth/social_providers/atlassian.rb +1 -0
- data/lib/better_auth/social_providers/base.rb +45 -23
- data/lib/better_auth/social_providers/cognito.rb +75 -5
- data/lib/better_auth/social_providers/facebook.rb +95 -1
- data/lib/better_auth/social_providers/figma.rb +2 -0
- data/lib/better_auth/social_providers/github.rb +2 -0
- data/lib/better_auth/social_providers/google.rb +12 -1
- data/lib/better_auth/social_providers/line.rb +35 -1
- data/lib/better_auth/social_providers/linear.rb +1 -0
- data/lib/better_auth/social_providers/microsoft_entra_id.rb +9 -0
- data/lib/better_auth/social_providers/naver.rb +24 -1
- data/lib/better_auth/social_providers/notion.rb +1 -0
- data/lib/better_auth/social_providers/paybin.rb +25 -1
- data/lib/better_auth/social_providers/paypal.rb +118 -2
- data/lib/better_auth/social_providers/reddit.rb +25 -4
- data/lib/better_auth/social_providers/salesforce.rb +1 -0
- data/lib/better_auth/social_providers/twitch.rb +24 -1
- data/lib/better_auth/social_providers/twitter.rb +36 -1
- data/lib/better_auth/social_providers/vk.rb +34 -1
- data/lib/better_auth/social_providers/wechat.rb +6 -4
- data/lib/better_auth/sql_migration.rb +566 -0
- data/lib/better_auth/version.rb +1 -1
- data/lib/better_auth.rb +14 -41
- metadata +14 -18
- data/lib/better_auth/plugins/mcp/authorization.rb +0 -111
- data/lib/better_auth/plugins/mcp/config.rb +0 -51
- data/lib/better_auth/plugins/mcp/consent.rb +0 -31
- data/lib/better_auth/plugins/mcp/legacy_aliases.rb +0 -43
- data/lib/better_auth/plugins/mcp/metadata.rb +0 -81
- data/lib/better_auth/plugins/mcp/registration.rb +0 -31
- data/lib/better_auth/plugins/mcp/resource_handler.rb +0 -37
- data/lib/better_auth/plugins/mcp/schema.rb +0 -91
- data/lib/better_auth/plugins/mcp/token.rb +0 -108
- data/lib/better_auth/plugins/mcp/userinfo.rb +0 -37
- data/lib/better_auth/plugins/mcp.rb +0 -215
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d7cf9a11bc4e929dad460e2d1d72cf12e9a34465f73d14259293267f06e591e
|
|
4
|
+
data.tar.gz: 06c018ee8a3fad409045a6a6639c851fb596a854707540dc710f8651e87b0d79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
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
|
[](https://rubygems.org/gems/better_auth)
|
|
20
|
-
[](https://github.com/sebasxsala/better-auth/stargazers)
|
|
20
|
+
[](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/`](
|
|
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
|
-
|
|
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
|
-
|
|
266
|
-
git
|
|
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
|
-
**
|
|
361
|
+
**Synchronized releases:**
|
|
296
362
|
|
|
297
|
-
Release
|
|
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
|
-
|
|
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
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
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
|