Module: BetterAuth::APIKey::Adapter
- Defined in:
- lib/better_auth/api_key/adapter.rb
Constant Summary collapse
- HASH_STORAGE_PREFIX =
"api-key:"- ID_STORAGE_PREFIX =
"api-key:by-id:"- REFERENCE_STORAGE_PREFIX =
"api-key:by-ref:"
Class Method Summary collapse
- .batch(storage_instance, &block) ⇒ Object
- .delete(ctx, record, config) ⇒ Object
- .delete_record(ctx, record, config) ⇒ Object
- .deserialize_record(record) ⇒ Object
- .find_by_hash(ctx, hashed, config) ⇒ Object
- .find_by_id(ctx, id, config) ⇒ Object
- .get(ctx, key, config) ⇒ Object
- .list_for_reference(ctx, reference_id, config) ⇒ Object
- .migrate_legacy_metadata(ctx, record, config) ⇒ Object
- .populate_reference(ctx, reference_id, records, config) ⇒ Object
- .ref_list_add(storage_instance, reference_key, id) ⇒ Object
- .ref_list_remove(storage_instance, reference_key, id) ⇒ Object
- .safe_parse_id_list(raw) ⇒ Object
- .schedule_record_delete(ctx, record, config) ⇒ Object
- .set(ctx, record, config) ⇒ Object
- .storage(config, context = nil) ⇒ Object
- .storage_key_by_hash(hashed_key) ⇒ Object
- .storage_key_by_id(id) ⇒ Object
- .storage_key_by_reference(reference_id) ⇒ Object
- .storage_record(record) ⇒ Object
- .store(ctx, data, config) ⇒ Object
- .update_record(ctx, record, update, config, defer: false) ⇒ Object
Class Method Details
.batch(storage_instance, &block) ⇒ Object
208 209 210 211 212 213 214 |
# File 'lib/better_auth/api_key/adapter.rb', line 208 def batch(storage_instance, &block) if storage_instance.respond_to?(:batch) storage_instance.batch(&block) else block.call end end |
.delete(ctx, record, config) ⇒ Object
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/better_auth/api_key/adapter.rb', line 164 def delete(ctx, record, config) storage_instance = storage(config, ctx.context) return unless storage_instance reference_id = BetterAuth::Plugins.api_key_record_reference_id(record) reference_key = storage_key_by_reference(reference_id) batch(storage_instance) do operations = [ -> { storage_instance.delete(storage_key_by_hash(record["key"])) }, -> { storage_instance.delete(storage_key_by_id(record["id"])) }, # Ruby-only legacy storage layout cleanup; upstream never wrote here. -> { storage_instance.delete("api-key:key:#{record["key"]}") }, -> { storage_instance.delete("api-key:id:#{record["id"]}") } ] operations << if config[:fallback_to_database] -> { storage_instance.delete(reference_key) } else -> { ref_list_remove(storage_instance, reference_key, record["id"]) } end operations.each(&:call) end end |
.delete_record(ctx, record, config) ⇒ Object
98 99 100 101 |
# File 'lib/better_auth/api_key/adapter.rb', line 98 def delete_record(ctx, record, config) ctx.context.adapter.delete(model: BetterAuth::Plugins::API_KEY_TABLE_NAME, where: [{field: "id", value: record["id"]}]) if config[:storage] == "database" || config[:fallback_to_database] delete(ctx, record, config) if config[:storage] == "secondary-storage" end |
.deserialize_record(record) ⇒ Object
237 238 239 240 241 242 |
# File 'lib/better_auth/api_key/adapter.rb', line 237 def deserialize_record(record) %w[createdAt updatedAt expiresAt lastRefillAt lastRequest].each do |field| record[field] = BetterAuth::APIKey::Utils.normalize_time(record[field]) if record[field] end record end |
.find_by_hash(ctx, hashed, config) ⇒ Object
38 39 40 41 42 43 44 45 46 47 |
# File 'lib/better_auth/api_key/adapter.rb', line 38 def find_by_hash(ctx, hashed, config) if config[:storage] == "secondary-storage" record = get(ctx, storage_key_by_hash(hashed), config) || get(ctx, "api-key:key:#{hashed}", config) return record if record return nil unless config[:fallback_to_database] end record = ctx.context.adapter.find_one(model: BetterAuth::Plugins::API_KEY_TABLE_NAME, where: [{field: "key", value: hashed}]) set(ctx, record, config) if record && config[:storage] == "secondary-storage" && config[:fallback_to_database] record end |
.find_by_id(ctx, id, config) ⇒ Object
49 50 51 52 53 54 55 56 57 58 |
# File 'lib/better_auth/api_key/adapter.rb', line 49 def find_by_id(ctx, id, config) if config[:storage] == "secondary-storage" record = get(ctx, storage_key_by_id(id), config) || get(ctx, "api-key:id:#{id}", config) return record if record return nil unless config[:fallback_to_database] end record = ctx.context.adapter.find_one(model: BetterAuth::Plugins::API_KEY_TABLE_NAME, where: [{field: "id", value: id}]) set(ctx, record, config) if record && config[:storage] == "secondary-storage" && config[:fallback_to_database] record end |
.get(ctx, key, config) ⇒ Object
131 132 133 134 135 136 |
# File 'lib/better_auth/api_key/adapter.rb', line 131 def get(ctx, key, config) raw = storage(config, ctx.context)&.get(key) raw && deserialize_record(JSON.parse(raw)) rescue JSON::ParserError nil end |
.list_for_reference(ctx, reference_id, config) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/better_auth/api_key/adapter.rb', line 60 def list_for_reference(ctx, reference_id, config) if config[:storage] == "secondary-storage" begin storage_instance = storage(config, ctx.context) ids = JSON.parse((storage_instance&.get(storage_key_by_reference(reference_id)) || storage_instance&.get("api-key:user:#{reference_id}")).to_s) records = ids.filter_map { |id| find_by_id(ctx, id, config) } return records unless records.empty? && config[:fallback_to_database] rescue JSON::ParserError, NoMethodError return [] unless config[:fallback_to_database] end end records = ctx.context.adapter.find_many(model: BetterAuth::Plugins::API_KEY_TABLE_NAME, where: [{field: "referenceId", value: reference_id}]) legacy = ctx.context.adapter.find_many(model: BetterAuth::Plugins::API_KEY_TABLE_NAME, where: [{field: "userId", value: reference_id}]) combined = (records + legacy).uniq { |record| record["id"] } populate_reference(ctx, reference_id, combined, config) if config[:storage] == "secondary-storage" && config[:fallback_to_database] combined end |
.migrate_legacy_metadata(ctx, record, config) ⇒ Object
112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/better_auth/api_key/adapter.rb', line 112 def (ctx, record, config) parsed = BetterAuth::APIKey::Utils.decode_json(record["metadata"]) return record unless parsed.is_a?(Hash) encoded = BetterAuth::APIKey::Utils.encode_json(parsed) return record.merge("metadata" => encoded) if record["metadata"] == encoded updated = record.merge("metadata" => encoded) if config[:storage] == "database" || config[:fallback_to_database] ctx.context.adapter.update(model: BetterAuth::Plugins::API_KEY_TABLE_NAME, where: [{field: "id", value: record["id"]}], update: {metadata: encoded}) end set(ctx, updated, config) if config[:storage] == "secondary-storage" updated end |
.populate_reference(ctx, reference_id, records, config) ⇒ Object
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/better_auth/api_key/adapter.rb', line 216 def populate_reference(ctx, reference_id, records, config) storage_instance = storage(config, ctx.context) return unless storage_instance ids = [] records.each do |record| serialized = JSON.generate(storage_record(record)) expires_at = BetterAuth::APIKey::Utils.normalize_time(record["expiresAt"]) ttl = expires_at ? [(expires_at - Time.now).to_i, 0].max : nil storage_instance.set(storage_key_by_hash(record["key"]), serialized, ttl) storage_instance.set(storage_key_by_id(record["id"]), serialized, ttl) ids << record["id"] end reference_key = storage_key_by_reference(reference_id) ids.empty? ? storage_instance.delete(reference_key) : storage_instance.set(reference_key, JSON.generate(ids)) end |
.ref_list_add(storage_instance, reference_key, id) ⇒ Object
188 189 190 191 192 |
# File 'lib/better_auth/api_key/adapter.rb', line 188 def ref_list_add(storage_instance, reference_key, id) ids = safe_parse_id_list(storage_instance.get(reference_key)) ids << id unless ids.include?(id) storage_instance.set(reference_key, JSON.generate(ids)) end |
.ref_list_remove(storage_instance, reference_key, id) ⇒ Object
194 195 196 197 |
# File 'lib/better_auth/api_key/adapter.rb', line 194 def ref_list_remove(storage_instance, reference_key, id) ids = safe_parse_id_list(storage_instance.get(reference_key)).reject { |existing| existing == id } ids.empty? ? storage_instance.delete(reference_key) : storage_instance.set(reference_key, JSON.generate(ids)) end |
.safe_parse_id_list(raw) ⇒ Object
199 200 201 202 203 204 205 206 |
# File 'lib/better_auth/api_key/adapter.rb', line 199 def safe_parse_id_list(raw) return [] if raw.nil? parsed = JSON.parse(raw.to_s) parsed.is_a?(Array) ? parsed : [] rescue JSON::ParserError [] end |
.schedule_record_delete(ctx, record, config) ⇒ Object
103 104 105 106 107 108 109 110 |
# File 'lib/better_auth/api_key/adapter.rb', line 103 def schedule_record_delete(ctx, record, config) task = -> { delete_record(ctx, record, config) } if config[:defer_updates] && BetterAuth::APIKey::Utils.background_tasks?(ctx) ctx.context.run_in_background(task) else task.call end end |
.set(ctx, record, config) ⇒ Object
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/better_auth/api_key/adapter.rb', line 138 def set(ctx, record, config) storage_instance = storage(config, ctx.context) unless storage_instance raise BetterAuth::APIError.new("INTERNAL_SERVER_ERROR", message: "Secondary storage is required when storage mode is 'secondary-storage'") end serialized = JSON.generate(storage_record(record)) expires_at = BetterAuth::APIKey::Utils.normalize_time(record["expiresAt"]) ttl = expires_at ? [(expires_at - Time.now).to_i, 0].max : nil reference_id = BetterAuth::Plugins.api_key_record_reference_id(record) reference_key = storage_key_by_reference(reference_id) batch(storage_instance) do operations = [ -> { storage_instance.set(storage_key_by_hash(record["key"]), serialized, ttl) }, -> { storage_instance.set(storage_key_by_id(record["id"]), serialized, ttl) } ] operations << if config[:fallback_to_database] -> { storage_instance.delete(reference_key) } else -> { ref_list_add(storage_instance, reference_key, record["id"]) } end operations.each(&:call) end end |
.storage(config, context = nil) ⇒ Object
127 128 129 |
# File 'lib/better_auth/api_key/adapter.rb', line 127 def storage(config, context = nil) config[:custom_storage] || context&.&.secondary_storage end |
.storage_key_by_hash(hashed_key) ⇒ Object
16 17 18 |
# File 'lib/better_auth/api_key/adapter.rb', line 16 def storage_key_by_hash(hashed_key) "#{HASH_STORAGE_PREFIX}#{hashed_key}" end |
.storage_key_by_id(id) ⇒ Object
20 21 22 |
# File 'lib/better_auth/api_key/adapter.rb', line 20 def storage_key_by_id(id) "#{ID_STORAGE_PREFIX}#{id}" end |
.storage_key_by_reference(reference_id) ⇒ Object
24 25 26 |
# File 'lib/better_auth/api_key/adapter.rb', line 24 def storage_key_by_reference(reference_id) "#{REFERENCE_STORAGE_PREFIX}#{reference_id}" end |
.storage_record(record) ⇒ Object
233 234 235 |
# File 'lib/better_auth/api_key/adapter.rb', line 233 def storage_record(record) record.transform_values { |value| value.is_a?(Time) ? value.iso8601 : value } end |
.store(ctx, data, config) ⇒ Object
28 29 30 31 32 33 34 35 36 |
# File 'lib/better_auth/api_key/adapter.rb', line 28 def store(ctx, data, config) record = nil if config[:storage] == "database" || config[:fallback_to_database] record = ctx.context.adapter.create(model: BetterAuth::Plugins::API_KEY_TABLE_NAME, data: data) end record ||= data.transform_keys { |key| BetterAuth::Schema.storage_key(key) }.merge("id" => SecureRandom.hex(16)) set(ctx, record, config) if config[:storage] == "secondary-storage" record end |
.update_record(ctx, record, update, config, defer: false) ⇒ Object
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/better_auth/api_key/adapter.rb', line 78 def update_record(ctx, record, update, config, defer: false) performer = lambda do updated = nil if config[:storage] == "database" || config[:fallback_to_database] updated = ctx.context.adapter.update(model: BetterAuth::Plugins::API_KEY_TABLE_NAME, where: [{field: "id", value: record["id"]}], update: update) end updated ||= record.merge(update.transform_keys { |key| BetterAuth::Schema.storage_key(key) }) set(ctx, updated, config) if config[:storage] == "secondary-storage" updated end if defer && config[:defer_updates] && BetterAuth::Plugins.api_key_background_tasks?(ctx) scheduled = record.merge(update.transform_keys { |key| BetterAuth::Schema.storage_key(key) }) ctx.context.run_in_background(performer) scheduled else performer.call end end |