better_auth 0.10.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +138 -91
  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 +191 -56
  9. data/lib/better_auth/adapters/mssql.rb +7 -3
  10. data/lib/better_auth/adapters/sql.rb +294 -49
  11. data/lib/better_auth/api.rb +3 -24
  12. data/lib/better_auth/configuration.rb +41 -46
  13. data/lib/better_auth/context.rb +43 -67
  14. data/lib/better_auth/cookies.rb +110 -14
  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 +14 -3
  18. data/lib/better_auth/error.rb +3 -1
  19. data/lib/better_auth/logger.rb +1 -1
  20. data/lib/better_auth/middleware/origin_check.rb +9 -1
  21. data/lib/better_auth/migration_plan.rb +4 -4
  22. data/lib/better_auth/oauth_state.rb +147 -0
  23. data/lib/better_auth/plugin_loader.rb +220 -0
  24. data/lib/better_auth/plugins/access.rb +10 -13
  25. data/lib/better_auth/plugins/admin.rb +79 -28
  26. data/lib/better_auth/plugins/api_key.rb +4 -7
  27. data/lib/better_auth/plugins/bearer.rb +8 -22
  28. data/lib/better_auth/plugins/captcha.rb +24 -6
  29. data/lib/better_auth/plugins/device_authorization.rb +47 -9
  30. data/lib/better_auth/plugins/dub.rb +2 -2
  31. data/lib/better_auth/plugins/email_otp.rb +30 -47
  32. data/lib/better_auth/plugins/generic_oauth.rb +141 -146
  33. data/lib/better_auth/plugins/i18n.rb +215 -0
  34. data/lib/better_auth/plugins/jwt.rb +37 -19
  35. data/lib/better_auth/plugins/magic_link.rb +18 -21
  36. data/lib/better_auth/plugins/oauth_popup.rb +330 -0
  37. data/lib/better_auth/plugins/oauth_protocol.rb +142 -32
  38. data/lib/better_auth/plugins/oauth_provider.rb +4 -7
  39. data/lib/better_auth/plugins/oauth_proxy.rb +1 -1
  40. data/lib/better_auth/plugins/one_tap.rb +61 -60
  41. data/lib/better_auth/plugins/one_time_token.rb +1 -4
  42. data/lib/better_auth/plugins/open_api.rb +35 -7
  43. data/lib/better_auth/plugins/organization.rb +455 -122
  44. data/lib/better_auth/plugins/passkey.rb +4 -7
  45. data/lib/better_auth/plugins/phone_number.rb +24 -26
  46. data/lib/better_auth/plugins/scim.rb +4 -7
  47. data/lib/better_auth/plugins/siwe.rb +126 -16
  48. data/lib/better_auth/plugins/sso.rb +4 -7
  49. data/lib/better_auth/plugins/stripe.rb +4 -7
  50. data/lib/better_auth/plugins/two_factor.rb +186 -21
  51. data/lib/better_auth/plugins/username.rb +56 -13
  52. data/lib/better_auth/plugins.rb +57 -0
  53. data/lib/better_auth/rate_limiter.rb +297 -135
  54. data/lib/better_auth/request_ip.rb +123 -13
  55. data/lib/better_auth/router.rb +3 -3
  56. data/lib/better_auth/routes/account.rb +40 -8
  57. data/lib/better_auth/routes/email_verification.rb +88 -5
  58. data/lib/better_auth/routes/password.rb +5 -6
  59. data/lib/better_auth/routes/session.rb +7 -2
  60. data/lib/better_auth/routes/sign_in.rb +10 -1
  61. data/lib/better_auth/routes/sign_up.rb +45 -22
  62. data/lib/better_auth/routes/social.rb +169 -54
  63. data/lib/better_auth/routes/user.rb +11 -8
  64. data/lib/better_auth/schema/sql.rb +90 -24
  65. data/lib/better_auth/schema.rb +124 -3
  66. data/lib/better_auth/session.rb +27 -9
  67. data/lib/better_auth/session_store.rb +1 -1
  68. data/lib/better_auth/social_providers/apple.rb +6 -1
  69. data/lib/better_auth/social_providers/atlassian.rb +1 -0
  70. data/lib/better_auth/social_providers/base.rb +41 -14
  71. data/lib/better_auth/social_providers/cognito.rb +75 -5
  72. data/lib/better_auth/social_providers/facebook.rb +95 -1
  73. data/lib/better_auth/social_providers/figma.rb +2 -0
  74. data/lib/better_auth/social_providers/google.rb +12 -1
  75. data/lib/better_auth/social_providers/line.rb +35 -1
  76. data/lib/better_auth/social_providers/linear.rb +1 -0
  77. data/lib/better_auth/social_providers/microsoft_entra_id.rb +9 -0
  78. data/lib/better_auth/social_providers/naver.rb +24 -1
  79. data/lib/better_auth/social_providers/notion.rb +1 -0
  80. data/lib/better_auth/social_providers/paybin.rb +25 -1
  81. data/lib/better_auth/social_providers/paypal.rb +118 -2
  82. data/lib/better_auth/social_providers/reddit.rb +25 -4
  83. data/lib/better_auth/social_providers/salesforce.rb +1 -0
  84. data/lib/better_auth/social_providers/twitch.rb +24 -1
  85. data/lib/better_auth/social_providers/twitter.rb +36 -1
  86. data/lib/better_auth/social_providers/vk.rb +34 -1
  87. data/lib/better_auth/social_providers/wechat.rb +6 -4
  88. data/lib/better_auth/sql_migration.rb +5 -5
  89. data/lib/better_auth/version.rb +1 -1
  90. data/lib/better_auth.rb +11 -41
  91. metadata +5 -13
  92. data/lib/better_auth/plugins/mcp/authorization.rb +0 -111
  93. data/lib/better_auth/plugins/mcp/config.rb +0 -51
  94. data/lib/better_auth/plugins/mcp/consent.rb +0 -31
  95. data/lib/better_auth/plugins/mcp/legacy_aliases.rb +0 -43
  96. data/lib/better_auth/plugins/mcp/metadata.rb +0 -81
  97. data/lib/better_auth/plugins/mcp/registration.rb +0 -31
  98. data/lib/better_auth/plugins/mcp/resource_handler.rb +0 -37
  99. data/lib/better_auth/plugins/mcp/schema.rb +0 -91
  100. data/lib/better_auth/plugins/mcp/token.rb +0 -108
  101. data/lib/better_auth/plugins/mcp/userinfo.rb +0 -37
  102. data/lib/better_auth/plugins/mcp.rb +0 -256
  103. data/lib/better_auth/plugins/oidc_provider.rb +0 -769
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "jwt"
4
-
5
- module BetterAuth
6
- module Plugins
7
- module MCP
8
- module_function
9
-
10
- def userinfo(ctx, config)
11
- OAuthProtocol.userinfo(config[:store], ctx.headers["authorization"], prefix: config[:prefix], jwt_secret: ctx.context.secret)
12
- end
13
-
14
- def session_from_token(ctx, config)
15
- authorization = ctx.headers["authorization"].to_s
16
- token_value = authorization.start_with?("Bearer ") ? authorization.delete_prefix("Bearer ").strip : authorization.strip
17
- return nil if token_value.empty?
18
-
19
- token_record = OAuthProtocol.token_record(config[:store], token_value, prefix: config[:prefix])
20
- return token_record if token_record
21
-
22
- payload = ::JWT.decode(token_value, ctx.context.secret, true, algorithm: "HS256").first
23
- {
24
- "clientId" => payload["azp"],
25
- "userId" => payload["sub"],
26
- "sessionId" => payload["sid"],
27
- "scopes" => OAuthProtocol.parse_scopes(payload["scope"]),
28
- "audience" => payload["aud"],
29
- "subject" => payload["sub"],
30
- "expiresAt" => payload["exp"] ? Time.at(payload["exp"].to_i) : nil
31
- }.compact
32
- rescue ::JWT::DecodeError
33
- nil
34
- end
35
- end
36
- end
37
- end
@@ -1,256 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require_relative "mcp/config"
5
- require_relative "mcp/metadata"
6
- require_relative "mcp/schema"
7
- require_relative "mcp/registration"
8
- require_relative "mcp/authorization"
9
- require_relative "mcp/consent"
10
- require_relative "mcp/token"
11
- require_relative "mcp/userinfo"
12
- require_relative "mcp/resource_handler"
13
- require_relative "mcp/legacy_aliases"
14
-
15
- module BetterAuth
16
- module Plugins
17
- module MCP
18
- module_function
19
-
20
- def with_mcp_auth(app, resource_metadata_url:, auth: nil, resource_metadata_mappings: {})
21
- ResourceHandler.with_mcp_auth(
22
- app,
23
- resource_metadata_url: resource_metadata_url,
24
- auth: auth,
25
- resource_metadata_mappings: resource_metadata_mappings
26
- )
27
- end
28
- end
29
-
30
- module_function
31
-
32
- def mcp(options = {})
33
- config = MCP.normalize_config(options)
34
- Plugin.new(
35
- id: "mcp",
36
- endpoints: mcp_endpoints(config),
37
- hooks: {after: [{matcher: ->(_ctx) { true }, handler: ->(ctx) { MCP.restore_login_prompt(ctx, config) }}]},
38
- schema: MCP.schema,
39
- options: config
40
- )
41
- end
42
-
43
- def mcp_endpoints(config)
44
- {
45
- get_mcp_o_auth_config: mcp_oauth_config_endpoint(config),
46
- get_mcp_protected_resource: mcp_protected_resource_endpoint(config),
47
- mcp_register: mcp_register_endpoint(config),
48
- legacy_mcp_register: MCP.legacy_register_endpoint(config),
49
- mcp_o_auth_authorize: mcp_authorize_endpoint(config),
50
- legacy_mcp_o_auth_authorize: MCP.legacy_authorize_endpoint(config),
51
- o_auth_consent: mcp_consent_endpoint(config),
52
- mcp_o_auth_token: mcp_token_endpoint(config),
53
- legacy_mcp_o_auth_token: MCP.legacy_token_endpoint(config),
54
- mcp_o_auth_user_info: mcp_userinfo_endpoint(config),
55
- legacy_mcp_o_auth_user_info: MCP.legacy_userinfo_endpoint(config),
56
- get_mcp_session: mcp_get_session_endpoint(config),
57
- mcp_jwks: mcp_jwks_endpoint(config),
58
- legacy_mcp_jwks: MCP.legacy_jwks_endpoint(config),
59
- mcp_o_auth_introspect: mcp_introspect_endpoint(config),
60
- mcp_o_auth_revoke: mcp_revoke_endpoint(config)
61
- }
62
- end
63
-
64
- def mcp_oauth_config_endpoint(config)
65
- Endpoint.new(path: "/.well-known/oauth-authorization-server", method: "GET", metadata: {hide: true}) do |ctx|
66
- ctx.json(MCP.oauth_metadata(ctx, config))
67
- end
68
- end
69
-
70
- def mcp_protected_resource_endpoint(config)
71
- Endpoint.new(path: "/.well-known/oauth-protected-resource", method: "GET", metadata: {hide: true}) do |ctx|
72
- ctx.json(MCP.protected_resource_metadata(ctx, config))
73
- end
74
- end
75
-
76
- def mcp_register_endpoint(config)
77
- Endpoint.new(path: "/oauth2/register", method: "POST", metadata: mcp_openapi("registerMcpClient", "Register an OAuth2 application", "OAuth2 application registered successfully", mcp_client_schema)) do |ctx|
78
- ctx.json(MCP.register_client(ctx, config), status: 201, headers: MCP.no_store_headers)
79
- end
80
- end
81
-
82
- def mcp_authorize_endpoint(config)
83
- Endpoint.new(path: "/oauth2/authorize", method: "GET", metadata: mcp_openapi("mcpOAuthAuthorize", "Authorize an OAuth2 request using MCP", "Authorization response generated successfully", {type: "object", additionalProperties: true})) do |ctx|
84
- MCP.authorize(ctx, config)
85
- end
86
- end
87
-
88
- def mcp_consent_endpoint(config)
89
- Endpoint.new(path: "/oauth2/consent", method: "POST", metadata: mcp_openapi("mcpOAuthConsent", "Handle MCP OAuth2 consent", "OAuth2 consent handled successfully", {type: "object", additionalProperties: true})) do |ctx|
90
- ctx.json(MCP.consent(ctx, config))
91
- end
92
- end
93
-
94
- def mcp_token_endpoint(config)
95
- Endpoint.new(
96
- path: "/oauth2/token",
97
- method: "POST",
98
- metadata: mcp_openapi("mcpOAuthToken", "Exchange OAuth2 code for MCP tokens", "OAuth2 tokens issued successfully", mcp_token_response_schema).merge(allowed_media_types: ["application/x-www-form-urlencoded", "application/json"])
99
- ) do |ctx|
100
- ctx.json(MCP.token(ctx, config), headers: MCP.no_store_headers)
101
- end
102
- end
103
-
104
- def mcp_userinfo_endpoint(config)
105
- Endpoint.new(path: "/oauth2/userinfo", method: "GET", metadata: mcp_openapi("mcpOAuthUserinfo", "Get MCP OAuth2 user information", "User information retrieved successfully", mcp_userinfo_schema)) do |ctx|
106
- ctx.json(MCP.userinfo(ctx, config))
107
- end
108
- end
109
-
110
- def mcp_get_session_endpoint(config)
111
- Endpoint.new(path: "/mcp/get-session", method: "GET", metadata: mcp_openapi("getMcpSession", "Get the MCP session", "MCP session retrieved successfully", {type: ["object", "null"]})) do |ctx|
112
- ctx.json(MCP.session_from_token(ctx, config))
113
- end
114
- end
115
-
116
- def mcp_jwks_endpoint(config)
117
- Endpoint.new(path: "/oauth2/jwks", method: "GET", metadata: mcp_openapi("getMcpJSONWebKeySet", "Get the MCP JSON Web Key Set", "JSON Web Key Set retrieved successfully", mcp_jwks_response_schema)) do |ctx|
118
- ctx.json(MCP.jwks(ctx, config))
119
- end
120
- end
121
-
122
- def mcp_introspect_endpoint(config)
123
- Endpoint.new(
124
- path: "/oauth2/introspect",
125
- method: "POST",
126
- metadata: mcp_openapi("mcpOAuthIntrospect", "Introspect an MCP OAuth2 token", "OAuth2 token introspection result", mcp_introspection_schema).merge(allowed_media_types: ["application/x-www-form-urlencoded", "application/json"])
127
- ) do |ctx|
128
- ctx.json(MCP.introspect(ctx, config))
129
- end
130
- end
131
-
132
- def mcp_revoke_endpoint(config)
133
- Endpoint.new(
134
- path: "/oauth2/revoke",
135
- method: "POST",
136
- metadata: mcp_openapi("mcpOAuthRevoke", "Revoke an MCP OAuth2 token", "OAuth2 token revoked successfully", OpenAPI.object_schema({revoked: {type: "boolean"}}, required: ["revoked"])).merge(allowed_media_types: ["application/x-www-form-urlencoded", "application/json"])
137
- ) do |ctx|
138
- ctx.json(MCP.revoke(ctx, config))
139
- end
140
- end
141
-
142
- def mcp_openapi(operation_id, description, response_description, response_schema)
143
- {
144
- openapi: {
145
- operationId: operation_id,
146
- description: description,
147
- requestBody: mcp_request_body_for(operation_id),
148
- responses: {
149
- "200" => OpenAPI.json_response(response_description, response_schema)
150
- }
151
- }
152
- }
153
- end
154
-
155
- def mcp_request_body_for(operation_id)
156
- schema = case operation_id
157
- when "registerMcpClient", "legacyRegisterMcpClient"
158
- OpenAPI.object_schema(
159
- {
160
- redirect_uris: {type: "array", items: {type: "string", format: "uri"}},
161
- client_name: {type: "string"},
162
- client_uri: {type: "string", format: "uri"},
163
- logo_uri: {type: "string", format: "uri"},
164
- grant_types: {type: "array", items: {type: "string"}},
165
- response_types: {type: "array", items: {type: "string"}},
166
- scope: {type: "string"},
167
- contacts: {type: "array", items: {type: "string"}},
168
- metadata: {type: "object", additionalProperties: true}
169
- },
170
- required: ["redirect_uris"]
171
- )
172
- when "mcpOAuthConsent"
173
- OpenAPI.object_schema({consent_code: {type: "string"}, accept: {type: "boolean"}, scope: {type: "string"}, scopes: {type: "array", items: {type: "string"}}}, required: ["consent_code"])
174
- when "mcpOAuthToken", "legacyMcpOAuthToken"
175
- OpenAPI.object_schema(
176
- {
177
- grant_type: {type: "string", enum: [OAuthProtocol::AUTH_CODE_GRANT, OAuthProtocol::REFRESH_GRANT]},
178
- code: {type: "string"},
179
- redirect_uri: {type: "string", format: "uri"},
180
- code_verifier: {type: "string"},
181
- client_id: {type: "string"},
182
- client_secret: {type: "string"},
183
- refresh_token: {type: "string"},
184
- scope: {type: "string"},
185
- resource: {oneOf: [{type: "string"}, {type: "array", items: {type: "string"}}]}
186
- },
187
- required: ["grant_type"]
188
- )
189
- when "mcpOAuthIntrospect", "mcpOAuthRevoke"
190
- OpenAPI.object_schema({token: {type: "string"}, token_type_hint: {type: "string", enum: ["access_token", "refresh_token"]}}, required: ["token"])
191
- end
192
- schema ? OpenAPI.json_request_body(schema) : nil
193
- end
194
-
195
- def mcp_client_schema
196
- OpenAPI.object_schema(
197
- {
198
- clientId: {type: "string"},
199
- clientSecret: {type: ["string", "null"]},
200
- name: {type: ["string", "null"]},
201
- redirectUris: {type: "array", items: {type: "string"}}
202
- },
203
- required: ["clientId"]
204
- )
205
- end
206
-
207
- def mcp_token_response_schema
208
- OpenAPI.object_schema(
209
- {
210
- access_token: {type: "string"},
211
- token_type: {type: "string"},
212
- expires_in: {type: "number"},
213
- refresh_token: {type: ["string", "null"]},
214
- scope: {type: ["string", "null"]}
215
- },
216
- required: ["access_token", "token_type", "expires_in"]
217
- )
218
- end
219
-
220
- def mcp_userinfo_schema
221
- OpenAPI.object_schema(
222
- {
223
- sub: {type: "string"},
224
- email: {type: ["string", "null"]},
225
- email_verified: {type: ["boolean", "null"]},
226
- name: {type: ["string", "null"]}
227
- },
228
- required: ["sub"]
229
- )
230
- end
231
-
232
- def mcp_jwks_response_schema
233
- OpenAPI.object_schema(
234
- {keys: {type: "array", items: {type: "object"}}},
235
- required: ["keys"]
236
- )
237
- end
238
-
239
- def mcp_introspection_schema
240
- OpenAPI.object_schema(
241
- {
242
- active: {type: "boolean"},
243
- client_id: {type: ["string", "null"]},
244
- scope: {type: ["string", "null"]},
245
- sub: {type: ["string", "null"]},
246
- iss: {type: ["string", "null"]},
247
- iat: {type: ["number", "null"]},
248
- exp: {type: ["number", "null"]},
249
- sid: {type: ["string", "null"]},
250
- aud: {type: ["string", "array", "null"], items: {type: "string"}}
251
- },
252
- required: ["active"]
253
- )
254
- end
255
- end
256
- end