better_auth-api-key 0.2.1 → 0.6.2
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/lib/better_auth/api_key/adapter.rb +245 -0
- data/lib/better_auth/api_key/configuration.rb +78 -0
- data/lib/better_auth/api_key/error_codes.rb +39 -0
- data/lib/better_auth/api_key/keys.rb +55 -0
- data/lib/better_auth/api_key/org_authorization.rb +80 -0
- data/lib/better_auth/api_key/plugin_factory.rb +37 -0
- data/lib/better_auth/api_key/rate_limit.rb +41 -0
- data/lib/better_auth/api_key/routes/create_api_key.rb +53 -0
- data/lib/better_auth/api_key/routes/delete_all_expired_api_keys.rb +23 -0
- data/lib/better_auth/api_key/routes/delete_api_key.rb +35 -0
- data/lib/better_auth/api_key/routes/get_api_key.rb +33 -0
- data/lib/better_auth/api_key/routes/index.rb +72 -0
- data/lib/better_auth/api_key/routes/list_api_keys.rb +44 -0
- data/lib/better_auth/api_key/routes/update_api_key.rb +45 -0
- data/lib/better_auth/api_key/routes/verify_api_key.rb +43 -0
- data/lib/better_auth/api_key/schema.rb +40 -0
- data/lib/better_auth/api_key/session.rb +63 -0
- data/lib/better_auth/api_key/types.rb +30 -0
- data/lib/better_auth/api_key/utils.rb +93 -0
- data/lib/better_auth/api_key/validation.rb +137 -0
- data/lib/better_auth/api_key/version.rb +1 -1
- data/lib/better_auth/api_key.rb +20 -0
- data/lib/better_auth/plugins/api_key.rb +91 -824
- metadata +21 -1
|
@@ -3,765 +3,204 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "securerandom"
|
|
5
5
|
require "time"
|
|
6
|
+
require_relative "../api_key/error_codes"
|
|
7
|
+
require_relative "../api_key/types"
|
|
8
|
+
require_relative "../api_key/utils"
|
|
9
|
+
require_relative "../api_key/rate_limit"
|
|
10
|
+
require_relative "../api_key/keys"
|
|
11
|
+
require_relative "../api_key/adapter"
|
|
12
|
+
require_relative "../api_key/schema"
|
|
13
|
+
require_relative "../api_key/org_authorization"
|
|
14
|
+
require_relative "../api_key/validation"
|
|
15
|
+
require_relative "../api_key/configuration"
|
|
16
|
+
require_relative "../api_key/session"
|
|
17
|
+
require_relative "../api_key/plugin_factory"
|
|
18
|
+
require_relative "../api_key/routes/index"
|
|
19
|
+
require_relative "../api_key/routes/create_api_key"
|
|
20
|
+
require_relative "../api_key/routes/verify_api_key"
|
|
21
|
+
require_relative "../api_key/routes/get_api_key"
|
|
22
|
+
require_relative "../api_key/routes/update_api_key"
|
|
23
|
+
require_relative "../api_key/routes/delete_api_key"
|
|
24
|
+
require_relative "../api_key/routes/list_api_keys"
|
|
25
|
+
require_relative "../api_key/routes/delete_all_expired_api_keys"
|
|
6
26
|
|
|
7
27
|
module BetterAuth
|
|
8
28
|
module Plugins
|
|
9
29
|
singleton_class.remove_method(:api_key) if singleton_class.method_defined?(:api_key)
|
|
10
30
|
remove_method(:api_key) if method_defined?(:api_key) || private_method_defined?(:api_key)
|
|
11
31
|
|
|
12
|
-
API_KEY_ERROR_CODES =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"REFILL_INTERVAL_AND_AMOUNT_REQUIRED" => "refillInterval is required when refillAmount is provided",
|
|
16
|
-
"USER_BANNED" => "User is banned",
|
|
17
|
-
"UNAUTHORIZED_SESSION" => "Unauthorized or invalid session",
|
|
18
|
-
"KEY_NOT_FOUND" => "API Key not found",
|
|
19
|
-
"KEY_DISABLED" => "API Key is disabled",
|
|
20
|
-
"KEY_EXPIRED" => "API Key has expired",
|
|
21
|
-
"USAGE_EXCEEDED" => "API Key has reached its usage limit",
|
|
22
|
-
"KEY_NOT_RECOVERABLE" => "API Key is not recoverable",
|
|
23
|
-
"EXPIRES_IN_IS_TOO_SMALL" => "The expiresIn is smaller than the predefined minimum value.",
|
|
24
|
-
"EXPIRES_IN_IS_TOO_LARGE" => "The expiresIn is larger than the predefined maximum value.",
|
|
25
|
-
"INVALID_REMAINING" => "The remaining count is either too large or too small.",
|
|
26
|
-
"INVALID_PREFIX_LENGTH" => "The prefix length is either too large or too small.",
|
|
27
|
-
"INVALID_NAME_LENGTH" => "The name length is either too large or too small.",
|
|
28
|
-
"METADATA_DISABLED" => "Metadata is disabled.",
|
|
29
|
-
"RATE_LIMIT_EXCEEDED" => "Rate limit exceeded.",
|
|
30
|
-
"NO_VALUES_TO_UPDATE" => "No values to update.",
|
|
31
|
-
"KEY_DISABLED_EXPIRATION" => "Custom key expiration values are disabled.",
|
|
32
|
-
"INVALID_API_KEY" => "Invalid API key.",
|
|
33
|
-
"INVALID_USER_ID_FROM_API_KEY" => "The user id from the API key is invalid.",
|
|
34
|
-
"INVALID_REFERENCE_ID_FROM_API_KEY" => "The reference id from the API key is invalid.",
|
|
35
|
-
"INVALID_API_KEY_GETTER_RETURN_TYPE" => "API Key getter returned an invalid key type. Expected string.",
|
|
36
|
-
"SERVER_ONLY_PROPERTY" => "The property you're trying to set can only be set from the server auth instance only.",
|
|
37
|
-
"FAILED_TO_UPDATE_API_KEY" => "Failed to update API key",
|
|
38
|
-
"NAME_REQUIRED" => "API Key name is required.",
|
|
39
|
-
"ORGANIZATION_ID_REQUIRED" => "Organization ID is required for organization-owned API keys.",
|
|
40
|
-
"USER_NOT_MEMBER_OF_ORGANIZATION" => "You are not a member of the organization that owns this API key.",
|
|
41
|
-
"INSUFFICIENT_API_KEY_PERMISSIONS" => "You do not have permission to perform this action on organization API keys.",
|
|
42
|
-
"NO_DEFAULT_API_KEY_CONFIGURATION_FOUND" => "No default api-key configuration found.",
|
|
43
|
-
"ORGANIZATION_PLUGIN_REQUIRED" => "Organization plugin is required for organization-owned API keys. Please install and configure the organization plugin."
|
|
44
|
-
}.freeze
|
|
45
|
-
|
|
46
|
-
API_KEY_TABLE_NAME = "apikey"
|
|
32
|
+
API_KEY_ERROR_CODES = BetterAuth::APIKey::ERROR_CODES
|
|
33
|
+
|
|
34
|
+
API_KEY_TABLE_NAME = BetterAuth::APIKey::Types::API_KEY_TABLE_NAME
|
|
47
35
|
|
|
48
36
|
module_function
|
|
49
37
|
|
|
50
38
|
def default_api_key_hasher(key)
|
|
51
|
-
|
|
39
|
+
BetterAuth::APIKey::Keys.default_hasher(key)
|
|
52
40
|
end
|
|
53
41
|
|
|
54
42
|
def api_key(configurations = {}, options = nil)
|
|
55
|
-
|
|
56
|
-
Plugin.new(
|
|
57
|
-
id: "api-key",
|
|
58
|
-
version: BetterAuth::APIKey::VERSION,
|
|
59
|
-
hooks: {
|
|
60
|
-
before: [
|
|
61
|
-
{
|
|
62
|
-
matcher: ->(ctx) { !!api_key_session_header_config(ctx, config) },
|
|
63
|
-
handler: ->(ctx) { api_key_session_hook(ctx, config) }
|
|
64
|
-
}
|
|
65
|
-
]
|
|
66
|
-
},
|
|
67
|
-
endpoints: {
|
|
68
|
-
create_api_key: api_key_create_endpoint(config),
|
|
69
|
-
verify_api_key: api_key_verify_endpoint(config),
|
|
70
|
-
get_api_key: api_key_get_endpoint(config),
|
|
71
|
-
update_api_key: api_key_update_endpoint(config),
|
|
72
|
-
delete_api_key: api_key_delete_endpoint(config),
|
|
73
|
-
list_api_keys: api_key_list_endpoint(config),
|
|
74
|
-
delete_all_expired_api_keys: api_key_delete_expired_endpoint(config)
|
|
75
|
-
},
|
|
76
|
-
schema: api_key_schema(config, config[:schema]),
|
|
77
|
-
error_codes: API_KEY_ERROR_CODES,
|
|
78
|
-
options: config
|
|
79
|
-
)
|
|
43
|
+
BetterAuth::APIKey::PluginFactory.build(configurations, options)
|
|
80
44
|
end
|
|
81
45
|
|
|
82
46
|
def api_key_config(configurations, options = nil)
|
|
83
|
-
|
|
84
|
-
normalized_configs = configurations.map { |config| api_key_single_config(config) }
|
|
85
|
-
if normalized_configs.any? { |config| config[:config_id].to_s.empty? }
|
|
86
|
-
raise Error, "configId is required for each API key configuration in the api-key plugin."
|
|
87
|
-
end
|
|
88
|
-
config_ids = normalized_configs.map { |config| config[:config_id] }
|
|
89
|
-
raise Error, "configId must be unique for each API key configuration in the api-key plugin." if config_ids.uniq.length != config_ids.length
|
|
90
|
-
|
|
91
|
-
plugin_options = normalize_hash(options || {})
|
|
92
|
-
default_config = normalized_configs.find { |config| api_key_default_config_id?(config[:config_id]) }
|
|
93
|
-
default_config ||= normalized_configs.first
|
|
94
|
-
default_config.merge(
|
|
95
|
-
configurations: normalized_configs,
|
|
96
|
-
schema: plugin_options[:schema] || default_config[:schema]
|
|
97
|
-
)
|
|
98
|
-
else
|
|
99
|
-
config = api_key_single_config(configurations)
|
|
100
|
-
config[:config_id] ||= "default"
|
|
101
|
-
config.merge(configurations: [config])
|
|
102
|
-
end
|
|
47
|
+
BetterAuth::APIKey::Configuration.normalize(configurations, options)
|
|
103
48
|
end
|
|
104
49
|
|
|
105
50
|
def api_key_single_config(options)
|
|
106
|
-
|
|
107
|
-
rate_limit_options = data[:rate_limit] || {}
|
|
108
|
-
starting_characters_options = data[:starting_characters_config] || {}
|
|
109
|
-
{
|
|
110
|
-
config_id: data[:config_id],
|
|
111
|
-
api_key_headers: data[:api_key_headers] || "x-api-key",
|
|
112
|
-
default_key_length: data[:default_key_length] || 64,
|
|
113
|
-
default_prefix: data[:default_prefix],
|
|
114
|
-
maximum_prefix_length: data.key?(:maximum_prefix_length) ? data[:maximum_prefix_length] : 32,
|
|
115
|
-
minimum_prefix_length: data.key?(:minimum_prefix_length) ? data[:minimum_prefix_length] : 1,
|
|
116
|
-
maximum_name_length: data.key?(:maximum_name_length) ? data[:maximum_name_length] : 32,
|
|
117
|
-
minimum_name_length: data.key?(:minimum_name_length) ? data[:minimum_name_length] : 1,
|
|
118
|
-
enable_metadata: data[:enable_metadata] || false,
|
|
119
|
-
disable_key_hashing: data[:disable_key_hashing] || false,
|
|
120
|
-
require_name: data[:require_name] || false,
|
|
121
|
-
storage: data[:storage] || "database",
|
|
122
|
-
rate_limit: {
|
|
123
|
-
enabled: rate_limit_options.fetch(:enabled, true),
|
|
124
|
-
time_window: rate_limit_options[:time_window] || 86_400_000,
|
|
125
|
-
max_requests: rate_limit_options[:max_requests] || 10
|
|
126
|
-
},
|
|
127
|
-
key_expiration: {
|
|
128
|
-
default_expires_in: data.dig(:key_expiration, :default_expires_in),
|
|
129
|
-
disable_custom_expires_time: data.dig(:key_expiration, :disable_custom_expires_time) || false,
|
|
130
|
-
max_expires_in: data.dig(:key_expiration, :max_expires_in) || 365,
|
|
131
|
-
min_expires_in: data.dig(:key_expiration, :min_expires_in) || 1
|
|
132
|
-
},
|
|
133
|
-
starting_characters_config: {
|
|
134
|
-
should_store: starting_characters_options.fetch(:should_store, true),
|
|
135
|
-
characters_length: starting_characters_options[:characters_length] || 6
|
|
136
|
-
},
|
|
137
|
-
enable_session_for_api_keys: data[:enable_session_for_api_keys] || false,
|
|
138
|
-
fallback_to_database: data[:fallback_to_database] || false,
|
|
139
|
-
custom_storage: data[:custom_storage],
|
|
140
|
-
custom_key_generator: data[:custom_key_generator],
|
|
141
|
-
custom_api_key_getter: data[:custom_api_key_getter],
|
|
142
|
-
custom_api_key_validator: data[:custom_api_key_validator],
|
|
143
|
-
default_permissions: data[:default_permissions],
|
|
144
|
-
permissions: data[:permissions] || {},
|
|
145
|
-
references: data[:references] || "user",
|
|
146
|
-
defer_updates: data[:defer_updates] || false,
|
|
147
|
-
schema: data[:schema]
|
|
148
|
-
}
|
|
51
|
+
BetterAuth::APIKey::Configuration.single(options)
|
|
149
52
|
end
|
|
150
53
|
|
|
151
54
|
def api_key_schema(config, custom_schema = nil)
|
|
152
|
-
|
|
153
|
-
apikey: {
|
|
154
|
-
fields: {
|
|
155
|
-
configId: {type: "string", required: true, default_value: "default", index: true},
|
|
156
|
-
name: {type: "string", required: false},
|
|
157
|
-
start: {type: "string", required: false},
|
|
158
|
-
prefix: {type: "string", required: false},
|
|
159
|
-
key: {type: "string", required: true, index: true},
|
|
160
|
-
referenceId: {type: "string", required: true, index: true},
|
|
161
|
-
refillInterval: {type: "number", required: false},
|
|
162
|
-
refillAmount: {type: "number", required: false},
|
|
163
|
-
lastRefillAt: {type: "date", required: false},
|
|
164
|
-
enabled: {type: "boolean", required: false, default_value: true},
|
|
165
|
-
rateLimitEnabled: {type: "boolean", required: false, default_value: true},
|
|
166
|
-
rateLimitTimeWindow: {type: "number", required: false, default_value: config[:rate_limit][:time_window]},
|
|
167
|
-
rateLimitMax: {type: "number", required: false, default_value: config[:rate_limit][:max_requests]},
|
|
168
|
-
requestCount: {type: "number", required: false, default_value: 0},
|
|
169
|
-
remaining: {type: "number", required: false},
|
|
170
|
-
lastRequest: {type: "date", required: false},
|
|
171
|
-
expiresAt: {type: "date", required: false},
|
|
172
|
-
createdAt: {type: "date", required: true},
|
|
173
|
-
updatedAt: {type: "date", required: true},
|
|
174
|
-
permissions: {type: "string", required: false},
|
|
175
|
-
metadata: {type: "string", required: false}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
deep_merge_hashes(base, normalize_hash(custom_schema || {}))
|
|
55
|
+
BetterAuth::APIKey::SchemaDefinition.schema(config, custom_schema)
|
|
180
56
|
end
|
|
181
57
|
|
|
182
58
|
def api_key_create_endpoint(config)
|
|
183
|
-
|
|
184
|
-
body = api_key_normalize_body(ctx.body)
|
|
185
|
-
resolved_config = api_key_resolve_config(ctx.context, config, body[:config_id])
|
|
186
|
-
session = Routes.current_session(ctx, allow_nil: true)
|
|
187
|
-
reference_id = api_key_create_reference_id!(ctx, body, session, resolved_config)
|
|
188
|
-
|
|
189
|
-
api_key_validate_create_update!(body, resolved_config, create: true, client: !ctx.headers.empty?)
|
|
190
|
-
key_prefix = body.key?(:prefix) ? body[:prefix] : resolved_config[:default_prefix]
|
|
191
|
-
key = api_key_generate_key(resolved_config, key_prefix)
|
|
192
|
-
now = Time.now
|
|
193
|
-
hashed = api_key_hash(key, resolved_config)
|
|
194
|
-
data = {
|
|
195
|
-
configId: resolved_config[:config_id] || "default",
|
|
196
|
-
name: body[:name],
|
|
197
|
-
start: resolved_config[:starting_characters_config][:should_store] ? key[0, resolved_config[:starting_characters_config][:characters_length].to_i] : nil,
|
|
198
|
-
prefix: key_prefix,
|
|
199
|
-
key: hashed,
|
|
200
|
-
referenceId: reference_id,
|
|
201
|
-
enabled: true,
|
|
202
|
-
rateLimitEnabled: body.key?(:rate_limit_enabled) ? body[:rate_limit_enabled] : resolved_config[:rate_limit][:enabled],
|
|
203
|
-
rateLimitTimeWindow: body[:rate_limit_time_window] || resolved_config[:rate_limit][:time_window],
|
|
204
|
-
rateLimitMax: body[:rate_limit_max] || resolved_config[:rate_limit][:max_requests],
|
|
205
|
-
requestCount: 0,
|
|
206
|
-
remaining: body.key?(:remaining) ? body[:remaining] : nil,
|
|
207
|
-
refillAmount: body[:refill_amount],
|
|
208
|
-
refillInterval: body[:refill_interval],
|
|
209
|
-
lastRefillAt: nil,
|
|
210
|
-
expiresAt: api_key_expires_at(body, resolved_config),
|
|
211
|
-
createdAt: now,
|
|
212
|
-
updatedAt: now,
|
|
213
|
-
permissions: api_key_encode_json(body[:permissions] || api_key_default_permissions(resolved_config, reference_id, ctx)),
|
|
214
|
-
metadata: body.key?(:metadata) ? api_key_encode_json(body[:metadata]) : nil
|
|
215
|
-
}
|
|
216
|
-
record = api_key_store(ctx, data, resolved_config)
|
|
217
|
-
api_key_public(record, reveal_key: key, include_key_field: true)
|
|
218
|
-
end
|
|
59
|
+
BetterAuth::APIKey::Routes::CreateAPIKey.endpoint(config)
|
|
219
60
|
end
|
|
220
61
|
|
|
221
62
|
def api_key_verify_endpoint(config)
|
|
222
|
-
|
|
223
|
-
body = normalize_hash(ctx.body)
|
|
224
|
-
resolved_config = api_key_resolve_config(ctx.context, config, body[:config_id])
|
|
225
|
-
key = body[:key]
|
|
226
|
-
raise APIError.new("FORBIDDEN", message: API_KEY_ERROR_CODES["INVALID_API_KEY"], code: "INVALID_API_KEY") if key.to_s.empty?
|
|
227
|
-
|
|
228
|
-
if resolved_config[:custom_api_key_validator].respond_to?(:call) && !resolved_config[:custom_api_key_validator].call({ctx: ctx, key: key})
|
|
229
|
-
ctx.json({valid: false, error: {message: API_KEY_ERROR_CODES["INVALID_API_KEY"], code: "KEY_NOT_FOUND"}, key: nil})
|
|
230
|
-
else
|
|
231
|
-
record = api_key_validate!(ctx, key, resolved_config, permissions: body[:permissions])
|
|
232
|
-
record_config = api_key_resolve_config(ctx.context, config, api_key_record_config_id(record))
|
|
233
|
-
api_key_schedule_cleanup(ctx, record_config)
|
|
234
|
-
ctx.json({valid: true, error: nil, key: api_key_public(record, include_key_field: false)})
|
|
235
|
-
end
|
|
236
|
-
rescue APIError => error
|
|
237
|
-
ctx.context.logger.error("Failed to validate API key: #{error.message}") if ctx.context.logger.respond_to?(:error)
|
|
238
|
-
ctx.json({valid: false, error: api_key_error_payload(error), key: nil})
|
|
239
|
-
rescue => error
|
|
240
|
-
ctx.context.logger.error("Failed to validate API key: #{error.message}") if ctx.context.logger.respond_to?(:error)
|
|
241
|
-
ctx.json({valid: false, error: {message: API_KEY_ERROR_CODES["INVALID_API_KEY"], code: "INVALID_API_KEY"}, key: nil})
|
|
242
|
-
end
|
|
63
|
+
BetterAuth::APIKey::Routes::VerifyAPIKey.endpoint(config)
|
|
243
64
|
end
|
|
244
65
|
|
|
245
66
|
def api_key_get_endpoint(config)
|
|
246
|
-
|
|
247
|
-
session = Routes.current_session(ctx)
|
|
248
|
-
query = normalize_hash(ctx.query)
|
|
249
|
-
resolved_config = api_key_resolve_config(ctx.context, config, query[:config_id])
|
|
250
|
-
id = query[:id]
|
|
251
|
-
record = api_key_find_by_id(ctx, id, resolved_config)
|
|
252
|
-
raise APIError.new("NOT_FOUND", message: API_KEY_ERROR_CODES["KEY_NOT_FOUND"]) unless record && api_key_config_id_matches?(api_key_record_config_id(record), resolved_config[:config_id])
|
|
253
|
-
|
|
254
|
-
record_config = api_key_resolve_config(ctx.context, config, api_key_record_config_id(record))
|
|
255
|
-
api_key_authorize_reference!(ctx, record_config, session[:user]["id"], api_key_record_reference_id(record), "read")
|
|
256
|
-
|
|
257
|
-
record = api_key_migrate_legacy_metadata(ctx, record, record_config)
|
|
258
|
-
api_key_delete_expired(ctx.context, record_config)
|
|
259
|
-
ctx.json(api_key_public(record, include_key_field: false))
|
|
260
|
-
end
|
|
67
|
+
BetterAuth::APIKey::Routes::GetAPIKey.endpoint(config)
|
|
261
68
|
end
|
|
262
69
|
|
|
263
70
|
def api_key_update_endpoint(config)
|
|
264
|
-
|
|
265
|
-
body = api_key_normalize_body(ctx.body)
|
|
266
|
-
resolved_config = api_key_resolve_config(ctx.context, config, body[:config_id])
|
|
267
|
-
session = Routes.current_session(ctx, allow_nil: true)
|
|
268
|
-
user_id = session&.dig(:user, "id") || body[:user_id]
|
|
269
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["UNAUTHORIZED_SESSION"]) unless user_id
|
|
270
|
-
if session && body[:user_id] && body[:user_id] != session[:user]["id"]
|
|
271
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["UNAUTHORIZED_SESSION"])
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
key_id = body[:key_id]
|
|
275
|
-
record = api_key_find_by_id(ctx, key_id, resolved_config)
|
|
276
|
-
raise APIError.new("NOT_FOUND", message: API_KEY_ERROR_CODES["KEY_NOT_FOUND"]) unless record
|
|
277
|
-
raise APIError.new("NOT_FOUND", message: API_KEY_ERROR_CODES["KEY_NOT_FOUND"]) unless api_key_config_id_matches?(api_key_record_config_id(record), resolved_config[:config_id])
|
|
278
|
-
|
|
279
|
-
record_config = api_key_resolve_config(ctx.context, config, api_key_record_config_id(record))
|
|
280
|
-
api_key_authorize_reference!(ctx, record_config, user_id, api_key_record_reference_id(record), "update")
|
|
281
|
-
|
|
282
|
-
api_key_validate_create_update!(body, record_config, create: false, client: api_key_auth_required?(ctx))
|
|
283
|
-
update = api_key_update_payload(body, record_config)
|
|
284
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["NO_VALUES_TO_UPDATE"]) if update.empty?
|
|
285
|
-
|
|
286
|
-
updated = api_key_update_record(ctx, record, update.merge(updatedAt: Time.now), record_config)
|
|
287
|
-
updated = api_key_migrate_legacy_metadata(ctx, updated, record_config)
|
|
288
|
-
api_key_delete_expired(ctx.context, record_config)
|
|
289
|
-
ctx.json(api_key_public(updated, include_key_field: false))
|
|
290
|
-
end
|
|
71
|
+
BetterAuth::APIKey::Routes::UpdateAPIKey.endpoint(config)
|
|
291
72
|
end
|
|
292
73
|
|
|
293
74
|
def api_key_delete_endpoint(config)
|
|
294
|
-
|
|
295
|
-
session = Routes.current_session(ctx)
|
|
296
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["USER_BANNED"]) if session[:user]["banned"] == true
|
|
297
|
-
|
|
298
|
-
body = normalize_hash(ctx.body)
|
|
299
|
-
resolved_config = api_key_resolve_config(ctx.context, config, body[:config_id])
|
|
300
|
-
key_id = body[:key_id]
|
|
301
|
-
record = api_key_find_by_id(ctx, key_id, resolved_config)
|
|
302
|
-
raise APIError.new("NOT_FOUND", message: API_KEY_ERROR_CODES["KEY_NOT_FOUND"]) unless record && api_key_config_id_matches?(api_key_record_config_id(record), resolved_config[:config_id])
|
|
303
|
-
|
|
304
|
-
record_config = api_key_resolve_config(ctx.context, config, api_key_record_config_id(record))
|
|
305
|
-
api_key_authorize_reference!(ctx, record_config, session[:user]["id"], api_key_record_reference_id(record), "delete")
|
|
306
|
-
|
|
307
|
-
api_key_delete_record(ctx, record, record_config)
|
|
308
|
-
api_key_delete_expired(ctx.context, record_config)
|
|
309
|
-
ctx.json({success: true})
|
|
310
|
-
end
|
|
75
|
+
BetterAuth::APIKey::Routes::DeleteAPIKey.endpoint(config)
|
|
311
76
|
end
|
|
312
77
|
|
|
313
78
|
def api_key_list_endpoint(config)
|
|
314
|
-
|
|
315
|
-
session = Routes.current_session(ctx)
|
|
316
|
-
query = normalize_hash(ctx.query)
|
|
317
|
-
api_key_validate_list_query!(query)
|
|
318
|
-
configs = query[:config_id] ? [api_key_resolve_config(ctx.context, config, query[:config_id])] : config.fetch(:configurations, [config])
|
|
319
|
-
reference_id = query[:organization_id] || session[:user]["id"]
|
|
320
|
-
expected_reference = query[:organization_id] ? "organization" : "user"
|
|
321
|
-
api_key_check_org_permission!(ctx, session[:user]["id"], reference_id, "read") if query[:organization_id]
|
|
322
|
-
records = configs.flat_map { |entry| api_key_list_for_reference(ctx, reference_id, entry) }.uniq { |record| record["id"] }
|
|
323
|
-
records = records.select do |record|
|
|
324
|
-
record_config = api_key_resolve_config(ctx.context, config, api_key_record_config_id(record))
|
|
325
|
-
record_config[:references].to_s == expected_reference &&
|
|
326
|
-
api_key_record_reference_id(record) == reference_id &&
|
|
327
|
-
(!query[:config_id] || api_key_config_id_matches?(api_key_record_config_id(record), query[:config_id]))
|
|
328
|
-
end
|
|
329
|
-
total = records.length
|
|
330
|
-
records = api_key_sort_records(records, query[:sort_by], query[:sort_direction])
|
|
331
|
-
offset = query.key?(:offset) ? query[:offset].to_i : nil
|
|
332
|
-
limit = query.key?(:limit) ? query[:limit].to_i : nil
|
|
333
|
-
records = records.drop(offset) if offset
|
|
334
|
-
records = records.first(limit) if limit
|
|
335
|
-
records.each { |record| api_key_delete_expired(ctx.context, api_key_resolve_config(ctx.context, config, api_key_record_config_id(record))) }
|
|
336
|
-
api_keys = records.map do |record|
|
|
337
|
-
record_config = api_key_resolve_config(ctx.context, config, api_key_record_config_id(record))
|
|
338
|
-
api_key_public(api_key_migrate_legacy_metadata(ctx, record, record_config), include_key_field: false)
|
|
339
|
-
end
|
|
340
|
-
ctx.json({apiKeys: api_keys, total: total, limit: limit, offset: offset})
|
|
341
|
-
end
|
|
79
|
+
BetterAuth::APIKey::Routes::ListAPIKeys.endpoint(config)
|
|
342
80
|
end
|
|
343
81
|
|
|
344
82
|
def api_key_delete_expired_endpoint(config)
|
|
345
|
-
|
|
346
|
-
api_key_delete_expired(ctx.context, config, bypass_last_check: true)
|
|
347
|
-
ctx.json({success: true, error: nil})
|
|
348
|
-
rescue => error
|
|
349
|
-
ctx.context.logger.error("[API KEY PLUGIN] Failed to delete expired API keys: #{error.message}") if ctx.context.logger.respond_to?(:error)
|
|
350
|
-
ctx.json({success: false, error: error})
|
|
351
|
-
end
|
|
83
|
+
BetterAuth::APIKey::Routes::DeleteAllExpiredAPIKeys.endpoint(config)
|
|
352
84
|
end
|
|
353
85
|
|
|
354
86
|
def api_key_resolve_config(context, config, config_id = nil)
|
|
355
|
-
|
|
356
|
-
return configurations.find { |entry| api_key_default_config_id?(entry[:config_id]) } || configurations.first if config_id.to_s.empty?
|
|
357
|
-
|
|
358
|
-
configurations.find { |entry| entry[:config_id].to_s == config_id.to_s } ||
|
|
359
|
-
begin
|
|
360
|
-
default = configurations.find { |entry| api_key_default_config_id?(entry[:config_id]) }
|
|
361
|
-
unless default
|
|
362
|
-
context.logger.error(API_KEY_ERROR_CODES["NO_DEFAULT_API_KEY_CONFIGURATION_FOUND"]) if context.respond_to?(:logger) && context.logger.respond_to?(:error)
|
|
363
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["NO_DEFAULT_API_KEY_CONFIGURATION_FOUND"])
|
|
364
|
-
end
|
|
365
|
-
default
|
|
366
|
-
end
|
|
87
|
+
BetterAuth::APIKey::Routes.resolve_config(context, config, config_id)
|
|
367
88
|
end
|
|
368
89
|
|
|
369
90
|
def api_key_default_config_id?(value)
|
|
370
|
-
|
|
91
|
+
BetterAuth::APIKey::Routes.default_config_id?(value)
|
|
371
92
|
end
|
|
372
93
|
|
|
373
94
|
def api_key_config_id_matches?(record_config_id, expected_config_id)
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
record_config_id.to_s == expected_config_id.to_s
|
|
95
|
+
BetterAuth::APIKey::Routes.config_id_matches?(record_config_id, expected_config_id)
|
|
377
96
|
end
|
|
378
97
|
|
|
379
98
|
def api_key_create_reference_id!(ctx, body, session, config)
|
|
380
|
-
|
|
381
|
-
organization_id = body[:organization_id]
|
|
382
|
-
if organization_id.to_s.empty?
|
|
383
|
-
raise APIError.new(
|
|
384
|
-
"BAD_REQUEST",
|
|
385
|
-
message: API_KEY_ERROR_CODES["ORGANIZATION_ID_REQUIRED"],
|
|
386
|
-
code: "ORGANIZATION_ID_REQUIRED"
|
|
387
|
-
)
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
user_id = session&.dig(:user, "id") || body[:user_id]
|
|
391
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["UNAUTHORIZED_SESSION"]) if user_id.to_s.empty?
|
|
392
|
-
|
|
393
|
-
api_key_check_org_permission!(ctx, user_id, organization_id, "create")
|
|
394
|
-
organization_id
|
|
395
|
-
elsif session && body[:user_id] && body[:user_id] != session[:user]["id"]
|
|
396
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["UNAUTHORIZED_SESSION"])
|
|
397
|
-
elsif session
|
|
398
|
-
|
|
399
|
-
session[:user]["id"]
|
|
400
|
-
else
|
|
401
|
-
user_id = body[:user_id]
|
|
402
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["UNAUTHORIZED_SESSION"]) if user_id.to_s.empty?
|
|
403
|
-
|
|
404
|
-
user_id
|
|
405
|
-
end
|
|
99
|
+
BetterAuth::APIKey::OrgAuthorization.create_reference_id!(ctx, body, session, config)
|
|
406
100
|
end
|
|
407
101
|
|
|
408
102
|
def api_key_record_reference_id(record)
|
|
409
|
-
record
|
|
103
|
+
BetterAuth::APIKey::Types.record_reference_id(record)
|
|
410
104
|
end
|
|
411
105
|
|
|
412
106
|
def api_key_record_user_id(record)
|
|
413
|
-
|
|
107
|
+
BetterAuth::APIKey::Types.record_user_id(record)
|
|
414
108
|
end
|
|
415
109
|
|
|
416
110
|
def api_key_record_config_id(record)
|
|
417
|
-
record
|
|
111
|
+
BetterAuth::APIKey::Types.record_config_id(record)
|
|
418
112
|
end
|
|
419
113
|
|
|
420
114
|
def api_key_default_permissions(config, reference_id, ctx)
|
|
421
|
-
|
|
422
|
-
return permissions.call(reference_id, ctx) if permissions.respond_to?(:call)
|
|
423
|
-
|
|
424
|
-
permissions
|
|
115
|
+
BetterAuth::APIKey::Types.default_permissions(config, reference_id, ctx)
|
|
425
116
|
end
|
|
426
117
|
|
|
427
118
|
def api_key_authorize_reference!(ctx, config, user_id, reference_id, action)
|
|
428
|
-
|
|
429
|
-
api_key_check_org_permission!(ctx, user_id, reference_id, action)
|
|
430
|
-
elsif reference_id != user_id
|
|
431
|
-
raise APIError.new("NOT_FOUND", message: API_KEY_ERROR_CODES["KEY_NOT_FOUND"])
|
|
432
|
-
end
|
|
119
|
+
BetterAuth::APIKey::OrgAuthorization.authorize_reference!(ctx, config, user_id, reference_id, action)
|
|
433
120
|
end
|
|
434
121
|
|
|
435
122
|
def api_key_check_org_permission!(ctx, user_id, organization_id, action)
|
|
436
|
-
|
|
437
|
-
unless org_plugin
|
|
438
|
-
raise APIError.new(
|
|
439
|
-
"INTERNAL_SERVER_ERROR",
|
|
440
|
-
message: API_KEY_ERROR_CODES["ORGANIZATION_PLUGIN_REQUIRED"],
|
|
441
|
-
code: "ORGANIZATION_PLUGIN_REQUIRED"
|
|
442
|
-
)
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
member = ctx.context.adapter.find_one(model: "member", where: [{field: "userId", value: user_id}, {field: "organizationId", value: organization_id}])
|
|
446
|
-
unless member
|
|
447
|
-
raise APIError.new(
|
|
448
|
-
"FORBIDDEN",
|
|
449
|
-
message: API_KEY_ERROR_CODES["USER_NOT_MEMBER_OF_ORGANIZATION"],
|
|
450
|
-
code: "USER_NOT_MEMBER_OF_ORGANIZATION"
|
|
451
|
-
)
|
|
452
|
-
end
|
|
453
|
-
|
|
454
|
-
return member if member["role"].to_s == (org_plugin.options[:creator_role] || "owner").to_s
|
|
455
|
-
|
|
456
|
-
permissions = {"apiKey" => [action]}
|
|
457
|
-
return member if BetterAuth::Plugins.organization_permission?(ctx, org_plugin.options, member["role"], permissions, organization_id)
|
|
458
|
-
|
|
459
|
-
raise APIError.new(
|
|
460
|
-
"FORBIDDEN",
|
|
461
|
-
message: API_KEY_ERROR_CODES["INSUFFICIENT_API_KEY_PERMISSIONS"],
|
|
462
|
-
code: "INSUFFICIENT_API_KEY_PERMISSIONS"
|
|
463
|
-
)
|
|
123
|
+
BetterAuth::APIKey::OrgAuthorization.check_permission!(ctx, user_id, organization_id, action)
|
|
464
124
|
end
|
|
465
125
|
|
|
466
126
|
def api_key_sort_records(records, sort_by, direction)
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
key = Schema.storage_key(sort_by)
|
|
470
|
-
sorted = records.sort_by { |record| record[key] || record[key.to_sym] || "" }
|
|
471
|
-
if direction.to_s.downcase == "desc"
|
|
472
|
-
sorted.reverse
|
|
473
|
-
else
|
|
474
|
-
sorted
|
|
475
|
-
end
|
|
127
|
+
BetterAuth::APIKey::Utils.sort_records(records, sort_by, direction)
|
|
476
128
|
end
|
|
477
129
|
|
|
478
130
|
def api_key_validate_list_query!(query)
|
|
479
|
-
|
|
480
|
-
next unless query.key?(key)
|
|
481
|
-
|
|
482
|
-
value = query[key]
|
|
483
|
-
raise APIError.new("BAD_REQUEST", message: "Invalid #{key}") unless value.to_s.match?(/\A\d+\z/)
|
|
484
|
-
end
|
|
485
|
-
|
|
486
|
-
direction = query[:sort_direction]
|
|
487
|
-
return if direction.nil? || %w[asc desc].include?(direction.to_s.downcase)
|
|
488
|
-
|
|
489
|
-
raise APIError.new("BAD_REQUEST", message: "Invalid sortDirection")
|
|
131
|
+
BetterAuth::APIKey::Utils.validate_list_query!(query)
|
|
490
132
|
end
|
|
491
133
|
|
|
492
134
|
def api_key_error_code(error)
|
|
493
|
-
|
|
135
|
+
BetterAuth::APIKey::Utils.error_code(error)
|
|
494
136
|
end
|
|
495
137
|
|
|
496
138
|
def api_key_error_payload(error)
|
|
497
|
-
|
|
498
|
-
return payload if payload.is_a?(Hash) && payload.key?(:details)
|
|
499
|
-
|
|
500
|
-
{message: error.message, code: api_key_error_code(error)}
|
|
139
|
+
BetterAuth::APIKey::Utils.error_payload(error)
|
|
501
140
|
end
|
|
502
141
|
|
|
503
142
|
def api_key_session_header_config(ctx, config)
|
|
504
|
-
|
|
505
|
-
entry[:enable_session_for_api_keys] && api_key_get_from_headers(ctx, entry)
|
|
506
|
-
end
|
|
143
|
+
BetterAuth::APIKey::Session.header_config(ctx, config)
|
|
507
144
|
end
|
|
508
145
|
|
|
509
146
|
def api_key_session_hook(ctx, config)
|
|
510
|
-
|
|
511
|
-
key = api_key_get_from_headers(ctx, config)
|
|
512
|
-
unless key.is_a?(String)
|
|
513
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["INVALID_API_KEY_GETTER_RETURN_TYPE"])
|
|
514
|
-
end
|
|
515
|
-
raise APIError.new("FORBIDDEN", message: API_KEY_ERROR_CODES["INVALID_API_KEY"]) if key.length < config[:default_key_length].to_i
|
|
516
|
-
|
|
517
|
-
if config[:custom_api_key_validator].respond_to?(:call) && !config[:custom_api_key_validator].call({ctx: ctx, key: key})
|
|
518
|
-
raise APIError.new("FORBIDDEN", message: API_KEY_ERROR_CODES["INVALID_API_KEY"])
|
|
519
|
-
end
|
|
520
|
-
|
|
521
|
-
record = api_key_validate!(ctx, key, config)
|
|
522
|
-
api_key_schedule_cleanup(ctx, config)
|
|
523
|
-
if config[:references].to_s != "user"
|
|
524
|
-
raise APIError.new(
|
|
525
|
-
"UNAUTHORIZED",
|
|
526
|
-
message: API_KEY_ERROR_CODES["INVALID_REFERENCE_ID_FROM_API_KEY"],
|
|
527
|
-
code: "INVALID_REFERENCE_ID_FROM_API_KEY"
|
|
528
|
-
)
|
|
529
|
-
end
|
|
530
|
-
reference_id = api_key_record_reference_id(record)
|
|
531
|
-
user = ctx.context.internal_adapter.find_user_by_id(reference_id)
|
|
532
|
-
unless user
|
|
533
|
-
raise APIError.new(
|
|
534
|
-
"UNAUTHORIZED",
|
|
535
|
-
message: API_KEY_ERROR_CODES["INVALID_REFERENCE_ID_FROM_API_KEY"],
|
|
536
|
-
code: "INVALID_REFERENCE_ID_FROM_API_KEY"
|
|
537
|
-
)
|
|
538
|
-
end
|
|
539
|
-
|
|
540
|
-
session = {
|
|
541
|
-
user: user,
|
|
542
|
-
session: {
|
|
543
|
-
"id" => record["id"],
|
|
544
|
-
"token" => key,
|
|
545
|
-
"userId" => reference_id,
|
|
546
|
-
"userAgent" => ctx.headers["user-agent"],
|
|
547
|
-
"ipAddress" => RequestIP.client_ip(ctx.request || ctx.headers, ctx.context.options),
|
|
548
|
-
"createdAt" => Time.now,
|
|
549
|
-
"updatedAt" => Time.now,
|
|
550
|
-
"expiresAt" => record["expiresAt"] || (Time.now + ctx.context.options.session[:expires_in].to_i)
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
ctx.context.set_current_session(session)
|
|
554
|
-
nil
|
|
147
|
+
BetterAuth::APIKey::Session.hook(ctx, config)
|
|
555
148
|
end
|
|
556
149
|
|
|
557
150
|
def api_key_validate!(ctx, key, config, permissions: nil)
|
|
558
|
-
|
|
559
|
-
record = api_key_find_by_hash(ctx, hashed, config)
|
|
560
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["INVALID_API_KEY"]) unless record
|
|
561
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["INVALID_API_KEY"]) unless api_key_config_id_matches?(api_key_record_config_id(record), config[:config_id])
|
|
562
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["KEY_DISABLED"]) if record["enabled"] == false
|
|
563
|
-
if record["expiresAt"] && record["expiresAt"] <= Time.now
|
|
564
|
-
api_key_schedule_record_delete(ctx, record, config)
|
|
565
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["KEY_EXPIRED"])
|
|
566
|
-
end
|
|
567
|
-
if record["remaining"].to_i <= 0 && !record["remaining"].nil? && record["refillAmount"].nil?
|
|
568
|
-
api_key_schedule_record_delete(ctx, record, config)
|
|
569
|
-
raise APIError.new("TOO_MANY_REQUESTS", message: API_KEY_ERROR_CODES["USAGE_EXCEEDED"])
|
|
570
|
-
end
|
|
571
|
-
|
|
572
|
-
api_key_check_permissions!(record, permissions)
|
|
573
|
-
update = api_key_usage_update(record, config)
|
|
574
|
-
updated = api_key_update_record(ctx, record, update, config, defer: true)
|
|
575
|
-
api_key_migrate_legacy_metadata(ctx, updated || record.merge(update.transform_keys { |key_name| Schema.storage_key(key_name) }), config)
|
|
151
|
+
BetterAuth::APIKey::Validation.validate_api_key!(ctx, key, config, permissions: permissions)
|
|
576
152
|
end
|
|
577
153
|
|
|
578
154
|
def api_key_usage_update(record, config)
|
|
579
|
-
|
|
580
|
-
update = {lastRequest: now, updatedAt: now}
|
|
581
|
-
|
|
582
|
-
if (try_again_in = api_key_rate_limit_try_again_in(record, config, now))
|
|
583
|
-
raise APIError.new(
|
|
584
|
-
"UNAUTHORIZED",
|
|
585
|
-
message: API_KEY_ERROR_CODES["RATE_LIMIT_EXCEEDED"],
|
|
586
|
-
code: "RATE_LIMITED",
|
|
587
|
-
body: {
|
|
588
|
-
message: API_KEY_ERROR_CODES["RATE_LIMIT_EXCEEDED"],
|
|
589
|
-
code: "RATE_LIMITED",
|
|
590
|
-
details: {tryAgainIn: try_again_in}
|
|
591
|
-
}
|
|
592
|
-
)
|
|
593
|
-
end
|
|
594
|
-
update[:requestCount] = api_key_next_request_count(record, now) if api_key_rate_limit_counts_requests?(record, config)
|
|
595
|
-
|
|
596
|
-
remaining = record["remaining"]
|
|
597
|
-
if !remaining.nil?
|
|
598
|
-
if remaining.to_i <= 0 && record["refillAmount"] && record["refillInterval"]
|
|
599
|
-
last_refill = api_key_normalize_time(record["lastRefillAt"] || record["createdAt"])
|
|
600
|
-
if !last_refill || ((now - last_refill) * 1000) > record["refillInterval"].to_i
|
|
601
|
-
remaining = record["refillAmount"].to_i
|
|
602
|
-
update[:lastRefillAt] = now
|
|
603
|
-
end
|
|
604
|
-
end
|
|
605
|
-
raise APIError.new("TOO_MANY_REQUESTS", message: API_KEY_ERROR_CODES["USAGE_EXCEEDED"]) if remaining.to_i <= 0
|
|
606
|
-
|
|
607
|
-
update[:remaining] = remaining.to_i - 1
|
|
608
|
-
end
|
|
609
|
-
update
|
|
155
|
+
BetterAuth::APIKey::Validation.usage_update(record, config)
|
|
610
156
|
end
|
|
611
157
|
|
|
612
158
|
def api_key_rate_limit_try_again_in(record, config, now)
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
window = record["rateLimitTimeWindow"]
|
|
616
|
-
max = record["rateLimitMax"]
|
|
617
|
-
return nil if window.nil? || max.nil?
|
|
618
|
-
|
|
619
|
-
last = api_key_normalize_time(record["lastRequest"])
|
|
620
|
-
return nil unless last
|
|
621
|
-
|
|
622
|
-
elapsed = (now - last) * 1000
|
|
623
|
-
return nil if elapsed > window.to_i
|
|
624
|
-
return nil unless record["requestCount"].to_i >= max.to_i
|
|
625
|
-
|
|
626
|
-
(window.to_i - elapsed).ceil
|
|
159
|
+
BetterAuth::APIKey::RateLimit.try_again_in(record, config, now)
|
|
627
160
|
end
|
|
628
161
|
|
|
629
162
|
def api_key_rate_limit_counts_requests?(record, config)
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
!record["rateLimitTimeWindow"].nil? && !record["rateLimitMax"].nil?
|
|
163
|
+
BetterAuth::APIKey::RateLimit.counts_requests?(record, config)
|
|
633
164
|
end
|
|
634
165
|
|
|
635
166
|
def api_key_next_request_count(record, now)
|
|
636
|
-
|
|
637
|
-
window = record["rateLimitTimeWindow"].to_i
|
|
638
|
-
if last && window.positive? && ((now - last) * 1000) <= window
|
|
639
|
-
record["requestCount"].to_i + 1
|
|
640
|
-
else
|
|
641
|
-
1
|
|
642
|
-
end
|
|
167
|
+
BetterAuth::APIKey::RateLimit.next_request_count(record, now)
|
|
643
168
|
end
|
|
644
169
|
|
|
645
170
|
def api_key_validate_create_update!(body, config, create:, client:)
|
|
646
|
-
|
|
647
|
-
if create && config[:require_name] && name.to_s.empty?
|
|
648
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["NAME_REQUIRED"])
|
|
649
|
-
end
|
|
650
|
-
if name && !name.to_s.length.between?(config[:minimum_name_length].to_i, config[:maximum_name_length].to_i)
|
|
651
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["INVALID_NAME_LENGTH"])
|
|
652
|
-
end
|
|
653
|
-
prefix = body[:prefix]
|
|
654
|
-
if prefix && !prefix.to_s.length.between?(config[:minimum_prefix_length].to_i, config[:maximum_prefix_length].to_i)
|
|
655
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["INVALID_PREFIX_LENGTH"])
|
|
656
|
-
end
|
|
657
|
-
if prefix && !prefix.to_s.match?(/\A[a-zA-Z0-9_-]+\z/)
|
|
658
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["INVALID_PREFIX_LENGTH"])
|
|
659
|
-
end
|
|
660
|
-
if body.key?(:remaining) && !body[:remaining].nil?
|
|
661
|
-
minimum = create ? 0 : 1
|
|
662
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["INVALID_REMAINING"]) if body[:remaining].to_i < minimum
|
|
663
|
-
end
|
|
664
|
-
if body[:metadata] && (create || config[:enable_metadata])
|
|
665
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["METADATA_DISABLED"]) unless config[:enable_metadata]
|
|
666
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["INVALID_METADATA_TYPE"]) unless body[:metadata].nil? || body[:metadata].is_a?(Hash)
|
|
667
|
-
end
|
|
668
|
-
server_only_keys = %i[refill_amount refill_interval rate_limit_max rate_limit_time_window rate_limit_enabled remaining permissions]
|
|
669
|
-
if client && server_only_keys.any? { |key| (create && key == :remaining) ? !body[:remaining].nil? : body.key?(key) }
|
|
670
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["SERVER_ONLY_PROPERTY"])
|
|
671
|
-
end
|
|
672
|
-
amount_present = body.key?(:refill_amount)
|
|
673
|
-
interval_present = body.key?(:refill_interval)
|
|
674
|
-
if amount_present && !interval_present
|
|
675
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["REFILL_AMOUNT_AND_INTERVAL_REQUIRED"])
|
|
676
|
-
end
|
|
677
|
-
if interval_present && !amount_present
|
|
678
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["REFILL_INTERVAL_AND_AMOUNT_REQUIRED"])
|
|
679
|
-
end
|
|
680
|
-
if body.key?(:expires_in)
|
|
681
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["KEY_DISABLED_EXPIRATION"]) if config[:key_expiration][:disable_custom_expires_time]
|
|
682
|
-
return if body[:expires_in].nil?
|
|
683
|
-
|
|
684
|
-
days = body[:expires_in].to_f / 86_400
|
|
685
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["EXPIRES_IN_IS_TOO_SMALL"]) if days < config[:key_expiration][:min_expires_in].to_f
|
|
686
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["EXPIRES_IN_IS_TOO_LARGE"]) if days > config[:key_expiration][:max_expires_in].to_f
|
|
687
|
-
end
|
|
171
|
+
BetterAuth::APIKey::Validation.validate_create_update!(body, config, create: create, client: client)
|
|
688
172
|
end
|
|
689
173
|
|
|
690
174
|
def api_key_update_payload(body, config)
|
|
691
|
-
|
|
692
|
-
update[:name] = body[:name] if body.key?(:name)
|
|
693
|
-
update[:enabled] = body[:enabled] unless body[:enabled].nil?
|
|
694
|
-
update[:remaining] = body[:remaining] if body.key?(:remaining)
|
|
695
|
-
update[:refillAmount] = body[:refill_amount] if body.key?(:refill_amount)
|
|
696
|
-
update[:refillInterval] = body[:refill_interval] if body.key?(:refill_interval)
|
|
697
|
-
update[:rateLimitEnabled] = body[:rate_limit_enabled] if body.key?(:rate_limit_enabled)
|
|
698
|
-
update[:rateLimitTimeWindow] = body[:rate_limit_time_window] if body.key?(:rate_limit_time_window)
|
|
699
|
-
update[:rateLimitMax] = body[:rate_limit_max] if body.key?(:rate_limit_max)
|
|
700
|
-
update[:expiresAt] = body[:expires_in].nil? ? nil : Time.now + body[:expires_in].to_i if body.key?(:expires_in)
|
|
701
|
-
update[:metadata] = api_key_encode_json(body[:metadata]) if body.key?(:metadata) && config[:enable_metadata]
|
|
702
|
-
update[:permissions] = api_key_encode_json(body[:permissions]) if body.key?(:permissions)
|
|
703
|
-
update
|
|
175
|
+
BetterAuth::APIKey::Validation.update_payload(body, config)
|
|
704
176
|
end
|
|
705
177
|
|
|
706
178
|
def api_key_generate_key(config, prefix)
|
|
707
|
-
|
|
708
|
-
return generator.call({length: config[:default_key_length], prefix: prefix}) if generator.respond_to?(:call)
|
|
709
|
-
|
|
710
|
-
alphabet = [*("a".."z"), *("A".."Z")]
|
|
711
|
-
"#{prefix}#{Array.new(config[:default_key_length].to_i) { alphabet[SecureRandom.random_number(alphabet.length)] }.join}"
|
|
179
|
+
BetterAuth::APIKey::Keys.generate(config, prefix)
|
|
712
180
|
end
|
|
713
181
|
|
|
714
182
|
def api_key_hash(key, config)
|
|
715
|
-
|
|
183
|
+
BetterAuth::APIKey::Keys.hash(key, config)
|
|
716
184
|
end
|
|
717
185
|
|
|
718
186
|
def api_key_normalize_body(raw)
|
|
719
|
-
|
|
720
|
-
return body unless raw.is_a?(Hash)
|
|
721
|
-
|
|
722
|
-
metadata_key = raw.key?(:metadata) ? :metadata : ("metadata" if raw.key?("metadata"))
|
|
723
|
-
body[:metadata] = raw[metadata_key] if metadata_key
|
|
724
|
-
body
|
|
187
|
+
BetterAuth::APIKey::Keys.normalize_body(raw)
|
|
725
188
|
end
|
|
726
189
|
|
|
727
190
|
def api_key_expires_at(body, config)
|
|
728
|
-
|
|
729
|
-
Time.now + body[:expires_in].to_i unless body[:expires_in].nil?
|
|
730
|
-
elsif config[:key_expiration][:default_expires_in]
|
|
731
|
-
Time.now + config[:key_expiration][:default_expires_in].to_i
|
|
732
|
-
end
|
|
191
|
+
BetterAuth::APIKey::Keys.expires_at(body, config)
|
|
733
192
|
end
|
|
734
193
|
|
|
735
194
|
def api_key_store(ctx, data, config)
|
|
736
|
-
|
|
737
|
-
if config[:storage] == "database" || config[:fallback_to_database]
|
|
738
|
-
record = ctx.context.adapter.create(model: API_KEY_TABLE_NAME, data: data)
|
|
739
|
-
end
|
|
740
|
-
record ||= data.transform_keys { |key| Schema.storage_key(key) }.merge("id" => SecureRandom.hex(16))
|
|
741
|
-
api_key_storage_set(ctx, record, config) if config[:storage] == "secondary-storage"
|
|
742
|
-
record
|
|
195
|
+
BetterAuth::APIKey::Adapter.store(ctx, data, config)
|
|
743
196
|
end
|
|
744
197
|
|
|
745
198
|
def api_key_find_by_hash(ctx, hashed, config)
|
|
746
|
-
|
|
747
|
-
record = api_key_storage_get(ctx, "api-key:#{hashed}", config) || api_key_storage_get(ctx, "api-key:key:#{hashed}", config)
|
|
748
|
-
return record if record
|
|
749
|
-
return nil unless config[:fallback_to_database]
|
|
750
|
-
end
|
|
751
|
-
record = ctx.context.adapter.find_one(model: API_KEY_TABLE_NAME, where: [{field: "key", value: hashed}])
|
|
752
|
-
api_key_storage_set(ctx, record, config) if record && config[:storage] == "secondary-storage" && config[:fallback_to_database]
|
|
753
|
-
record
|
|
199
|
+
BetterAuth::APIKey::Adapter.find_by_hash(ctx, hashed, config)
|
|
754
200
|
end
|
|
755
201
|
|
|
756
202
|
def api_key_find_by_id(ctx, id, config)
|
|
757
|
-
|
|
758
|
-
record = api_key_storage_get(ctx, "api-key:by-id:#{id}", config) || api_key_storage_get(ctx, "api-key:id:#{id}", config)
|
|
759
|
-
return record if record
|
|
760
|
-
return nil unless config[:fallback_to_database]
|
|
761
|
-
end
|
|
762
|
-
record = ctx.context.adapter.find_one(model: API_KEY_TABLE_NAME, where: [{field: "id", value: id}])
|
|
763
|
-
api_key_storage_set(ctx, record, config) if record && config[:storage] == "secondary-storage" && config[:fallback_to_database]
|
|
764
|
-
record
|
|
203
|
+
BetterAuth::APIKey::Adapter.find_by_id(ctx, id, config)
|
|
765
204
|
end
|
|
766
205
|
|
|
767
206
|
def api_key_list_for_user(ctx, user_id, config)
|
|
@@ -769,279 +208,107 @@ module BetterAuth
|
|
|
769
208
|
end
|
|
770
209
|
|
|
771
210
|
def api_key_list_for_reference(ctx, reference_id, config)
|
|
772
|
-
|
|
773
|
-
begin
|
|
774
|
-
storage = api_key_storage(config, ctx.context)
|
|
775
|
-
ids = JSON.parse((storage&.get("api-key:by-ref:#{reference_id}") || storage&.get("api-key:user:#{reference_id}")).to_s)
|
|
776
|
-
records = ids.filter_map { |id| api_key_find_by_id(ctx, id, config) }
|
|
777
|
-
return records unless records.empty? && config[:fallback_to_database]
|
|
778
|
-
rescue JSON::ParserError, NoMethodError
|
|
779
|
-
return [] unless config[:fallback_to_database]
|
|
780
|
-
end
|
|
781
|
-
end
|
|
782
|
-
records = ctx.context.adapter.find_many(model: API_KEY_TABLE_NAME, where: [{field: "referenceId", value: reference_id}])
|
|
783
|
-
legacy = ctx.context.adapter.find_many(model: API_KEY_TABLE_NAME, where: [{field: "userId", value: reference_id}])
|
|
784
|
-
combined = (records + legacy).uniq { |record| record["id"] }
|
|
785
|
-
api_key_storage_populate_reference(ctx, reference_id, combined, config) if config[:storage] == "secondary-storage" && config[:fallback_to_database]
|
|
786
|
-
combined
|
|
211
|
+
BetterAuth::APIKey::Adapter.list_for_reference(ctx, reference_id, config)
|
|
787
212
|
end
|
|
788
213
|
|
|
789
214
|
def api_key_update_record(ctx, record, update, config, defer: false)
|
|
790
|
-
|
|
791
|
-
updated = nil
|
|
792
|
-
if config[:storage] == "database" || config[:fallback_to_database]
|
|
793
|
-
updated = ctx.context.adapter.update(model: API_KEY_TABLE_NAME, where: [{field: "id", value: record["id"]}], update: update)
|
|
794
|
-
end
|
|
795
|
-
updated ||= record.merge(update.transform_keys { |key| Schema.storage_key(key) })
|
|
796
|
-
api_key_storage_set(ctx, updated, config) if config[:storage] == "secondary-storage"
|
|
797
|
-
updated
|
|
798
|
-
end
|
|
799
|
-
|
|
800
|
-
if defer && config[:defer_updates] && api_key_background_tasks?(ctx)
|
|
801
|
-
scheduled = record.merge(update.transform_keys { |key| Schema.storage_key(key) })
|
|
802
|
-
ctx.context.run_in_background(performer)
|
|
803
|
-
scheduled
|
|
804
|
-
else
|
|
805
|
-
performer.call
|
|
806
|
-
end
|
|
215
|
+
BetterAuth::APIKey::Adapter.update_record(ctx, record, update, config, defer: defer)
|
|
807
216
|
end
|
|
808
217
|
|
|
809
218
|
def api_key_delete_record(ctx, record, config)
|
|
810
|
-
|
|
811
|
-
api_key_storage_delete(ctx, record, config) if config[:storage] == "secondary-storage"
|
|
219
|
+
BetterAuth::APIKey::Adapter.delete_record(ctx, record, config)
|
|
812
220
|
end
|
|
813
221
|
|
|
814
222
|
def api_key_schedule_record_delete(ctx, record, config)
|
|
815
|
-
|
|
816
|
-
if config[:defer_updates] && api_key_background_tasks?(ctx)
|
|
817
|
-
ctx.context.run_in_background(task)
|
|
818
|
-
else
|
|
819
|
-
task.call
|
|
820
|
-
end
|
|
223
|
+
BetterAuth::APIKey::Adapter.schedule_record_delete(ctx, record, config)
|
|
821
224
|
end
|
|
822
225
|
|
|
823
226
|
def api_key_schedule_cleanup(ctx, config)
|
|
824
|
-
|
|
825
|
-
if config[:defer_updates] && api_key_background_tasks?(ctx)
|
|
826
|
-
ctx.context.run_in_background(task)
|
|
827
|
-
else
|
|
828
|
-
task.call
|
|
829
|
-
end
|
|
227
|
+
BetterAuth::APIKey::Routes.schedule_cleanup(ctx, config)
|
|
830
228
|
end
|
|
831
229
|
|
|
832
|
-
@api_key_last_expired_check = nil
|
|
833
|
-
|
|
834
230
|
def api_key_delete_expired(context, config, bypass_last_check: false)
|
|
835
|
-
|
|
836
|
-
unless bypass_last_check
|
|
837
|
-
now = Time.now
|
|
838
|
-
return if @api_key_last_expired_check && ((now - @api_key_last_expired_check) * 1000) < 10_000
|
|
839
|
-
|
|
840
|
-
@api_key_last_expired_check = now
|
|
841
|
-
end
|
|
842
|
-
|
|
843
|
-
expired = context.adapter.find_many(model: API_KEY_TABLE_NAME).select do |record|
|
|
844
|
-
record["expiresAt"] && record["expiresAt"] < Time.now
|
|
845
|
-
end
|
|
846
|
-
expired.each do |record|
|
|
847
|
-
context.adapter.delete(model: API_KEY_TABLE_NAME, where: [{field: "id", value: record["id"]}])
|
|
848
|
-
end
|
|
231
|
+
BetterAuth::APIKey::Routes.delete_expired(context, config, bypass_last_check: bypass_last_check)
|
|
849
232
|
end
|
|
850
233
|
|
|
851
234
|
def api_key_storage(config, context = nil)
|
|
852
|
-
config
|
|
235
|
+
BetterAuth::APIKey::Adapter.storage(config, context)
|
|
853
236
|
end
|
|
854
237
|
|
|
855
238
|
def api_key_storage_get(ctx, key, config)
|
|
856
|
-
|
|
857
|
-
raw && api_key_deserialize_storage_record(JSON.parse(raw))
|
|
858
|
-
rescue JSON::ParserError
|
|
859
|
-
nil
|
|
239
|
+
BetterAuth::APIKey::Adapter.get(ctx, key, config)
|
|
860
240
|
end
|
|
861
241
|
|
|
862
242
|
def api_key_storage_set(ctx, record, config)
|
|
863
|
-
|
|
864
|
-
unless storage
|
|
865
|
-
raise APIError.new("INTERNAL_SERVER_ERROR", message: "Secondary storage is required when storage mode is 'secondary-storage'")
|
|
866
|
-
end
|
|
867
|
-
|
|
868
|
-
serialized = JSON.generate(api_key_storage_record(record))
|
|
869
|
-
expires_at = api_key_normalize_time(record["expiresAt"])
|
|
870
|
-
ttl = expires_at ? [(expires_at - Time.now).to_i, 0].max : nil
|
|
871
|
-
reference_id = api_key_record_reference_id(record)
|
|
872
|
-
user_key = "api-key:by-ref:#{reference_id}"
|
|
873
|
-
|
|
874
|
-
api_key_storage_batch(storage) do
|
|
875
|
-
operations = [
|
|
876
|
-
-> { storage.set("api-key:#{record["key"]}", serialized, ttl) },
|
|
877
|
-
-> { storage.set("api-key:by-id:#{record["id"]}", serialized, ttl) }
|
|
878
|
-
]
|
|
879
|
-
operations << if config[:fallback_to_database]
|
|
880
|
-
# In fallback mode the ref list is a cache invalidated on writes
|
|
881
|
-
# to avoid races with concurrent writers of the same reference.
|
|
882
|
-
-> { storage.delete(user_key) }
|
|
883
|
-
else
|
|
884
|
-
-> { api_key_ref_list_add(storage, user_key, record["id"]) }
|
|
885
|
-
end
|
|
886
|
-
operations.each(&:call)
|
|
887
|
-
end
|
|
243
|
+
BetterAuth::APIKey::Adapter.set(ctx, record, config)
|
|
888
244
|
end
|
|
889
245
|
|
|
890
246
|
def api_key_storage_delete(ctx, record, config)
|
|
891
|
-
|
|
892
|
-
return unless storage
|
|
893
|
-
|
|
894
|
-
reference_id = api_key_record_reference_id(record)
|
|
895
|
-
user_key = "api-key:by-ref:#{reference_id}"
|
|
896
|
-
|
|
897
|
-
api_key_storage_batch(storage) do
|
|
898
|
-
operations = [
|
|
899
|
-
-> { storage.delete("api-key:#{record["key"]}") },
|
|
900
|
-
-> { storage.delete("api-key:by-id:#{record["id"]}") },
|
|
901
|
-
# Ruby-only legacy storage layout cleanup; upstream never wrote here.
|
|
902
|
-
-> { storage.delete("api-key:key:#{record["key"]}") },
|
|
903
|
-
-> { storage.delete("api-key:id:#{record["id"]}") }
|
|
904
|
-
]
|
|
905
|
-
operations << if config[:fallback_to_database]
|
|
906
|
-
-> { storage.delete(user_key) }
|
|
907
|
-
else
|
|
908
|
-
-> { api_key_ref_list_remove(storage, user_key, record["id"]) }
|
|
909
|
-
end
|
|
910
|
-
operations.each(&:call)
|
|
911
|
-
end
|
|
247
|
+
BetterAuth::APIKey::Adapter.delete(ctx, record, config)
|
|
912
248
|
end
|
|
913
249
|
|
|
914
250
|
def api_key_ref_list_add(storage, user_key, id)
|
|
915
|
-
|
|
916
|
-
ids << id unless ids.include?(id)
|
|
917
|
-
storage.set(user_key, JSON.generate(ids))
|
|
251
|
+
BetterAuth::APIKey::Adapter.ref_list_add(storage, user_key, id)
|
|
918
252
|
end
|
|
919
253
|
|
|
920
254
|
def api_key_ref_list_remove(storage, user_key, id)
|
|
921
|
-
|
|
922
|
-
ids.empty? ? storage.delete(user_key) : storage.set(user_key, JSON.generate(ids))
|
|
255
|
+
BetterAuth::APIKey::Adapter.ref_list_remove(storage, user_key, id)
|
|
923
256
|
end
|
|
924
257
|
|
|
925
258
|
def api_key_safe_parse_id_list(raw)
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
parsed = JSON.parse(raw.to_s)
|
|
929
|
-
parsed.is_a?(Array) ? parsed : []
|
|
930
|
-
rescue JSON::ParserError
|
|
931
|
-
[]
|
|
259
|
+
BetterAuth::APIKey::Adapter.safe_parse_id_list(raw)
|
|
932
260
|
end
|
|
933
261
|
|
|
934
262
|
def api_key_storage_batch(storage, &block)
|
|
935
|
-
|
|
936
|
-
storage.batch(&block)
|
|
937
|
-
else
|
|
938
|
-
block.call
|
|
939
|
-
end
|
|
263
|
+
BetterAuth::APIKey::Adapter.batch(storage, &block)
|
|
940
264
|
end
|
|
941
265
|
|
|
942
266
|
def api_key_storage_populate_reference(ctx, reference_id, records, config)
|
|
943
|
-
|
|
944
|
-
return unless storage
|
|
945
|
-
|
|
946
|
-
ids = []
|
|
947
|
-
records.each do |record|
|
|
948
|
-
serialized = JSON.generate(api_key_storage_record(record))
|
|
949
|
-
expires_at = api_key_normalize_time(record["expiresAt"])
|
|
950
|
-
ttl = expires_at ? [(expires_at - Time.now).to_i, 0].max : nil
|
|
951
|
-
storage.set("api-key:#{record["key"]}", serialized, ttl)
|
|
952
|
-
storage.set("api-key:by-id:#{record["id"]}", serialized, ttl)
|
|
953
|
-
ids << record["id"]
|
|
954
|
-
end
|
|
955
|
-
ids.empty? ? storage.delete("api-key:by-ref:#{reference_id}") : storage.set("api-key:by-ref:#{reference_id}", JSON.generate(ids))
|
|
267
|
+
BetterAuth::APIKey::Adapter.populate_reference(ctx, reference_id, records, config)
|
|
956
268
|
end
|
|
957
269
|
|
|
958
270
|
def api_key_storage_record(record)
|
|
959
|
-
|
|
271
|
+
BetterAuth::APIKey::Adapter.storage_record(record)
|
|
960
272
|
end
|
|
961
273
|
|
|
962
274
|
def api_key_deserialize_storage_record(record)
|
|
963
|
-
|
|
964
|
-
record[field] = api_key_normalize_time(record[field]) if record[field]
|
|
965
|
-
end
|
|
966
|
-
record
|
|
275
|
+
BetterAuth::APIKey::Adapter.deserialize_record(record)
|
|
967
276
|
end
|
|
968
277
|
|
|
969
278
|
def api_key_public(record, reveal_key: nil, include_key_field: false)
|
|
970
|
-
|
|
971
|
-
output = data.except(:key)
|
|
972
|
-
output[:configId] ||= api_key_record_config_id(record)
|
|
973
|
-
output[:referenceId] ||= api_key_record_reference_id(record)
|
|
974
|
-
output[:key] = reveal_key if include_key_field && reveal_key
|
|
975
|
-
output[:metadata] = api_key_decode_json(data[:metadata])
|
|
976
|
-
output[:permissions] = api_key_decode_json(data[:permissions])
|
|
977
|
-
output
|
|
279
|
+
BetterAuth::APIKey::Utils.public_record(record, reveal_key: reveal_key, include_key_field: include_key_field)
|
|
978
280
|
end
|
|
979
281
|
|
|
980
282
|
def api_key_migrate_legacy_metadata(ctx, record, config)
|
|
981
|
-
|
|
982
|
-
return record unless parsed.is_a?(Hash)
|
|
983
|
-
|
|
984
|
-
encoded = api_key_encode_json(parsed)
|
|
985
|
-
return record.merge("metadata" => encoded) if record["metadata"] == encoded
|
|
986
|
-
|
|
987
|
-
updated = record.merge("metadata" => encoded)
|
|
988
|
-
if config[:storage] == "database" || config[:fallback_to_database]
|
|
989
|
-
ctx.context.adapter.update(model: API_KEY_TABLE_NAME, where: [{field: "id", value: record["id"]}], update: {metadata: encoded})
|
|
990
|
-
end
|
|
991
|
-
api_key_storage_set(ctx, updated, config) if config[:storage] == "secondary-storage"
|
|
992
|
-
updated
|
|
283
|
+
BetterAuth::APIKey::Adapter.migrate_legacy_metadata(ctx, record, config)
|
|
993
284
|
end
|
|
994
285
|
|
|
995
286
|
def api_key_background_tasks?(ctx)
|
|
996
|
-
|
|
287
|
+
BetterAuth::APIKey::Utils.background_tasks?(ctx)
|
|
997
288
|
end
|
|
998
289
|
|
|
999
290
|
def api_key_auth_required?(ctx)
|
|
1000
|
-
|
|
291
|
+
BetterAuth::APIKey::Utils.auth_required?(ctx)
|
|
1001
292
|
end
|
|
1002
293
|
|
|
1003
294
|
def api_key_get_from_headers(ctx, config)
|
|
1004
|
-
|
|
1005
|
-
return getter.call(ctx) if getter.respond_to?(:call)
|
|
1006
|
-
|
|
1007
|
-
Array(config[:api_key_headers]).each do |header|
|
|
1008
|
-
value = ctx.headers[header.to_s.downcase]
|
|
1009
|
-
return value if value
|
|
1010
|
-
end
|
|
1011
|
-
nil
|
|
295
|
+
BetterAuth::APIKey::Keys.from_headers(ctx, config)
|
|
1012
296
|
end
|
|
1013
297
|
|
|
1014
298
|
def api_key_check_permissions!(record, required)
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
actual = api_key_decode_json(record["permissions"]) || {}
|
|
1018
|
-
result = Role.new(actual).authorize(required)
|
|
1019
|
-
unless result[:success]
|
|
1020
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["KEY_NOT_FOUND"], code: "KEY_NOT_FOUND")
|
|
1021
|
-
end
|
|
299
|
+
BetterAuth::APIKey::Validation.check_permissions!(record, required)
|
|
1022
300
|
end
|
|
1023
301
|
|
|
1024
302
|
def api_key_encode_json(value)
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
JSON.generate(value)
|
|
303
|
+
BetterAuth::APIKey::Utils.encode_json(value)
|
|
1028
304
|
end
|
|
1029
305
|
|
|
1030
306
|
def api_key_decode_json(value)
|
|
1031
|
-
|
|
1032
|
-
return value if value.is_a?(Hash)
|
|
1033
|
-
|
|
1034
|
-
parsed = JSON.parse(value.to_s)
|
|
1035
|
-
parsed.is_a?(String) ? api_key_decode_json(parsed) : parsed
|
|
1036
|
-
rescue JSON::ParserError
|
|
1037
|
-
nil
|
|
307
|
+
BetterAuth::APIKey::Utils.decode_json(value)
|
|
1038
308
|
end
|
|
1039
309
|
|
|
1040
310
|
def api_key_normalize_time(value)
|
|
1041
|
-
|
|
1042
|
-
return nil if value.nil?
|
|
1043
|
-
|
|
1044
|
-
Time.parse(value.to_s)
|
|
311
|
+
BetterAuth::APIKey::Utils.normalize_time(value)
|
|
1045
312
|
end
|
|
1046
313
|
end
|
|
1047
314
|
end
|