better_auth-api-key 0.2.0 → 0.5.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 +5 -0
- 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 +94 -802
- metadata +21 -1
|
@@ -3,740 +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 = normalize_hash(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 = normalize_hash(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
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["ORGANIZATION_ID_REQUIRED"]) if organization_id.to_s.empty?
|
|
383
|
-
|
|
384
|
-
user_id = session&.dig(:user, "id") || body[:user_id]
|
|
385
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["UNAUTHORIZED_SESSION"]) if user_id.to_s.empty?
|
|
386
|
-
|
|
387
|
-
api_key_check_org_permission!(ctx, user_id, organization_id, "create")
|
|
388
|
-
organization_id
|
|
389
|
-
elsif session && body[:user_id] && body[:user_id] != session[:user]["id"]
|
|
390
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["UNAUTHORIZED_SESSION"])
|
|
391
|
-
elsif session
|
|
392
|
-
|
|
393
|
-
session[:user]["id"]
|
|
394
|
-
else
|
|
395
|
-
user_id = body[:user_id]
|
|
396
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["UNAUTHORIZED_SESSION"]) if user_id.to_s.empty?
|
|
397
|
-
|
|
398
|
-
user_id
|
|
399
|
-
end
|
|
99
|
+
BetterAuth::APIKey::OrgAuthorization.create_reference_id!(ctx, body, session, config)
|
|
400
100
|
end
|
|
401
101
|
|
|
402
102
|
def api_key_record_reference_id(record)
|
|
403
|
-
record
|
|
103
|
+
BetterAuth::APIKey::Types.record_reference_id(record)
|
|
404
104
|
end
|
|
405
105
|
|
|
406
106
|
def api_key_record_user_id(record)
|
|
407
|
-
|
|
107
|
+
BetterAuth::APIKey::Types.record_user_id(record)
|
|
408
108
|
end
|
|
409
109
|
|
|
410
110
|
def api_key_record_config_id(record)
|
|
411
|
-
record
|
|
111
|
+
BetterAuth::APIKey::Types.record_config_id(record)
|
|
412
112
|
end
|
|
413
113
|
|
|
414
114
|
def api_key_default_permissions(config, reference_id, ctx)
|
|
415
|
-
|
|
416
|
-
return permissions.call(reference_id, ctx) if permissions.respond_to?(:call)
|
|
417
|
-
|
|
418
|
-
permissions
|
|
115
|
+
BetterAuth::APIKey::Types.default_permissions(config, reference_id, ctx)
|
|
419
116
|
end
|
|
420
117
|
|
|
421
118
|
def api_key_authorize_reference!(ctx, config, user_id, reference_id, action)
|
|
422
|
-
|
|
423
|
-
api_key_check_org_permission!(ctx, user_id, reference_id, action)
|
|
424
|
-
elsif reference_id != user_id
|
|
425
|
-
raise APIError.new("NOT_FOUND", message: API_KEY_ERROR_CODES["KEY_NOT_FOUND"])
|
|
426
|
-
end
|
|
119
|
+
BetterAuth::APIKey::OrgAuthorization.authorize_reference!(ctx, config, user_id, reference_id, action)
|
|
427
120
|
end
|
|
428
121
|
|
|
429
122
|
def api_key_check_org_permission!(ctx, user_id, organization_id, action)
|
|
430
|
-
|
|
431
|
-
unless org_plugin
|
|
432
|
-
raise APIError.new(
|
|
433
|
-
"INTERNAL_SERVER_ERROR",
|
|
434
|
-
message: API_KEY_ERROR_CODES["ORGANIZATION_PLUGIN_REQUIRED"],
|
|
435
|
-
code: "ORGANIZATION_PLUGIN_REQUIRED"
|
|
436
|
-
)
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
member = ctx.context.adapter.find_one(model: "member", where: [{field: "userId", value: user_id}, {field: "organizationId", value: organization_id}])
|
|
440
|
-
unless member
|
|
441
|
-
raise APIError.new(
|
|
442
|
-
"FORBIDDEN",
|
|
443
|
-
message: API_KEY_ERROR_CODES["USER_NOT_MEMBER_OF_ORGANIZATION"],
|
|
444
|
-
code: "USER_NOT_MEMBER_OF_ORGANIZATION"
|
|
445
|
-
)
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
return member if member["role"].to_s == (org_plugin.options[:creator_role] || "owner").to_s
|
|
449
|
-
|
|
450
|
-
permissions = {"apiKey" => [action]}
|
|
451
|
-
return member if BetterAuth::Plugins.organization_permission?(ctx, org_plugin.options, member["role"], permissions, organization_id)
|
|
452
|
-
|
|
453
|
-
raise APIError.new(
|
|
454
|
-
"FORBIDDEN",
|
|
455
|
-
message: API_KEY_ERROR_CODES["INSUFFICIENT_API_KEY_PERMISSIONS"],
|
|
456
|
-
code: "INSUFFICIENT_API_KEY_PERMISSIONS"
|
|
457
|
-
)
|
|
123
|
+
BetterAuth::APIKey::OrgAuthorization.check_permission!(ctx, user_id, organization_id, action)
|
|
458
124
|
end
|
|
459
125
|
|
|
460
126
|
def api_key_sort_records(records, sort_by, direction)
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
key = Schema.storage_key(sort_by)
|
|
464
|
-
sorted = records.sort_by { |record| record[key] || record[key.to_sym] || "" }
|
|
465
|
-
if direction.to_s.downcase == "desc"
|
|
466
|
-
sorted.reverse
|
|
467
|
-
else
|
|
468
|
-
sorted
|
|
469
|
-
end
|
|
127
|
+
BetterAuth::APIKey::Utils.sort_records(records, sort_by, direction)
|
|
470
128
|
end
|
|
471
129
|
|
|
472
130
|
def api_key_validate_list_query!(query)
|
|
473
|
-
|
|
474
|
-
next unless query.key?(key)
|
|
475
|
-
|
|
476
|
-
value = query[key]
|
|
477
|
-
raise APIError.new("BAD_REQUEST", message: "Invalid #{key}") unless value.to_s.match?(/\A\d+\z/)
|
|
478
|
-
end
|
|
479
|
-
|
|
480
|
-
direction = query[:sort_direction]
|
|
481
|
-
return if direction.nil? || %w[asc desc].include?(direction.to_s.downcase)
|
|
482
|
-
|
|
483
|
-
raise APIError.new("BAD_REQUEST", message: "Invalid sortDirection")
|
|
131
|
+
BetterAuth::APIKey::Utils.validate_list_query!(query)
|
|
484
132
|
end
|
|
485
133
|
|
|
486
134
|
def api_key_error_code(error)
|
|
487
|
-
|
|
135
|
+
BetterAuth::APIKey::Utils.error_code(error)
|
|
488
136
|
end
|
|
489
137
|
|
|
490
138
|
def api_key_error_payload(error)
|
|
491
|
-
|
|
492
|
-
return payload if payload.is_a?(Hash) && payload.key?(:details)
|
|
493
|
-
|
|
494
|
-
{message: error.message, code: api_key_error_code(error)}
|
|
139
|
+
BetterAuth::APIKey::Utils.error_payload(error)
|
|
495
140
|
end
|
|
496
141
|
|
|
497
142
|
def api_key_session_header_config(ctx, config)
|
|
498
|
-
|
|
499
|
-
entry[:enable_session_for_api_keys] && api_key_get_from_headers(ctx, entry)
|
|
500
|
-
end
|
|
143
|
+
BetterAuth::APIKey::Session.header_config(ctx, config)
|
|
501
144
|
end
|
|
502
145
|
|
|
503
146
|
def api_key_session_hook(ctx, config)
|
|
504
|
-
|
|
505
|
-
key = api_key_get_from_headers(ctx, config)
|
|
506
|
-
unless key.is_a?(String)
|
|
507
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["INVALID_API_KEY_GETTER_RETURN_TYPE"])
|
|
508
|
-
end
|
|
509
|
-
raise APIError.new("FORBIDDEN", message: API_KEY_ERROR_CODES["INVALID_API_KEY"]) if key.length < config[:default_key_length].to_i
|
|
510
|
-
|
|
511
|
-
if config[:custom_api_key_validator].respond_to?(:call) && !config[:custom_api_key_validator].call({ctx: ctx, key: key})
|
|
512
|
-
raise APIError.new("FORBIDDEN", message: API_KEY_ERROR_CODES["INVALID_API_KEY"])
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
record = api_key_validate!(ctx, key, config)
|
|
516
|
-
api_key_schedule_cleanup(ctx, config)
|
|
517
|
-
if config[:references].to_s != "user"
|
|
518
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["INVALID_REFERENCE_ID_FROM_API_KEY"])
|
|
519
|
-
end
|
|
520
|
-
reference_id = api_key_record_reference_id(record)
|
|
521
|
-
user = ctx.context.internal_adapter.find_user_by_id(reference_id)
|
|
522
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["INVALID_REFERENCE_ID_FROM_API_KEY"]) unless user
|
|
523
|
-
|
|
524
|
-
session = {
|
|
525
|
-
user: user,
|
|
526
|
-
session: {
|
|
527
|
-
"id" => record["id"],
|
|
528
|
-
"token" => key,
|
|
529
|
-
"userId" => reference_id,
|
|
530
|
-
"userAgent" => ctx.headers["user-agent"],
|
|
531
|
-
"ipAddress" => RequestIP.client_ip(ctx.request || ctx.headers, ctx.context.options),
|
|
532
|
-
"createdAt" => Time.now,
|
|
533
|
-
"updatedAt" => Time.now,
|
|
534
|
-
"expiresAt" => record["expiresAt"] || (Time.now + ctx.context.options.session[:expires_in].to_i)
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
ctx.context.set_current_session(session)
|
|
538
|
-
nil
|
|
147
|
+
BetterAuth::APIKey::Session.hook(ctx, config)
|
|
539
148
|
end
|
|
540
149
|
|
|
541
150
|
def api_key_validate!(ctx, key, config, permissions: nil)
|
|
542
|
-
|
|
543
|
-
record = api_key_find_by_hash(ctx, hashed, config)
|
|
544
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["INVALID_API_KEY"]) unless record
|
|
545
|
-
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])
|
|
546
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["KEY_DISABLED"]) if record["enabled"] == false
|
|
547
|
-
if record["expiresAt"] && record["expiresAt"] <= Time.now
|
|
548
|
-
api_key_schedule_record_delete(ctx, record, config)
|
|
549
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["KEY_EXPIRED"])
|
|
550
|
-
end
|
|
551
|
-
if record["remaining"].to_i <= 0 && !record["remaining"].nil? && record["refillAmount"].nil?
|
|
552
|
-
api_key_schedule_record_delete(ctx, record, config)
|
|
553
|
-
raise APIError.new("TOO_MANY_REQUESTS", message: API_KEY_ERROR_CODES["USAGE_EXCEEDED"])
|
|
554
|
-
end
|
|
555
|
-
|
|
556
|
-
api_key_check_permissions!(record, permissions)
|
|
557
|
-
update = api_key_usage_update(record, config)
|
|
558
|
-
updated = api_key_update_record(ctx, record, update, config, defer: true)
|
|
559
|
-
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)
|
|
560
152
|
end
|
|
561
153
|
|
|
562
154
|
def api_key_usage_update(record, config)
|
|
563
|
-
|
|
564
|
-
update = {lastRequest: now, updatedAt: now}
|
|
565
|
-
|
|
566
|
-
if (try_again_in = api_key_rate_limit_try_again_in(record, config, now))
|
|
567
|
-
raise APIError.new(
|
|
568
|
-
"UNAUTHORIZED",
|
|
569
|
-
message: API_KEY_ERROR_CODES["RATE_LIMIT_EXCEEDED"],
|
|
570
|
-
code: "RATE_LIMITED",
|
|
571
|
-
body: {
|
|
572
|
-
message: API_KEY_ERROR_CODES["RATE_LIMIT_EXCEEDED"],
|
|
573
|
-
code: "RATE_LIMITED",
|
|
574
|
-
details: {tryAgainIn: try_again_in}
|
|
575
|
-
}
|
|
576
|
-
)
|
|
577
|
-
end
|
|
578
|
-
update[:requestCount] = api_key_next_request_count(record, now) if api_key_rate_limit_counts_requests?(record, config)
|
|
579
|
-
|
|
580
|
-
remaining = record["remaining"]
|
|
581
|
-
if !remaining.nil?
|
|
582
|
-
if remaining.to_i <= 0 && record["refillAmount"] && record["refillInterval"]
|
|
583
|
-
last_refill = api_key_normalize_time(record["lastRefillAt"] || record["createdAt"])
|
|
584
|
-
if !last_refill || ((now - last_refill) * 1000) > record["refillInterval"].to_i
|
|
585
|
-
remaining = record["refillAmount"].to_i
|
|
586
|
-
update[:lastRefillAt] = now
|
|
587
|
-
end
|
|
588
|
-
end
|
|
589
|
-
raise APIError.new("TOO_MANY_REQUESTS", message: API_KEY_ERROR_CODES["USAGE_EXCEEDED"]) if remaining.to_i <= 0
|
|
590
|
-
|
|
591
|
-
update[:remaining] = remaining.to_i - 1
|
|
592
|
-
end
|
|
593
|
-
update
|
|
155
|
+
BetterAuth::APIKey::Validation.usage_update(record, config)
|
|
594
156
|
end
|
|
595
157
|
|
|
596
158
|
def api_key_rate_limit_try_again_in(record, config, now)
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
window = record["rateLimitTimeWindow"]
|
|
600
|
-
max = record["rateLimitMax"]
|
|
601
|
-
return nil if window.nil? || max.nil?
|
|
602
|
-
|
|
603
|
-
last = api_key_normalize_time(record["lastRequest"])
|
|
604
|
-
return nil unless last
|
|
605
|
-
|
|
606
|
-
elapsed = (now - last) * 1000
|
|
607
|
-
return nil if elapsed > window.to_i
|
|
608
|
-
return nil unless record["requestCount"].to_i >= max.to_i
|
|
609
|
-
|
|
610
|
-
(window.to_i - elapsed).ceil
|
|
159
|
+
BetterAuth::APIKey::RateLimit.try_again_in(record, config, now)
|
|
611
160
|
end
|
|
612
161
|
|
|
613
162
|
def api_key_rate_limit_counts_requests?(record, config)
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
!record["rateLimitTimeWindow"].nil? && !record["rateLimitMax"].nil?
|
|
163
|
+
BetterAuth::APIKey::RateLimit.counts_requests?(record, config)
|
|
617
164
|
end
|
|
618
165
|
|
|
619
166
|
def api_key_next_request_count(record, now)
|
|
620
|
-
|
|
621
|
-
window = record["rateLimitTimeWindow"].to_i
|
|
622
|
-
if last && window.positive? && ((now - last) * 1000) <= window
|
|
623
|
-
record["requestCount"].to_i + 1
|
|
624
|
-
else
|
|
625
|
-
1
|
|
626
|
-
end
|
|
167
|
+
BetterAuth::APIKey::RateLimit.next_request_count(record, now)
|
|
627
168
|
end
|
|
628
169
|
|
|
629
170
|
def api_key_validate_create_update!(body, config, create:, client:)
|
|
630
|
-
|
|
631
|
-
if create && config[:require_name] && name.to_s.empty?
|
|
632
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["NAME_REQUIRED"])
|
|
633
|
-
end
|
|
634
|
-
if name && !name.to_s.length.between?(config[:minimum_name_length].to_i, config[:maximum_name_length].to_i)
|
|
635
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["INVALID_NAME_LENGTH"])
|
|
636
|
-
end
|
|
637
|
-
prefix = body[:prefix]
|
|
638
|
-
if prefix && !prefix.to_s.length.between?(config[:minimum_prefix_length].to_i, config[:maximum_prefix_length].to_i)
|
|
639
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["INVALID_PREFIX_LENGTH"])
|
|
640
|
-
end
|
|
641
|
-
if prefix && !prefix.to_s.match?(/\A[a-zA-Z0-9_-]+\z/)
|
|
642
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["INVALID_PREFIX_LENGTH"])
|
|
643
|
-
end
|
|
644
|
-
if body.key?(:remaining) && !body[:remaining].nil?
|
|
645
|
-
minimum = create ? 0 : 1
|
|
646
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["INVALID_REMAINING"]) if body[:remaining].to_i < minimum
|
|
647
|
-
end
|
|
648
|
-
if body[:metadata] && (create || config[:enable_metadata])
|
|
649
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["METADATA_DISABLED"]) unless config[:enable_metadata]
|
|
650
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["INVALID_METADATA_TYPE"]) unless body[:metadata].nil? || body[:metadata].is_a?(Hash)
|
|
651
|
-
end
|
|
652
|
-
server_only_keys = %i[refill_amount refill_interval rate_limit_max rate_limit_time_window rate_limit_enabled remaining permissions]
|
|
653
|
-
if client && server_only_keys.any? { |key| (create && key == :remaining) ? !body[:remaining].nil? : body.key?(key) }
|
|
654
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["SERVER_ONLY_PROPERTY"])
|
|
655
|
-
end
|
|
656
|
-
amount_present = body.key?(:refill_amount)
|
|
657
|
-
interval_present = body.key?(:refill_interval)
|
|
658
|
-
if amount_present && !interval_present
|
|
659
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["REFILL_AMOUNT_AND_INTERVAL_REQUIRED"])
|
|
660
|
-
end
|
|
661
|
-
if interval_present && !amount_present
|
|
662
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["REFILL_INTERVAL_AND_AMOUNT_REQUIRED"])
|
|
663
|
-
end
|
|
664
|
-
if body.key?(:expires_in)
|
|
665
|
-
raise APIError.new("BAD_REQUEST", message: API_KEY_ERROR_CODES["KEY_DISABLED_EXPIRATION"]) if config[:key_expiration][:disable_custom_expires_time]
|
|
666
|
-
return if body[:expires_in].nil?
|
|
667
|
-
|
|
668
|
-
days = body[:expires_in].to_f / 86_400
|
|
669
|
-
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
|
|
670
|
-
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
|
|
671
|
-
end
|
|
171
|
+
BetterAuth::APIKey::Validation.validate_create_update!(body, config, create: create, client: client)
|
|
672
172
|
end
|
|
673
173
|
|
|
674
174
|
def api_key_update_payload(body, config)
|
|
675
|
-
|
|
676
|
-
update[:name] = body[:name] if body.key?(:name)
|
|
677
|
-
update[:enabled] = body[:enabled] unless body[:enabled].nil?
|
|
678
|
-
update[:remaining] = body[:remaining] if body.key?(:remaining)
|
|
679
|
-
update[:refillAmount] = body[:refill_amount] if body.key?(:refill_amount)
|
|
680
|
-
update[:refillInterval] = body[:refill_interval] if body.key?(:refill_interval)
|
|
681
|
-
update[:rateLimitEnabled] = body[:rate_limit_enabled] if body.key?(:rate_limit_enabled)
|
|
682
|
-
update[:rateLimitTimeWindow] = body[:rate_limit_time_window] if body.key?(:rate_limit_time_window)
|
|
683
|
-
update[:rateLimitMax] = body[:rate_limit_max] if body.key?(:rate_limit_max)
|
|
684
|
-
update[:expiresAt] = body[:expires_in].nil? ? nil : Time.now + body[:expires_in].to_i if body.key?(:expires_in)
|
|
685
|
-
update[:metadata] = api_key_encode_json(body[:metadata]) if body.key?(:metadata) && config[:enable_metadata]
|
|
686
|
-
update[:permissions] = api_key_encode_json(body[:permissions]) if body.key?(:permissions)
|
|
687
|
-
update
|
|
175
|
+
BetterAuth::APIKey::Validation.update_payload(body, config)
|
|
688
176
|
end
|
|
689
177
|
|
|
690
178
|
def api_key_generate_key(config, prefix)
|
|
691
|
-
|
|
692
|
-
return generator.call({length: config[:default_key_length], prefix: prefix}) if generator.respond_to?(:call)
|
|
693
|
-
|
|
694
|
-
alphabet = [*("a".."z"), *("A".."Z")]
|
|
695
|
-
"#{prefix}#{Array.new(config[:default_key_length].to_i) { alphabet[SecureRandom.random_number(alphabet.length)] }.join}"
|
|
179
|
+
BetterAuth::APIKey::Keys.generate(config, prefix)
|
|
696
180
|
end
|
|
697
181
|
|
|
698
182
|
def api_key_hash(key, config)
|
|
699
|
-
|
|
183
|
+
BetterAuth::APIKey::Keys.hash(key, config)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def api_key_normalize_body(raw)
|
|
187
|
+
BetterAuth::APIKey::Keys.normalize_body(raw)
|
|
700
188
|
end
|
|
701
189
|
|
|
702
190
|
def api_key_expires_at(body, config)
|
|
703
|
-
|
|
704
|
-
Time.now + body[:expires_in].to_i unless body[:expires_in].nil?
|
|
705
|
-
elsif config[:key_expiration][:default_expires_in]
|
|
706
|
-
Time.now + config[:key_expiration][:default_expires_in].to_i
|
|
707
|
-
end
|
|
191
|
+
BetterAuth::APIKey::Keys.expires_at(body, config)
|
|
708
192
|
end
|
|
709
193
|
|
|
710
194
|
def api_key_store(ctx, data, config)
|
|
711
|
-
|
|
712
|
-
if config[:storage] == "database" || config[:fallback_to_database]
|
|
713
|
-
record = ctx.context.adapter.create(model: API_KEY_TABLE_NAME, data: data)
|
|
714
|
-
end
|
|
715
|
-
record ||= data.transform_keys { |key| Schema.storage_key(key) }.merge("id" => SecureRandom.hex(16))
|
|
716
|
-
api_key_storage_set(ctx, record, config) if config[:storage] == "secondary-storage"
|
|
717
|
-
record
|
|
195
|
+
BetterAuth::APIKey::Adapter.store(ctx, data, config)
|
|
718
196
|
end
|
|
719
197
|
|
|
720
198
|
def api_key_find_by_hash(ctx, hashed, config)
|
|
721
|
-
|
|
722
|
-
record = api_key_storage_get(ctx, "api-key:#{hashed}", config) || api_key_storage_get(ctx, "api-key:key:#{hashed}", config)
|
|
723
|
-
return record if record
|
|
724
|
-
return nil unless config[:fallback_to_database]
|
|
725
|
-
end
|
|
726
|
-
record = ctx.context.adapter.find_one(model: API_KEY_TABLE_NAME, where: [{field: "key", value: hashed}])
|
|
727
|
-
api_key_storage_set(ctx, record, config) if record && config[:storage] == "secondary-storage" && config[:fallback_to_database]
|
|
728
|
-
record
|
|
199
|
+
BetterAuth::APIKey::Adapter.find_by_hash(ctx, hashed, config)
|
|
729
200
|
end
|
|
730
201
|
|
|
731
202
|
def api_key_find_by_id(ctx, id, config)
|
|
732
|
-
|
|
733
|
-
record = api_key_storage_get(ctx, "api-key:by-id:#{id}", config) || api_key_storage_get(ctx, "api-key:id:#{id}", config)
|
|
734
|
-
return record if record
|
|
735
|
-
return nil unless config[:fallback_to_database]
|
|
736
|
-
end
|
|
737
|
-
record = ctx.context.adapter.find_one(model: API_KEY_TABLE_NAME, where: [{field: "id", value: id}])
|
|
738
|
-
api_key_storage_set(ctx, record, config) if record && config[:storage] == "secondary-storage" && config[:fallback_to_database]
|
|
739
|
-
record
|
|
203
|
+
BetterAuth::APIKey::Adapter.find_by_id(ctx, id, config)
|
|
740
204
|
end
|
|
741
205
|
|
|
742
206
|
def api_key_list_for_user(ctx, user_id, config)
|
|
@@ -744,279 +208,107 @@ module BetterAuth
|
|
|
744
208
|
end
|
|
745
209
|
|
|
746
210
|
def api_key_list_for_reference(ctx, reference_id, config)
|
|
747
|
-
|
|
748
|
-
begin
|
|
749
|
-
storage = api_key_storage(config, ctx.context)
|
|
750
|
-
ids = JSON.parse((storage&.get("api-key:by-ref:#{reference_id}") || storage&.get("api-key:user:#{reference_id}")).to_s)
|
|
751
|
-
records = ids.filter_map { |id| api_key_find_by_id(ctx, id, config) }
|
|
752
|
-
return records unless records.empty? && config[:fallback_to_database]
|
|
753
|
-
rescue JSON::ParserError, NoMethodError
|
|
754
|
-
return [] unless config[:fallback_to_database]
|
|
755
|
-
end
|
|
756
|
-
end
|
|
757
|
-
records = ctx.context.adapter.find_many(model: API_KEY_TABLE_NAME, where: [{field: "referenceId", value: reference_id}])
|
|
758
|
-
legacy = ctx.context.adapter.find_many(model: API_KEY_TABLE_NAME, where: [{field: "userId", value: reference_id}])
|
|
759
|
-
combined = (records + legacy).uniq { |record| record["id"] }
|
|
760
|
-
api_key_storage_populate_reference(ctx, reference_id, combined, config) if config[:storage] == "secondary-storage" && config[:fallback_to_database]
|
|
761
|
-
combined
|
|
211
|
+
BetterAuth::APIKey::Adapter.list_for_reference(ctx, reference_id, config)
|
|
762
212
|
end
|
|
763
213
|
|
|
764
214
|
def api_key_update_record(ctx, record, update, config, defer: false)
|
|
765
|
-
|
|
766
|
-
updated = nil
|
|
767
|
-
if config[:storage] == "database" || config[:fallback_to_database]
|
|
768
|
-
updated = ctx.context.adapter.update(model: API_KEY_TABLE_NAME, where: [{field: "id", value: record["id"]}], update: update)
|
|
769
|
-
end
|
|
770
|
-
updated ||= record.merge(update.transform_keys { |key| Schema.storage_key(key) })
|
|
771
|
-
api_key_storage_set(ctx, updated, config) if config[:storage] == "secondary-storage"
|
|
772
|
-
updated
|
|
773
|
-
end
|
|
774
|
-
|
|
775
|
-
if defer && config[:defer_updates] && api_key_background_tasks?(ctx)
|
|
776
|
-
scheduled = record.merge(update.transform_keys { |key| Schema.storage_key(key) })
|
|
777
|
-
ctx.context.run_in_background(performer)
|
|
778
|
-
scheduled
|
|
779
|
-
else
|
|
780
|
-
performer.call
|
|
781
|
-
end
|
|
215
|
+
BetterAuth::APIKey::Adapter.update_record(ctx, record, update, config, defer: defer)
|
|
782
216
|
end
|
|
783
217
|
|
|
784
218
|
def api_key_delete_record(ctx, record, config)
|
|
785
|
-
|
|
786
|
-
api_key_storage_delete(ctx, record, config) if config[:storage] == "secondary-storage"
|
|
219
|
+
BetterAuth::APIKey::Adapter.delete_record(ctx, record, config)
|
|
787
220
|
end
|
|
788
221
|
|
|
789
222
|
def api_key_schedule_record_delete(ctx, record, config)
|
|
790
|
-
|
|
791
|
-
if config[:defer_updates] && api_key_background_tasks?(ctx)
|
|
792
|
-
ctx.context.run_in_background(task)
|
|
793
|
-
else
|
|
794
|
-
task.call
|
|
795
|
-
end
|
|
223
|
+
BetterAuth::APIKey::Adapter.schedule_record_delete(ctx, record, config)
|
|
796
224
|
end
|
|
797
225
|
|
|
798
226
|
def api_key_schedule_cleanup(ctx, config)
|
|
799
|
-
|
|
800
|
-
if config[:defer_updates] && api_key_background_tasks?(ctx)
|
|
801
|
-
ctx.context.run_in_background(task)
|
|
802
|
-
else
|
|
803
|
-
task.call
|
|
804
|
-
end
|
|
227
|
+
BetterAuth::APIKey::Routes.schedule_cleanup(ctx, config)
|
|
805
228
|
end
|
|
806
229
|
|
|
807
|
-
@api_key_last_expired_check = nil
|
|
808
|
-
|
|
809
230
|
def api_key_delete_expired(context, config, bypass_last_check: false)
|
|
810
|
-
|
|
811
|
-
unless bypass_last_check
|
|
812
|
-
now = Time.now
|
|
813
|
-
return if @api_key_last_expired_check && ((now - @api_key_last_expired_check) * 1000) < 10_000
|
|
814
|
-
|
|
815
|
-
@api_key_last_expired_check = now
|
|
816
|
-
end
|
|
817
|
-
|
|
818
|
-
expired = context.adapter.find_many(model: API_KEY_TABLE_NAME).select do |record|
|
|
819
|
-
record["expiresAt"] && record["expiresAt"] < Time.now
|
|
820
|
-
end
|
|
821
|
-
expired.each do |record|
|
|
822
|
-
context.adapter.delete(model: API_KEY_TABLE_NAME, where: [{field: "id", value: record["id"]}])
|
|
823
|
-
end
|
|
231
|
+
BetterAuth::APIKey::Routes.delete_expired(context, config, bypass_last_check: bypass_last_check)
|
|
824
232
|
end
|
|
825
233
|
|
|
826
234
|
def api_key_storage(config, context = nil)
|
|
827
|
-
config
|
|
235
|
+
BetterAuth::APIKey::Adapter.storage(config, context)
|
|
828
236
|
end
|
|
829
237
|
|
|
830
238
|
def api_key_storage_get(ctx, key, config)
|
|
831
|
-
|
|
832
|
-
raw && api_key_deserialize_storage_record(JSON.parse(raw))
|
|
833
|
-
rescue JSON::ParserError
|
|
834
|
-
nil
|
|
239
|
+
BetterAuth::APIKey::Adapter.get(ctx, key, config)
|
|
835
240
|
end
|
|
836
241
|
|
|
837
242
|
def api_key_storage_set(ctx, record, config)
|
|
838
|
-
|
|
839
|
-
unless storage
|
|
840
|
-
raise APIError.new("INTERNAL_SERVER_ERROR", message: "Secondary storage is required when storage mode is 'secondary-storage'")
|
|
841
|
-
end
|
|
842
|
-
|
|
843
|
-
serialized = JSON.generate(api_key_storage_record(record))
|
|
844
|
-
expires_at = api_key_normalize_time(record["expiresAt"])
|
|
845
|
-
ttl = expires_at ? [(expires_at - Time.now).to_i, 0].max : nil
|
|
846
|
-
reference_id = api_key_record_reference_id(record)
|
|
847
|
-
user_key = "api-key:by-ref:#{reference_id}"
|
|
848
|
-
|
|
849
|
-
api_key_storage_batch(storage) do
|
|
850
|
-
operations = [
|
|
851
|
-
-> { storage.set("api-key:#{record["key"]}", serialized, ttl) },
|
|
852
|
-
-> { storage.set("api-key:by-id:#{record["id"]}", serialized, ttl) }
|
|
853
|
-
]
|
|
854
|
-
operations << if config[:fallback_to_database]
|
|
855
|
-
# In fallback mode the ref list is a cache invalidated on writes
|
|
856
|
-
# to avoid races with concurrent writers of the same reference.
|
|
857
|
-
-> { storage.delete(user_key) }
|
|
858
|
-
else
|
|
859
|
-
-> { api_key_ref_list_add(storage, user_key, record["id"]) }
|
|
860
|
-
end
|
|
861
|
-
operations.each(&:call)
|
|
862
|
-
end
|
|
243
|
+
BetterAuth::APIKey::Adapter.set(ctx, record, config)
|
|
863
244
|
end
|
|
864
245
|
|
|
865
246
|
def api_key_storage_delete(ctx, record, config)
|
|
866
|
-
|
|
867
|
-
return unless storage
|
|
868
|
-
|
|
869
|
-
reference_id = api_key_record_reference_id(record)
|
|
870
|
-
user_key = "api-key:by-ref:#{reference_id}"
|
|
871
|
-
|
|
872
|
-
api_key_storage_batch(storage) do
|
|
873
|
-
operations = [
|
|
874
|
-
-> { storage.delete("api-key:#{record["key"]}") },
|
|
875
|
-
-> { storage.delete("api-key:by-id:#{record["id"]}") },
|
|
876
|
-
# Ruby-only legacy storage layout cleanup; upstream never wrote here.
|
|
877
|
-
-> { storage.delete("api-key:key:#{record["key"]}") },
|
|
878
|
-
-> { storage.delete("api-key:id:#{record["id"]}") }
|
|
879
|
-
]
|
|
880
|
-
operations << if config[:fallback_to_database]
|
|
881
|
-
-> { storage.delete(user_key) }
|
|
882
|
-
else
|
|
883
|
-
-> { api_key_ref_list_remove(storage, user_key, record["id"]) }
|
|
884
|
-
end
|
|
885
|
-
operations.each(&:call)
|
|
886
|
-
end
|
|
247
|
+
BetterAuth::APIKey::Adapter.delete(ctx, record, config)
|
|
887
248
|
end
|
|
888
249
|
|
|
889
250
|
def api_key_ref_list_add(storage, user_key, id)
|
|
890
|
-
|
|
891
|
-
ids << id unless ids.include?(id)
|
|
892
|
-
storage.set(user_key, JSON.generate(ids))
|
|
251
|
+
BetterAuth::APIKey::Adapter.ref_list_add(storage, user_key, id)
|
|
893
252
|
end
|
|
894
253
|
|
|
895
254
|
def api_key_ref_list_remove(storage, user_key, id)
|
|
896
|
-
|
|
897
|
-
ids.empty? ? storage.delete(user_key) : storage.set(user_key, JSON.generate(ids))
|
|
255
|
+
BetterAuth::APIKey::Adapter.ref_list_remove(storage, user_key, id)
|
|
898
256
|
end
|
|
899
257
|
|
|
900
258
|
def api_key_safe_parse_id_list(raw)
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
parsed = JSON.parse(raw.to_s)
|
|
904
|
-
parsed.is_a?(Array) ? parsed : []
|
|
905
|
-
rescue JSON::ParserError
|
|
906
|
-
[]
|
|
259
|
+
BetterAuth::APIKey::Adapter.safe_parse_id_list(raw)
|
|
907
260
|
end
|
|
908
261
|
|
|
909
262
|
def api_key_storage_batch(storage, &block)
|
|
910
|
-
|
|
911
|
-
storage.batch(&block)
|
|
912
|
-
else
|
|
913
|
-
block.call
|
|
914
|
-
end
|
|
263
|
+
BetterAuth::APIKey::Adapter.batch(storage, &block)
|
|
915
264
|
end
|
|
916
265
|
|
|
917
266
|
def api_key_storage_populate_reference(ctx, reference_id, records, config)
|
|
918
|
-
|
|
919
|
-
return unless storage
|
|
920
|
-
|
|
921
|
-
ids = []
|
|
922
|
-
records.each do |record|
|
|
923
|
-
serialized = JSON.generate(api_key_storage_record(record))
|
|
924
|
-
expires_at = api_key_normalize_time(record["expiresAt"])
|
|
925
|
-
ttl = expires_at ? [(expires_at - Time.now).to_i, 0].max : nil
|
|
926
|
-
storage.set("api-key:#{record["key"]}", serialized, ttl)
|
|
927
|
-
storage.set("api-key:by-id:#{record["id"]}", serialized, ttl)
|
|
928
|
-
ids << record["id"]
|
|
929
|
-
end
|
|
930
|
-
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)
|
|
931
268
|
end
|
|
932
269
|
|
|
933
270
|
def api_key_storage_record(record)
|
|
934
|
-
|
|
271
|
+
BetterAuth::APIKey::Adapter.storage_record(record)
|
|
935
272
|
end
|
|
936
273
|
|
|
937
274
|
def api_key_deserialize_storage_record(record)
|
|
938
|
-
|
|
939
|
-
record[field] = api_key_normalize_time(record[field]) if record[field]
|
|
940
|
-
end
|
|
941
|
-
record
|
|
275
|
+
BetterAuth::APIKey::Adapter.deserialize_record(record)
|
|
942
276
|
end
|
|
943
277
|
|
|
944
278
|
def api_key_public(record, reveal_key: nil, include_key_field: false)
|
|
945
|
-
|
|
946
|
-
output = data.except(:key)
|
|
947
|
-
output[:configId] ||= api_key_record_config_id(record)
|
|
948
|
-
output[:referenceId] ||= api_key_record_reference_id(record)
|
|
949
|
-
output[:key] = reveal_key if include_key_field && reveal_key
|
|
950
|
-
output[:metadata] = api_key_decode_json(data[:metadata])
|
|
951
|
-
output[:permissions] = api_key_decode_json(data[:permissions])
|
|
952
|
-
output
|
|
279
|
+
BetterAuth::APIKey::Utils.public_record(record, reveal_key: reveal_key, include_key_field: include_key_field)
|
|
953
280
|
end
|
|
954
281
|
|
|
955
282
|
def api_key_migrate_legacy_metadata(ctx, record, config)
|
|
956
|
-
|
|
957
|
-
return record unless parsed.is_a?(Hash)
|
|
958
|
-
|
|
959
|
-
encoded = api_key_encode_json(parsed)
|
|
960
|
-
return record.merge("metadata" => encoded) if record["metadata"] == encoded
|
|
961
|
-
|
|
962
|
-
updated = record.merge("metadata" => encoded)
|
|
963
|
-
if config[:storage] == "database" || config[:fallback_to_database]
|
|
964
|
-
ctx.context.adapter.update(model: API_KEY_TABLE_NAME, where: [{field: "id", value: record["id"]}], update: {metadata: encoded})
|
|
965
|
-
end
|
|
966
|
-
api_key_storage_set(ctx, updated, config) if config[:storage] == "secondary-storage"
|
|
967
|
-
updated
|
|
283
|
+
BetterAuth::APIKey::Adapter.migrate_legacy_metadata(ctx, record, config)
|
|
968
284
|
end
|
|
969
285
|
|
|
970
286
|
def api_key_background_tasks?(ctx)
|
|
971
|
-
|
|
287
|
+
BetterAuth::APIKey::Utils.background_tasks?(ctx)
|
|
972
288
|
end
|
|
973
289
|
|
|
974
290
|
def api_key_auth_required?(ctx)
|
|
975
|
-
|
|
291
|
+
BetterAuth::APIKey::Utils.auth_required?(ctx)
|
|
976
292
|
end
|
|
977
293
|
|
|
978
294
|
def api_key_get_from_headers(ctx, config)
|
|
979
|
-
|
|
980
|
-
return getter.call(ctx) if getter.respond_to?(:call)
|
|
981
|
-
|
|
982
|
-
Array(config[:api_key_headers]).each do |header|
|
|
983
|
-
value = ctx.headers[header.to_s.downcase]
|
|
984
|
-
return value if value
|
|
985
|
-
end
|
|
986
|
-
nil
|
|
295
|
+
BetterAuth::APIKey::Keys.from_headers(ctx, config)
|
|
987
296
|
end
|
|
988
297
|
|
|
989
298
|
def api_key_check_permissions!(record, required)
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
actual = api_key_decode_json(record["permissions"]) || {}
|
|
993
|
-
result = Role.new(actual).authorize(required)
|
|
994
|
-
unless result[:success]
|
|
995
|
-
raise APIError.new("UNAUTHORIZED", message: API_KEY_ERROR_CODES["KEY_NOT_FOUND"], code: "KEY_NOT_FOUND")
|
|
996
|
-
end
|
|
299
|
+
BetterAuth::APIKey::Validation.check_permissions!(record, required)
|
|
997
300
|
end
|
|
998
301
|
|
|
999
302
|
def api_key_encode_json(value)
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
JSON.generate(value)
|
|
303
|
+
BetterAuth::APIKey::Utils.encode_json(value)
|
|
1003
304
|
end
|
|
1004
305
|
|
|
1005
306
|
def api_key_decode_json(value)
|
|
1006
|
-
|
|
1007
|
-
return value if value.is_a?(Hash)
|
|
1008
|
-
|
|
1009
|
-
parsed = JSON.parse(value.to_s)
|
|
1010
|
-
parsed.is_a?(String) ? api_key_decode_json(parsed) : parsed
|
|
1011
|
-
rescue JSON::ParserError
|
|
1012
|
-
nil
|
|
307
|
+
BetterAuth::APIKey::Utils.decode_json(value)
|
|
1013
308
|
end
|
|
1014
309
|
|
|
1015
310
|
def api_key_normalize_time(value)
|
|
1016
|
-
|
|
1017
|
-
return nil if value.nil?
|
|
1018
|
-
|
|
1019
|
-
Time.parse(value.to_s)
|
|
311
|
+
BetterAuth::APIKey::Utils.normalize_time(value)
|
|
1020
312
|
end
|
|
1021
313
|
end
|
|
1022
314
|
end
|