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
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
module Plugins
|
|
5
|
+
# Maps `BetterAuth::Plugins` factory methods and nested modules to source files.
|
|
6
|
+
PLUGIN_FILES = {
|
|
7
|
+
additional_fields: "plugins/additional_fields",
|
|
8
|
+
custom_session: "plugins/custom_session",
|
|
9
|
+
multi_session: "plugins/multi_session",
|
|
10
|
+
last_login_method: "plugins/last_login_method",
|
|
11
|
+
bearer: "plugins/bearer",
|
|
12
|
+
jwt: "plugins/jwt",
|
|
13
|
+
open_api: "plugins/open_api",
|
|
14
|
+
access: "plugins/access",
|
|
15
|
+
username: "plugins/username",
|
|
16
|
+
anonymous: "plugins/anonymous",
|
|
17
|
+
magic_link: "plugins/magic_link",
|
|
18
|
+
email_otp: "plugins/email_otp",
|
|
19
|
+
phone_number: "plugins/phone_number",
|
|
20
|
+
one_time_token: "plugins/one_time_token",
|
|
21
|
+
one_tap: "plugins/one_tap",
|
|
22
|
+
siwe: "plugins/siwe",
|
|
23
|
+
generic_oauth: "plugins/generic_oauth",
|
|
24
|
+
oauth_popup: "plugins/oauth_popup",
|
|
25
|
+
dub: "plugins/dub",
|
|
26
|
+
oauth_proxy: "plugins/oauth_proxy",
|
|
27
|
+
passkey: "plugins/passkey",
|
|
28
|
+
organization_schema: "plugins/organization/schema",
|
|
29
|
+
organization: "plugins/organization",
|
|
30
|
+
admin_schema: "plugins/admin/schema",
|
|
31
|
+
admin: "plugins/admin",
|
|
32
|
+
oauth_protocol: "plugins/oauth_protocol",
|
|
33
|
+
oauth_provider: "plugins/oauth_provider",
|
|
34
|
+
device_authorization: "plugins/device_authorization",
|
|
35
|
+
two_factor: "plugins/two_factor",
|
|
36
|
+
captcha: "plugins/captcha",
|
|
37
|
+
have_i_been_pwned: "plugins/have_i_been_pwned",
|
|
38
|
+
api_key: "plugins/api_key",
|
|
39
|
+
sso: "plugins/sso",
|
|
40
|
+
scim: "plugins/scim",
|
|
41
|
+
stripe: "plugins/stripe",
|
|
42
|
+
expo: "plugins/expo",
|
|
43
|
+
i18n: "plugins/i18n"
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
PLUGIN_DEPENDENCIES = {
|
|
47
|
+
organization: %i[organization_schema access],
|
|
48
|
+
admin_schema: %i[organization_schema],
|
|
49
|
+
admin: %i[admin_schema access],
|
|
50
|
+
device_authorization: %i[oauth_protocol]
|
|
51
|
+
}.freeze
|
|
52
|
+
|
|
53
|
+
# Core route metadata helpers; loaded at boot because base endpoints reference OpenAPI.
|
|
54
|
+
BOOT_PLUGINS = %i[open_api].freeze
|
|
55
|
+
|
|
56
|
+
PLUGIN_ID_TO_LOADER = {
|
|
57
|
+
"additional-fields" => :additional_fields,
|
|
58
|
+
"custom-session" => :custom_session,
|
|
59
|
+
"multi-session" => :multi_session,
|
|
60
|
+
"last-login-method" => :last_login_method,
|
|
61
|
+
"bearer" => :bearer,
|
|
62
|
+
"jwt" => :jwt,
|
|
63
|
+
"open-api" => :open_api,
|
|
64
|
+
"username" => :username,
|
|
65
|
+
"anonymous" => :anonymous,
|
|
66
|
+
"magic-link" => :magic_link,
|
|
67
|
+
"email-otp" => :email_otp,
|
|
68
|
+
"phone-number" => :phone_number,
|
|
69
|
+
"one-time-token" => :one_time_token,
|
|
70
|
+
"one-tap" => :one_tap,
|
|
71
|
+
"siwe" => :siwe,
|
|
72
|
+
"generic-oauth" => :generic_oauth,
|
|
73
|
+
"oauth-popup" => :oauth_popup,
|
|
74
|
+
"dub" => :dub,
|
|
75
|
+
"oauth-proxy" => :oauth_proxy,
|
|
76
|
+
"passkey" => :passkey,
|
|
77
|
+
"organization" => :organization,
|
|
78
|
+
"admin" => :admin,
|
|
79
|
+
"oauth-provider" => :oauth_provider,
|
|
80
|
+
"device-authorization" => :device_authorization,
|
|
81
|
+
"two-factor" => :two_factor,
|
|
82
|
+
"captcha" => :captcha,
|
|
83
|
+
"have-i-been-pwned" => :have_i_been_pwned,
|
|
84
|
+
"api-key" => :api_key,
|
|
85
|
+
"sso" => :sso,
|
|
86
|
+
"scim" => :scim,
|
|
87
|
+
"stripe" => :stripe,
|
|
88
|
+
"expo" => :expo,
|
|
89
|
+
"i18n" => :i18n
|
|
90
|
+
}.freeze
|
|
91
|
+
|
|
92
|
+
NESTED_MODULE_LOADERS = {
|
|
93
|
+
OAuthProtocol: :oauth_protocol,
|
|
94
|
+
JWT: :jwt,
|
|
95
|
+
OrganizationSchema: :organization_schema,
|
|
96
|
+
AdminSchema: :admin_schema
|
|
97
|
+
}.freeze
|
|
98
|
+
|
|
99
|
+
LAZY_PLUGIN_METHODS = PLUGIN_FILES.keys.freeze
|
|
100
|
+
|
|
101
|
+
@loaded_plugins = {}
|
|
102
|
+
|
|
103
|
+
EXTERNAL_PLUGIN_IMPLEMENTATIONS = {
|
|
104
|
+
sso: :SSO_PLUGIN_IMPLEMENTATION,
|
|
105
|
+
scim: :SCIM_PLUGIN_IMPLEMENTATION,
|
|
106
|
+
api_key: :API_KEY_PLUGIN_IMPLEMENTATION,
|
|
107
|
+
passkey: :PASSKEY_PLUGIN_IMPLEMENTATION,
|
|
108
|
+
stripe: :STRIPE_PLUGIN_IMPLEMENTATION,
|
|
109
|
+
oauth_provider: :OAUTH_PROVIDER_PLUGIN_IMPLEMENTATION
|
|
110
|
+
}.freeze
|
|
111
|
+
|
|
112
|
+
module_function
|
|
113
|
+
|
|
114
|
+
def ensure_external_plugin_loaded!(gem_name:, entry:, implementation_constant:)
|
|
115
|
+
return if const_defined?(implementation_constant, false)
|
|
116
|
+
|
|
117
|
+
spec = Gem.loaded_specs[gem_name] || Gem::Specification.find_by_name(gem_name)
|
|
118
|
+
entry_path = File.join(spec.full_gem_path, entry)
|
|
119
|
+
load entry_path unless $LOADED_FEATURES.include?(entry_path)
|
|
120
|
+
|
|
121
|
+
return if const_defined?(implementation_constant, false)
|
|
122
|
+
|
|
123
|
+
raise LoadError,
|
|
124
|
+
"BetterAuth requires the #{gem_name} gem. Add it to your Gemfile and require its entrypoint."
|
|
125
|
+
rescue Gem::MissingSpecError
|
|
126
|
+
raise LoadError,
|
|
127
|
+
"BetterAuth requires the #{gem_name} gem. Add it to your Gemfile and require its entrypoint."
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def call_external_plugin!(method_name, *args, implementation_constant:, gem_name:, entry:, &block)
|
|
131
|
+
loader = method(method_name)
|
|
132
|
+
ensure_external_plugin_loaded!(
|
|
133
|
+
gem_name: gem_name,
|
|
134
|
+
entry: entry,
|
|
135
|
+
implementation_constant: implementation_constant
|
|
136
|
+
)
|
|
137
|
+
resolved = method(method_name)
|
|
138
|
+
if resolved == loader
|
|
139
|
+
raise LoadError,
|
|
140
|
+
"BetterAuth::Plugins.#{method_name} requires the #{gem_name} gem. Add it to your Gemfile and require its entrypoint."
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
resolved.call(*args, &block)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def load_plugin!(name)
|
|
147
|
+
name = name.to_sym
|
|
148
|
+
return true if @loaded_plugins[name]
|
|
149
|
+
implementation_constant = EXTERNAL_PLUGIN_IMPLEMENTATIONS[name]
|
|
150
|
+
if implementation_constant && const_defined?(implementation_constant, false)
|
|
151
|
+
@loaded_plugins[name] = true
|
|
152
|
+
return true
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
Array(PLUGIN_DEPENDENCIES[name]).each { |dependency| load_plugin!(dependency) }
|
|
156
|
+
|
|
157
|
+
relative_path = PLUGIN_FILES.fetch(name) do
|
|
158
|
+
raise ArgumentError, "Unknown plugin loader: #{name}"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
absolute_path = File.expand_path(relative_path, __dir__)
|
|
162
|
+
unless File.file?("#{absolute_path}.rb")
|
|
163
|
+
@loaded_plugins[name] = true
|
|
164
|
+
return false
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
require_relative relative_path
|
|
168
|
+
@loaded_plugins[name] = true
|
|
169
|
+
true
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def plugin_loaded?(name)
|
|
173
|
+
@loaded_plugins.key?(name.to_sym)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def ensure_plugin_loaded_for!(plugin)
|
|
177
|
+
return unless plugin.is_a?(BetterAuth::Plugin)
|
|
178
|
+
|
|
179
|
+
loader = PLUGIN_ID_TO_LOADER[plugin.id]
|
|
180
|
+
load_plugin!(loader) if loader
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def lazy_plugin_method?(name)
|
|
184
|
+
!plugin_loader_for_method(name).nil?
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def plugin_loader_for_method(name)
|
|
188
|
+
symbol = name.to_sym
|
|
189
|
+
return symbol if LAZY_PLUGIN_METHODS.include?(symbol)
|
|
190
|
+
|
|
191
|
+
PLUGIN_FILES.each_key do |plugin|
|
|
192
|
+
return plugin if name.to_s.start_with?("#{plugin}_")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
nil
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def load_plugin_for_constant!(name)
|
|
199
|
+
if (loader = NESTED_MODULE_LOADERS[name])
|
|
200
|
+
return load_plugin!(loader)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
if name == :OpenAPI
|
|
204
|
+
return load_plugin!(:open_api)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
constant = name.to_s
|
|
208
|
+
if constant.end_with?("_ERROR_CODES")
|
|
209
|
+
plugin_name = constant.delete_suffix("_ERROR_CODES").downcase.to_sym
|
|
210
|
+
return load_plugin!(plugin_name) if PLUGIN_FILES.key?(plugin_name)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
false
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def load_boot_plugins!
|
|
217
|
+
BOOT_PLUGINS.each { |plugin| load_plugin!(plugin) }
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
@@ -10,36 +10,33 @@ module BetterAuth
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def authorize(request, connector = "AND")
|
|
13
|
+
connector = (connector == "OR") ? "OR" : "AND"
|
|
13
14
|
success = false
|
|
14
15
|
stringify_request(request).each do |resource, requested_actions|
|
|
15
16
|
allowed_actions = statements[resource]
|
|
16
17
|
unless allowed_actions
|
|
18
|
+
next if connector == "OR"
|
|
19
|
+
|
|
17
20
|
return {success: false, error: "You are not allowed to access resource: #{resource}"}
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
success = if requested_actions.is_a?(Array)
|
|
21
|
-
requested_actions.all? { |action| allowed_actions.include?(action
|
|
24
|
+
requested_actions.any? && requested_actions.all? { |action| action.is_a?(String) && allowed_actions.include?(action) }
|
|
22
25
|
elsif requested_actions.is_a?(Hash)
|
|
23
|
-
unless requested_actions.key?("actions") || requested_actions.key?(:actions)
|
|
24
|
-
raise Error, "Invalid access control request"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
26
|
raw_actions = requested_actions["actions"] || requested_actions[:actions]
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
actions = Array(raw_actions).map(&:to_s)
|
|
31
|
-
action_connector = (requested_actions["connector"] || requested_actions[:connector] || "AND").to_s.upcase
|
|
27
|
+
actions = raw_actions.is_a?(Array) ? raw_actions : []
|
|
28
|
+
action_connector = ((requested_actions["connector"] || requested_actions[:connector]) == "OR") ? "OR" : "AND"
|
|
32
29
|
if action_connector == "OR"
|
|
33
|
-
actions.any? { |action| allowed_actions.include?(action) }
|
|
30
|
+
actions.any? { |action| action.is_a?(String) && allowed_actions.include?(action) }
|
|
34
31
|
else
|
|
35
|
-
actions.all? { |action| allowed_actions.include?(action) }
|
|
32
|
+
actions.any? && actions.all? { |action| action.is_a?(String) && allowed_actions.include?(action) }
|
|
36
33
|
end
|
|
37
34
|
else
|
|
38
35
|
raise Error, "Invalid access control request"
|
|
39
36
|
end
|
|
40
37
|
|
|
41
|
-
return {success: true} if success && connector
|
|
42
|
-
return {success: false, error: "unauthorized to access resource \"#{resource}\""} if !success && connector
|
|
38
|
+
return {success: true} if success && connector == "OR"
|
|
39
|
+
return {success: false, error: "unauthorized to access resource \"#{resource}\""} if !success && connector == "AND"
|
|
43
40
|
end
|
|
44
41
|
|
|
45
42
|
success ? {success: true} : {success: false, error: "Not authorized"}
|
|
@@ -25,15 +25,17 @@ module BetterAuth
|
|
|
25
25
|
"YOU_CANNOT_REMOVE_YOURSELF" => "You cannot remove yourself",
|
|
26
26
|
"YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE" => "You are not allowed to set a non-existent role value",
|
|
27
27
|
"YOU_CANNOT_IMPERSONATE_ADMINS" => "You cannot impersonate admins",
|
|
28
|
-
"INVALID_ROLE_TYPE" => "Invalid role type"
|
|
28
|
+
"INVALID_ROLE_TYPE" => "Invalid role type",
|
|
29
|
+
"YOU_ARE_NOT_ALLOWED_TO_SET_USERS_EMAIL" => "You are not allowed to update users email",
|
|
30
|
+
"PASSWORD_CANNOT_BE_UPDATED_VIA_UPDATE_USER" => "Password cannot be updated through update-user. Use the set-user-password endpoint instead"
|
|
29
31
|
}.freeze
|
|
30
32
|
|
|
31
33
|
ADMIN_DEFAULT_STATEMENTS = {
|
|
32
|
-
user: ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"],
|
|
34
|
+
user: ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "set-email", "get", "update"],
|
|
33
35
|
session: ["list", "revoke", "delete"]
|
|
34
36
|
}.freeze
|
|
35
37
|
ADMIN_DEFAULT_ROLE_STATEMENTS = {
|
|
36
|
-
user: ["create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "get", "update"],
|
|
38
|
+
user: ["create", "list", "set-role", "ban", "impersonate", "delete", "set-password", "set-email", "get", "update"],
|
|
37
39
|
session: ["list", "revoke", "delete"]
|
|
38
40
|
}.freeze
|
|
39
41
|
|
|
@@ -66,7 +68,11 @@ module BetterAuth
|
|
|
66
68
|
after: [
|
|
67
69
|
{
|
|
68
70
|
matcher: ->(ctx) { ctx.path == "/list-sessions" },
|
|
69
|
-
handler:
|
|
71
|
+
handler: lambda do |ctx|
|
|
72
|
+
next unless ctx.returned.is_a?(Array)
|
|
73
|
+
|
|
74
|
+
ctx.json(ctx.returned.reject { |session| session["impersonatedBy"] || session[:impersonatedBy] })
|
|
75
|
+
end
|
|
70
76
|
}
|
|
71
77
|
]
|
|
72
78
|
},
|
|
@@ -154,6 +160,8 @@ module BetterAuth
|
|
|
154
160
|
user_id = body[:user_id].to_s
|
|
155
161
|
raise APIError.new("BAD_REQUEST", message: "userId is required") if user_id.empty?
|
|
156
162
|
update = {role: admin_validate_roles!(body[:role], config)}
|
|
163
|
+
raise APIError.new("NOT_FOUND", code: "USER_NOT_FOUND", message: BASE_ERROR_CODES.fetch("USER_NOT_FOUND")) unless ctx.context.internal_adapter.find_user_by_id(user_id)
|
|
164
|
+
|
|
157
165
|
user = ctx.context.internal_adapter.update_user(user_id, update)
|
|
158
166
|
ctx.json({user: Schema.parse_output(ctx.context.options, "user", user || {})})
|
|
159
167
|
end
|
|
@@ -185,7 +193,7 @@ module BetterAuth
|
|
|
185
193
|
elsif query[:email]
|
|
186
194
|
ctx.context.internal_adapter.find_user_by_email(query[:email])&.fetch(:user)
|
|
187
195
|
end
|
|
188
|
-
raise APIError.new("NOT_FOUND", message: BASE_ERROR_CODES.fetch("USER_NOT_FOUND")) unless user
|
|
196
|
+
raise APIError.new("NOT_FOUND", code: "USER_NOT_FOUND", message: BASE_ERROR_CODES.fetch("USER_NOT_FOUND")) unless user
|
|
189
197
|
ctx.json(Schema.parse_output(ctx.context.options, "user", user))
|
|
190
198
|
end
|
|
191
199
|
end
|
|
@@ -208,10 +216,10 @@ module BetterAuth
|
|
|
208
216
|
required: ["email", "name"]
|
|
209
217
|
)
|
|
210
218
|
) do |ctx|
|
|
211
|
-
session = Routes.current_session(ctx, allow_nil: true)
|
|
219
|
+
session = Routes.current_session(ctx, allow_nil: true, sensitive: true)
|
|
212
220
|
if session
|
|
213
221
|
unless admin_permission?(session[:user], session[:user]["role"], {user: ["create"]}, config)
|
|
214
|
-
raise APIError.new("FORBIDDEN", message: ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS"))
|
|
222
|
+
raise APIError.new("FORBIDDEN", code: "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS", message: ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS"))
|
|
215
223
|
end
|
|
216
224
|
elsif !ctx.headers.empty?
|
|
217
225
|
raise APIError.new("UNAUTHORIZED")
|
|
@@ -219,18 +227,25 @@ module BetterAuth
|
|
|
219
227
|
|
|
220
228
|
body = normalize_hash(ctx.body)
|
|
221
229
|
email = body[:email].to_s.downcase
|
|
222
|
-
raise APIError.new("BAD_REQUEST", message: BASE_ERROR_CODES.fetch("INVALID_EMAIL")) unless Routes::EMAIL_PATTERN.match?(email)
|
|
230
|
+
raise APIError.new("BAD_REQUEST", code: "INVALID_EMAIL", message: BASE_ERROR_CODES.fetch("INVALID_EMAIL")) unless Routes::EMAIL_PATTERN.match?(email)
|
|
223
231
|
|
|
224
232
|
if ctx.context.internal_adapter.find_user_by_email(email)
|
|
225
|
-
raise APIError.new("BAD_REQUEST", message: ADMIN_ERROR_CODES.fetch("USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL"))
|
|
233
|
+
raise APIError.new("BAD_REQUEST", code: "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL", message: ADMIN_ERROR_CODES.fetch("USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL"))
|
|
226
234
|
end
|
|
227
235
|
data = normalize_hash(body[:data]).each_with_object({}) { |(key, value), result| result[Schema.storage_key(key)] = value }
|
|
236
|
+
requested_role = body.key?(:role) ? body[:role] : data.delete("role")
|
|
237
|
+
if !requested_role.nil? && session
|
|
238
|
+
admin_require_permission!(ctx, config, {user: ["set-role"]}, ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE"))
|
|
239
|
+
end
|
|
240
|
+
if session && ["banned", "banReason", "banExpires"].any? { |field| data.key?(field) }
|
|
241
|
+
admin_require_permission!(ctx, config, {user: ["ban"]}, ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_BAN_USERS"))
|
|
242
|
+
end
|
|
228
243
|
user = ctx.context.internal_adapter.create_user(data.merge(
|
|
229
244
|
name: body[:name].to_s,
|
|
230
245
|
email: email,
|
|
231
|
-
role:
|
|
246
|
+
role: requested_role.nil? ? config[:default_role] : admin_validate_roles!(requested_role, config)
|
|
232
247
|
).merge(body.key?(:image) ? {image: body[:image]} : {}), context: ctx)
|
|
233
|
-
raise APIError.new("INTERNAL_SERVER_ERROR", message: ADMIN_ERROR_CODES.fetch("FAILED_TO_CREATE_USER")) unless user
|
|
248
|
+
raise APIError.new("INTERNAL_SERVER_ERROR", code: "FAILED_TO_CREATE_USER", message: ADMIN_ERROR_CODES.fetch("FAILED_TO_CREATE_USER")) unless user
|
|
234
249
|
|
|
235
250
|
if body[:password].to_s != ""
|
|
236
251
|
ctx.context.internal_adapter.link_account(userId: user["id"], providerId: "credential", accountId: user["id"], password: Routes.hash_password(ctx, body[:password]))
|
|
@@ -265,12 +280,38 @@ module BetterAuth
|
|
|
265
280
|
admin_require_permission!(ctx, config, {user: ["update"]}, ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS"))
|
|
266
281
|
body = normalize_hash(ctx.body)
|
|
267
282
|
data = normalize_hash(body[:data] || body).except(:user_id, :data)
|
|
268
|
-
raise APIError.new("BAD_REQUEST", message: ADMIN_ERROR_CODES.fetch("NO_DATA_TO_UPDATE")) if data.empty?
|
|
283
|
+
raise APIError.new("BAD_REQUEST", code: "NO_DATA_TO_UPDATE", message: ADMIN_ERROR_CODES.fetch("NO_DATA_TO_UPDATE")) if data.empty?
|
|
284
|
+
if data.key?(:password)
|
|
285
|
+
raise APIError.new("BAD_REQUEST", code: "PASSWORD_CANNOT_BE_UPDATED_VIA_UPDATE_USER", message: ADMIN_ERROR_CODES.fetch("PASSWORD_CANNOT_BE_UPDATED_VIA_UPDATE_USER"))
|
|
286
|
+
end
|
|
269
287
|
if data.key?(:role)
|
|
270
288
|
admin_require_permission!(ctx, config, {user: ["set-role"]}, ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE"))
|
|
271
289
|
data[:role] = admin_validate_roles!(data[:role], config)
|
|
272
290
|
end
|
|
273
|
-
|
|
291
|
+
if [:banned, :ban_reason, :ban_expires].any? { |field| data.key?(field) }
|
|
292
|
+
session = admin_require_permission!(ctx, config, {user: ["ban"]}, ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_BAN_USERS"))
|
|
293
|
+
if data[:banned] == true && body[:user_id] == session[:user]["id"]
|
|
294
|
+
raise APIError.new("BAD_REQUEST", code: "YOU_CANNOT_BAN_YOURSELF", message: ADMIN_ERROR_CODES.fetch("YOU_CANNOT_BAN_YOURSELF"))
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
if data.key?(:email) || data.key?(:email_verified)
|
|
298
|
+
admin_require_permission!(ctx, config, {user: ["set-email"]}, ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_SET_USERS_EMAIL"))
|
|
299
|
+
if data.key?(:email)
|
|
300
|
+
email = data[:email].to_s.downcase
|
|
301
|
+
raise APIError.new("BAD_REQUEST", code: "INVALID_EMAIL", message: BASE_ERROR_CODES.fetch("INVALID_EMAIL")) unless Routes::EMAIL_PATTERN.match?(email)
|
|
302
|
+
|
|
303
|
+
existing = ctx.context.internal_adapter.find_user_by_email(email)
|
|
304
|
+
if existing && existing[:user]["id"] != body[:user_id]
|
|
305
|
+
raise APIError.new("BAD_REQUEST", code: "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL", message: ADMIN_ERROR_CODES.fetch("USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL"))
|
|
306
|
+
end
|
|
307
|
+
data[:email] = email
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
raise APIError.new("NOT_FOUND", code: "USER_NOT_FOUND", message: BASE_ERROR_CODES.fetch("USER_NOT_FOUND")) unless ctx.context.internal_adapter.find_user_by_id(body[:user_id])
|
|
311
|
+
|
|
312
|
+
stored_data = data.each_with_object({}) { |(key, value), result| result[Schema.storage_key(key)] = value }
|
|
313
|
+
user = ctx.context.internal_adapter.update_user(body[:user_id], stored_data)
|
|
314
|
+
ctx.context.internal_adapter.delete_user_sessions(body[:user_id]) if data[:banned] == true
|
|
274
315
|
ctx.json(Schema.parse_output(ctx.context.options, "user", user))
|
|
275
316
|
end
|
|
276
317
|
end
|
|
@@ -353,11 +394,11 @@ module BetterAuth
|
|
|
353
394
|
session = admin_require_permission!(ctx, config, {user: ["ban"]}, ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_BAN_USERS"))
|
|
354
395
|
body = normalize_hash(ctx.body)
|
|
355
396
|
found = ctx.context.internal_adapter.find_user_by_id(body[:user_id])
|
|
356
|
-
raise APIError.new("NOT_FOUND", message: BASE_ERROR_CODES.fetch("USER_NOT_FOUND")) unless found
|
|
357
|
-
raise APIError.new("BAD_REQUEST", message: ADMIN_ERROR_CODES.fetch("YOU_CANNOT_BAN_YOURSELF")) if body[:user_id] == session[:user]["id"]
|
|
397
|
+
raise APIError.new("NOT_FOUND", code: "USER_NOT_FOUND", message: BASE_ERROR_CODES.fetch("USER_NOT_FOUND")) unless found
|
|
398
|
+
raise APIError.new("BAD_REQUEST", code: "YOU_CANNOT_BAN_YOURSELF", message: ADMIN_ERROR_CODES.fetch("YOU_CANNOT_BAN_YOURSELF")) if body[:user_id] == session[:user]["id"]
|
|
358
399
|
expires_in = body[:ban_expires_in] || config[:default_ban_expires_in]
|
|
359
400
|
user = ctx.context.internal_adapter.update_user(body[:user_id], banned: true, banReason: body[:ban_reason] || config[:default_ban_reason] || "No reason", banExpires: expires_in ? Time.now + expires_in.to_i : nil, updatedAt: Time.now)
|
|
360
|
-
ctx.context.internal_adapter.
|
|
401
|
+
ctx.context.internal_adapter.delete_user_sessions(body[:user_id])
|
|
361
402
|
ctx.json({user: Schema.parse_output(ctx.context.options, "user", user)})
|
|
362
403
|
end
|
|
363
404
|
end
|
|
@@ -377,7 +418,10 @@ module BetterAuth
|
|
|
377
418
|
)
|
|
378
419
|
) do |ctx|
|
|
379
420
|
admin_require_permission!(ctx, config, {user: ["ban"]}, ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_BAN_USERS"))
|
|
380
|
-
|
|
421
|
+
user_id = normalize_hash(ctx.body)[:user_id]
|
|
422
|
+
raise APIError.new("NOT_FOUND", code: "USER_NOT_FOUND", message: BASE_ERROR_CODES.fetch("USER_NOT_FOUND")) unless ctx.context.internal_adapter.find_user_by_id(user_id)
|
|
423
|
+
|
|
424
|
+
user = ctx.context.internal_adapter.update_user(user_id, banned: false, banReason: nil, banExpires: nil, updatedAt: Time.now)
|
|
381
425
|
ctx.json({user: Schema.parse_output(ctx.context.options, "user", user)})
|
|
382
426
|
end
|
|
383
427
|
end
|
|
@@ -407,11 +451,11 @@ module BetterAuth
|
|
|
407
451
|
session = admin_require_permission!(ctx, config, {user: ["impersonate"]}, ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS"))
|
|
408
452
|
body = normalize_hash(ctx.body)
|
|
409
453
|
target = ctx.context.internal_adapter.find_user_by_id(body[:user_id])
|
|
410
|
-
raise APIError.new("NOT_FOUND",
|
|
454
|
+
raise APIError.new("NOT_FOUND", code: "USER_NOT_FOUND", message: BASE_ERROR_CODES.fetch("USER_NOT_FOUND")) unless target
|
|
411
455
|
can_impersonate_admins = config[:allow_impersonating_admins] ||
|
|
412
456
|
admin_permission?(session[:user], session[:user]["role"], {user: ["impersonate-admins"]}, config)
|
|
413
457
|
if !can_impersonate_admins && admin_user?(target, config)
|
|
414
|
-
raise APIError.new("FORBIDDEN", message: ADMIN_ERROR_CODES.fetch("YOU_CANNOT_IMPERSONATE_ADMINS"))
|
|
458
|
+
raise APIError.new("FORBIDDEN", code: "YOU_CANNOT_IMPERSONATE_ADMINS", message: ADMIN_ERROR_CODES.fetch("YOU_CANNOT_IMPERSONATE_ADMINS"))
|
|
415
459
|
end
|
|
416
460
|
impersonated = ctx.context.internal_adapter.create_session(target["id"], true, {impersonatedBy: session[:user]["id"], expiresAt: Time.now + config[:impersonation_session_duration].to_i}, true, ctx)
|
|
417
461
|
raise APIError.new("INTERNAL_SERVER_ERROR", message: ADMIN_ERROR_CODES.fetch("FAILED_TO_CREATE_USER")) unless impersonated
|
|
@@ -436,6 +480,7 @@ module BetterAuth
|
|
|
436
480
|
openapi: {
|
|
437
481
|
operationId: "stopImpersonating",
|
|
438
482
|
description: "Stop impersonating a user",
|
|
483
|
+
requestBody: OpenAPI.empty_request_body,
|
|
439
484
|
responses: {
|
|
440
485
|
"200" => OpenAPI.json_response("Impersonation stopped", OpenAPI.session_response_schema_pair)
|
|
441
486
|
}
|
|
@@ -519,7 +564,7 @@ module BetterAuth
|
|
|
519
564
|
}
|
|
520
565
|
) do |ctx|
|
|
521
566
|
admin_require_permission!(ctx, config, {session: ["revoke"]}, ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS"))
|
|
522
|
-
ctx.context.internal_adapter.
|
|
567
|
+
ctx.context.internal_adapter.delete_user_sessions(normalize_hash(ctx.body)[:user_id])
|
|
523
568
|
ctx.json({success: true})
|
|
524
569
|
end
|
|
525
570
|
end
|
|
@@ -548,8 +593,8 @@ module BetterAuth
|
|
|
548
593
|
) do |ctx|
|
|
549
594
|
session = admin_require_permission!(ctx, config, {user: ["delete"]}, ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS"))
|
|
550
595
|
user_id = normalize_hash(ctx.body)[:user_id]
|
|
551
|
-
raise APIError.new("BAD_REQUEST", message: ADMIN_ERROR_CODES.fetch("YOU_CANNOT_REMOVE_YOURSELF")) if user_id == session[:user]["id"]
|
|
552
|
-
raise APIError.new("NOT_FOUND", message: BASE_ERROR_CODES.fetch("USER_NOT_FOUND")) unless ctx.context.internal_adapter.find_user_by_id(user_id)
|
|
596
|
+
raise APIError.new("BAD_REQUEST", code: "YOU_CANNOT_REMOVE_YOURSELF", message: ADMIN_ERROR_CODES.fetch("YOU_CANNOT_REMOVE_YOURSELF")) if user_id == session[:user]["id"]
|
|
597
|
+
raise APIError.new("NOT_FOUND", code: "USER_NOT_FOUND", message: BASE_ERROR_CODES.fetch("USER_NOT_FOUND")) unless ctx.context.internal_adapter.find_user_by_id(user_id)
|
|
553
598
|
ctx.context.internal_adapter.delete_user(user_id)
|
|
554
599
|
ctx.json({success: true})
|
|
555
600
|
end
|
|
@@ -585,9 +630,17 @@ module BetterAuth
|
|
|
585
630
|
raise APIError.new("BAD_REQUEST", message: "userId is required") if user_id.empty?
|
|
586
631
|
min = ctx.context.options.email_and_password[:min_password_length]
|
|
587
632
|
max = ctx.context.options.email_and_password[:max_password_length]
|
|
588
|
-
raise APIError.new("BAD_REQUEST", message: BASE_ERROR_CODES.fetch("PASSWORD_TOO_SHORT")) if password.length < min
|
|
589
|
-
raise APIError.new("BAD_REQUEST", message: BASE_ERROR_CODES.fetch("PASSWORD_TOO_LONG")) if password.length > max
|
|
590
|
-
ctx.context.internal_adapter.
|
|
633
|
+
raise APIError.new("BAD_REQUEST", code: "PASSWORD_TOO_SHORT", message: BASE_ERROR_CODES.fetch("PASSWORD_TOO_SHORT")) if password.length < min
|
|
634
|
+
raise APIError.new("BAD_REQUEST", code: "PASSWORD_TOO_LONG", message: BASE_ERROR_CODES.fetch("PASSWORD_TOO_LONG")) if password.length > max
|
|
635
|
+
raise APIError.new("NOT_FOUND", code: "USER_NOT_FOUND", message: BASE_ERROR_CODES.fetch("USER_NOT_FOUND")) unless ctx.context.internal_adapter.find_user_by_id(user_id)
|
|
636
|
+
|
|
637
|
+
hashed_password = Routes.hash_password(ctx, password)
|
|
638
|
+
credential_account = ctx.context.internal_adapter.find_accounts(user_id).find { |account| account["providerId"] == "credential" }
|
|
639
|
+
if credential_account
|
|
640
|
+
ctx.context.internal_adapter.update_password(user_id, hashed_password)
|
|
641
|
+
else
|
|
642
|
+
ctx.context.internal_adapter.create_account(userId: user_id, providerId: "credential", accountId: user_id, password: hashed_password)
|
|
643
|
+
end
|
|
591
644
|
ctx.json({status: true})
|
|
592
645
|
end
|
|
593
646
|
end
|
|
@@ -626,7 +679,7 @@ module BetterAuth
|
|
|
626
679
|
}
|
|
627
680
|
}
|
|
628
681
|
) do |ctx|
|
|
629
|
-
session = Routes.current_session(ctx, allow_nil: true)
|
|
682
|
+
session = Routes.current_session(ctx, allow_nil: true, sensitive: true)
|
|
630
683
|
body = normalize_hash(ctx.body)
|
|
631
684
|
permissions = body[:permissions] || body[:permission]
|
|
632
685
|
unless permissions
|
|
@@ -718,7 +771,10 @@ module BetterAuth
|
|
|
718
771
|
session = Routes.current_session(ctx, sensitive: true)
|
|
719
772
|
return session if admin_permission?(session[:user], session[:user]["role"], permissions, config)
|
|
720
773
|
|
|
721
|
-
|
|
774
|
+
code = ADMIN_ERROR_CODES.key(message)
|
|
775
|
+
raise Error, "Unknown admin permission error: #{message}" unless code
|
|
776
|
+
|
|
777
|
+
raise APIError.new("FORBIDDEN", code: code, message: message)
|
|
722
778
|
end
|
|
723
779
|
|
|
724
780
|
def admin_permission?(user, role_string, permissions, config)
|
|
@@ -745,7 +801,7 @@ module BetterAuth
|
|
|
745
801
|
|
|
746
802
|
def admin_validate_roles!(roles, config)
|
|
747
803
|
unless Array(roles).all? { |role| role.is_a?(String) || role.is_a?(Symbol) }
|
|
748
|
-
raise APIError.new("BAD_REQUEST", message: ADMIN_ERROR_CODES.fetch("INVALID_ROLE_TYPE"))
|
|
804
|
+
raise APIError.new("BAD_REQUEST", code: "INVALID_ROLE_TYPE", message: ADMIN_ERROR_CODES.fetch("INVALID_ROLE_TYPE"))
|
|
749
805
|
end
|
|
750
806
|
|
|
751
807
|
parsed = admin_parse_roles(roles)
|
|
@@ -753,7 +809,7 @@ module BetterAuth
|
|
|
753
809
|
defined_roles = (config[:roles] || {}).transform_keys(&:to_s)
|
|
754
810
|
invalid = parsed.split(",", -1).reject { |role| admin_role_for(defined_roles, role) }
|
|
755
811
|
if invalid.any?
|
|
756
|
-
raise APIError.new("BAD_REQUEST", message: ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE"))
|
|
812
|
+
raise APIError.new("BAD_REQUEST", code: "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE", message: ADMIN_ERROR_CODES.fetch("YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE"))
|
|
757
813
|
end
|
|
758
814
|
end
|
|
759
815
|
|
|
@@ -47,6 +47,7 @@ module BetterAuth
|
|
|
47
47
|
openapi: {
|
|
48
48
|
operationId: "signInAnonymous",
|
|
49
49
|
description: "Sign in anonymously",
|
|
50
|
+
requestBody: OpenAPI.empty_request_body,
|
|
50
51
|
responses: {
|
|
51
52
|
"200" => OpenAPI.json_response(
|
|
52
53
|
"Anonymous session created",
|
|
@@ -96,6 +97,7 @@ module BetterAuth
|
|
|
96
97
|
openapi: {
|
|
97
98
|
operationId: "deleteAnonymousUser",
|
|
98
99
|
description: "Delete the current anonymous user",
|
|
100
|
+
requestBody: OpenAPI.empty_request_body,
|
|
99
101
|
responses: {
|
|
100
102
|
"200" => OpenAPI.json_response("Anonymous user deleted", OpenAPI.success_response_schema)
|
|
101
103
|
}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
return if defined?(BetterAuth::Plugins::API_KEY_PLUGIN_IMPLEMENTATION)
|
|
4
|
+
|
|
3
5
|
module BetterAuth
|
|
4
6
|
module Plugins
|
|
5
7
|
module_function
|
|
6
8
|
|
|
7
|
-
def api_key(*args)
|
|
8
|
-
|
|
9
|
-
BetterAuth::Plugins.api_key(*args)
|
|
10
|
-
rescue LoadError => error
|
|
11
|
-
raise if error.path && error.path != "better_auth/api_key"
|
|
12
|
-
|
|
13
|
-
raise LoadError, "BetterAuth::Plugins.api_key requires the better_auth-api-key gem. Add `gem \"better_auth-api-key\"` and `require \"better_auth/api_key\"`."
|
|
9
|
+
def api_key(*args, &block)
|
|
10
|
+
call_external_plugin!(:api_key, *args, implementation_constant: :API_KEY_PLUGIN_IMPLEMENTATION, gem_name: "better_auth-api-key", entry: "lib/better_auth/api_key.rb", &block)
|
|
14
11
|
end
|
|
15
12
|
end
|
|
16
13
|
end
|
|
@@ -50,7 +50,7 @@ module BetterAuth
|
|
|
50
50
|
return unless signed_token && valid_signed_token?(ctx, signed_token)
|
|
51
51
|
|
|
52
52
|
cookie_name = ctx.context.auth_cookies[:session_token].name
|
|
53
|
-
cookie =
|
|
53
|
+
cookie = Cookies.set_request_cookie(ctx.headers["cookie"], cookie_name, signed_token)
|
|
54
54
|
{context: {headers: ctx.headers.merge("cookie" => cookie)}}
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -86,37 +86,23 @@ module BetterAuth
|
|
|
86
86
|
token.to_s
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
def bearer_session_cookie(line)
|
|
90
|
-
first, *attributes = line.to_s.split(";").map(&:strip)
|
|
91
|
-
name, value = first.split("=", 2)
|
|
92
|
-
return unless name && value
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
name: name,
|
|
96
|
-
value: value,
|
|
97
|
-
attributes: attributes.each_with_object({}) do |attribute, result|
|
|
98
|
-
key, attribute_value = attribute.split("=", 2)
|
|
99
|
-
result[key.to_s.downcase] = attribute_value || true unless key.to_s.empty?
|
|
100
|
-
end
|
|
101
|
-
}
|
|
102
|
-
end
|
|
103
|
-
|
|
104
89
|
def expired_bearer_cookie?(cookie)
|
|
105
90
|
max_age = cookie[:attributes]["max-age"]
|
|
106
91
|
max_age.to_s.strip.match?(/\A[+-]?\d+\z/) && max_age.to_i == 0
|
|
107
92
|
end
|
|
108
93
|
|
|
109
94
|
def expose_auth_token(ctx)
|
|
110
|
-
set_cookie = ctx.response_headers["set-cookie"].to_s
|
|
111
95
|
token_name = ctx.context.auth_cookies[:session_token].name
|
|
112
|
-
|
|
113
|
-
cookie =
|
|
96
|
+
session_cookie = Cookies.split_set_cookie_header(ctx.response_headers["set-cookie"]).reverse_each.filter_map do |line|
|
|
97
|
+
cookie = Cookies.parse_set_cookie(line)
|
|
114
98
|
next unless cookie && cookie[:name] == token_name
|
|
115
|
-
next if cookie[:value].empty? || expired_bearer_cookie?(cookie)
|
|
116
99
|
|
|
117
|
-
cookie
|
|
100
|
+
cookie
|
|
118
101
|
end.first
|
|
119
|
-
return unless
|
|
102
|
+
return unless session_cookie
|
|
103
|
+
return if session_cookie[:value].empty? || expired_bearer_cookie?(session_cookie)
|
|
104
|
+
|
|
105
|
+
token = session_cookie[:value]
|
|
120
106
|
|
|
121
107
|
exposed = ctx.response_headers["access-control-expose-headers"].to_s.split(",").map(&:strip).reject(&:empty?)
|
|
122
108
|
exposed << "set-auth-token"
|