Module: BetterAuth::APIKey::Routes

Defined in:
lib/better_auth/api_key/routes/index.rb,
lib/better_auth/api_key/routes/get_api_key.rb,
lib/better_auth/api_key/routes/list_api_keys.rb,
lib/better_auth/api_key/routes/create_api_key.rb,
lib/better_auth/api_key/routes/delete_api_key.rb,
lib/better_auth/api_key/routes/update_api_key.rb,
lib/better_auth/api_key/routes/verify_api_key.rb,
lib/better_auth/api_key/routes/delete_all_expired_api_keys.rb

Defined Under Namespace

Modules: CreateAPIKey, DeleteAPIKey, DeleteAllExpiredAPIKeys, GetAPIKey, ListAPIKeys, UpdateAPIKey, VerifyAPIKey

Constant Summary collapse

ROUTE_NAMES =
%i[
  create_api_key
  verify_api_key
  get_api_key
  update_api_key
  delete_api_key
  list_api_keys
  delete_all_expired_api_keys
].freeze

Class Method Summary collapse

Class Method Details

.api_key_create_body_schemaObject



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/better_auth/api_key/routes/index.rb', line 229

def api_key_create_body_schema
  BetterAuth::OpenAPI.object_schema(
    {
      configId: {type: "string", description: "The configuration ID to use for the API key"},
      name: {type: "string", description: "Name of the API key"},
      expiresIn: {type: ["number", "null"], description: "Expiration time of the API key in seconds"},
      prefix: {type: "string", description: "Prefix of the API key"},
      remaining: {type: ["number", "null"], description: "Remaining number of requests"},
      metadata: {nullable: true, description: "Metadata associated with the API key"},
      refillAmount: {type: "number", description: "Amount to refill the remaining count"},
      refillInterval: {type: "number", description: "Interval to refill the API key in milliseconds"},
      rateLimitTimeWindow: {type: "number", description: "Rate limit time window in milliseconds"},
      rateLimitMax: {type: "number", description: "Maximum requests allowed within a window"},
      rateLimitEnabled: {type: "boolean", description: "Whether the key has rate limiting enabled"},
      permissions: api_key_permissions_schema.merge(description: "Permissions of the API key"),
      userId: {type: "string", description: "User ID that the API key belongs to"},
      organizationId: {type: "string", description: "Organization ID that the API key belongs to"}
    }
  )
end

.api_key_permissions_schemaObject



260
261
262
263
264
265
266
267
268
# File 'lib/better_auth/api_key/routes/index.rb', line 260

def api_key_permissions_schema
  {
    type: "object",
    additionalProperties: {
      type: "array",
      items: {type: "string"}
    }
  }
end

.api_key_record_schema(include_secret:) ⇒ Object



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/better_auth/api_key/routes/index.rb', line 270

def api_key_record_schema(include_secret:)
  properties = {
    id: {type: "string", description: "Unique identifier of the API key"},
    createdAt: {type: "string", format: "date-time", description: "Creation timestamp"},
    updatedAt: {type: "string", format: "date-time", description: "Last update timestamp"},
    name: {type: ["string", "null"], description: "Name of the API key"},
    start: {type: ["string", "null"], description: "Starting characters of the key"},
    prefix: {type: ["string", "null"], description: "Prefix of the API key"},
    enabled: {type: "boolean", description: "Whether the key is enabled"},
    expiresAt: {type: ["string", "null"], format: "date-time", description: "Expiration timestamp"},
    referenceId: {type: "string", description: "ID of the reference owning the key"},
    lastRefillAt: {type: ["string", "null"], format: "date-time", description: "Last refill timestamp"},
    lastRequest: {type: ["string", "null"], format: "date-time", description: "Last request timestamp"},
    metadata: {type: ["object", "null"], additionalProperties: true, description: "Metadata associated with the key"},
    rateLimitMax: {type: ["number", "null"], description: "Maximum requests in time window"},
    rateLimitTimeWindow: {type: ["number", "null"], description: "Rate limit time window in milliseconds"},
    rateLimitEnabled: {type: "boolean", description: "Whether rate limiting is enabled"},
    remaining: {type: ["number", "null"], description: "Remaining number of requests"},
    refillAmount: {type: ["number", "null"], description: "Amount to refill"},
    refillInterval: {type: ["number", "null"], description: "Refill interval in milliseconds"},
    permissions: api_key_permissions_schema.merge(nullable: true, description: "Permissions of the API key"),
    userId: {type: ["string", "null"], description: "ID of the user owning the key"}
  }
  properties[:key] = {type: "string", description: "The full API key"} if include_secret
  BetterAuth::OpenAPI.object_schema(properties)
end

.api_key_update_body_schemaObject



250
251
252
253
254
255
256
257
258
# File 'lib/better_auth/api_key/routes/index.rb', line 250

def api_key_update_body_schema
  BetterAuth::OpenAPI.object_schema(
    api_key_create_body_schema[:properties].merge(
      keyId: {type: "string", description: "The API key ID"},
      enabled: {type: "boolean", description: "Whether the API key is enabled"}
    ).except(:prefix, :organizationId),
    required: ["keyId"]
  )
end

.config_id_matches?(record_config_id, expected_config_id) ⇒ Boolean

Returns:

  • (Boolean)


37
38
39
40
41
# File 'lib/better_auth/api_key/routes/index.rb', line 37

def config_id_matches?(record_config_id, expected_config_id)
  return true if default_config_id?(record_config_id) && default_config_id?(expected_config_id)

  record_config_id.to_s == expected_config_id.to_s
end

.create_api_key_openapiObject



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/better_auth/api_key/routes/index.rb', line 88

def create_api_key_openapi
  {
    openapi: {
      description: "Create a new API key for a user",
      requestBody: BetterAuth::OpenAPI.json_request_body(api_key_create_body_schema, required: true),
      responses: {
        "200" => BetterAuth::OpenAPI.json_response("API key created successfully", api_key_record_schema(include_secret: true))
      }
    }
  }
end

.default_config_id?(value) ⇒ Boolean

Returns:

  • (Boolean)


33
34
35
# File 'lib/better_auth/api_key/routes/index.rb', line 33

def default_config_id?(value)
  value.nil? || value.to_s.empty? || value.to_s == "default"
end

.delete_all_expired_api_keys_openapiObject



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/better_auth/api_key/routes/index.rb', line 208

def delete_all_expired_api_keys_openapi
  {
    openapi: {
      description: "Delete all expired API keys",
      requestBody: BetterAuth::OpenAPI.empty_request_body,
      responses: {
        "200" => BetterAuth::OpenAPI.json_response(
          "Expired API key cleanup result",
          BetterAuth::OpenAPI.object_schema(
            {
              success: {type: "boolean"},
              error: {type: ["object", "null"], additionalProperties: true}
            },
            required: ["success", "error"]
          )
        )
      }
    }
  }
end

.delete_api_key_openapiObject



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/better_auth/api_key/routes/index.rb', line 158

def delete_api_key_openapi
  {
    openapi: {
      description: "Delete an API key by ID",
      requestBody: BetterAuth::OpenAPI.json_request_body(
        BetterAuth::OpenAPI.object_schema(
          {
            keyId: {type: "string", description: "The API key ID"},
            configId: {type: "string", description: "Configuration ID to use for the lookup"}
          },
          required: ["keyId"]
        )
      ),
      responses: {
        "200" => BetterAuth::OpenAPI.json_response("API key deleted successfully", BetterAuth::OpenAPI.success_response_schema)
      }
    }
  }
end

.delete_expired(context, config, bypass_last_check: false, raise_on_error: false) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/better_auth/api_key/routes/index.rb', line 45

def delete_expired(context, config, bypass_last_check: false, raise_on_error: false)
  return unless config[:storage] == "database" || config[:fallback_to_database]
  unless bypass_last_check
    now = Time.now
    return if @last_expired_check && ((now - @last_expired_check) * 1000) < 10_000

    @last_expired_check = now
  end

  now = Time.now
  context.adapter.delete_many(
    model: BetterAuth::Plugins::API_KEY_TABLE_NAME,
    where: [
      {field: "expiresAt", value: now, operator: "lt"},
      {field: "expiresAt", value: nil, operator: "ne"}
    ]
  )
rescue => error
  context.logger.error("[API KEY PLUGIN] Failed to delete expired API keys: #{error.message}") if context.respond_to?(:logger) && context.logger.respond_to?(:error)
  raise if raise_on_error
end

.get_api_key_openapiObject



131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/better_auth/api_key/routes/index.rb', line 131

def get_api_key_openapi
  {
    openapi: {
      description: "Get an API key by ID",
      parameters: [
        BetterAuth::OpenAPI.query_parameter("id", required: true, description: "The API key ID"),
        BetterAuth::OpenAPI.query_parameter("configId", description: "Configuration ID to use for the lookup")
      ],
      responses: {
        "200" => BetterAuth::OpenAPI.json_response("API key retrieved successfully", api_key_record_schema(include_secret: false))
      }
    }
  }
end

.list_api_keys_openapiObject



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/better_auth/api_key/routes/index.rb', line 178

def list_api_keys_openapi
  {
    openapi: {
      description: "List all API keys for the authenticated user or for a specific organization",
      parameters: [
        BetterAuth::OpenAPI.query_parameter("configId", description: "Filter by configuration ID"),
        BetterAuth::OpenAPI.query_parameter("organizationId", description: "Organization ID to list keys for"),
        BetterAuth::OpenAPI.query_parameter("limit", schema: {type: "number"}, description: "The number of API keys to return"),
        BetterAuth::OpenAPI.query_parameter("offset", schema: {type: "number"}, description: "The offset to start from"),
        BetterAuth::OpenAPI.query_parameter("sortBy", description: "The field to sort by"),
        BetterAuth::OpenAPI.query_parameter("sortDirection", schema: {type: "string", enum: ["asc", "desc"]}, description: "The direction to sort by")
      ],
      responses: {
        "200" => BetterAuth::OpenAPI.json_response(
          "API keys retrieved successfully",
          BetterAuth::OpenAPI.object_schema(
            {
              apiKeys: BetterAuth::OpenAPI.array_schema(api_key_record_schema(include_secret: false)),
              total: {type: "number"},
              limit: {type: ["number", "null"]},
              offset: {type: ["number", "null"]}
            },
            required: ["apiKeys", "total"]
          )
        )
      }
    }
  }
end

.openapi_for(route) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/better_auth/api_key/routes/index.rb', line 76

def openapi_for(route)
  {
    create_api_key: create_api_key_openapi,
    verify_api_key: verify_api_key_openapi,
    get_api_key: get_api_key_openapi,
    update_api_key: update_api_key_openapi,
    delete_api_key: delete_api_key_openapi,
    list_api_keys: list_api_keys_openapi,
    delete_all_expired_api_keys: delete_all_expired_api_keys_openapi
  }.fetch(route)
end

.resolve_config(context, config, config_id = nil) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/better_auth/api_key/routes/index.rb', line 18

def resolve_config(context, config, config_id = nil)
  configurations = config.fetch(:configurations, [config])
  return configurations.find { |entry| default_config_id?(entry[:config_id]) } || configurations.first if config_id.to_s.empty?

  configurations.find { |entry| entry[:config_id].to_s == config_id.to_s } ||
    begin
      default = configurations.find { |entry| default_config_id?(entry[:config_id]) }
      unless default
        context.logger.error(BetterAuth::Plugins::API_KEY_ERROR_CODES["NO_DEFAULT_API_KEY_CONFIGURATION_FOUND"]) if context.respond_to?(:logger) && context.logger.respond_to?(:error)
        raise BetterAuth::APIError.new("BAD_REQUEST", message: BetterAuth::Plugins::API_KEY_ERROR_CODES["NO_DEFAULT_API_KEY_CONFIGURATION_FOUND"])
      end
      default
    end
end

.schedule_cleanup(ctx, config) ⇒ Object



67
68
69
70
71
72
73
74
# File 'lib/better_auth/api_key/routes/index.rb', line 67

def schedule_cleanup(ctx, config)
  task = -> { delete_expired(ctx.context, config) }
  if config[:defer_updates] && BetterAuth::APIKey::Utils.background_tasks?(ctx)
    BetterAuth::APIKey::Utils.run_background_task(ctx, "Deferred API key cleanup", task)
  else
    task.call
  end
end

.update_api_key_openapiObject



146
147
148
149
150
151
152
153
154
155
156
# File 'lib/better_auth/api_key/routes/index.rb', line 146

def update_api_key_openapi
  {
    openapi: {
      description: "Update an existing API key by ID",
      requestBody: BetterAuth::OpenAPI.json_request_body(api_key_update_body_schema, required: true),
      responses: {
        "200" => BetterAuth::OpenAPI.json_response("API key updated successfully", api_key_record_schema(include_secret: false))
      }
    }
  }
end

.verify_api_key_openapiObject



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/better_auth/api_key/routes/index.rb', line 100

def verify_api_key_openapi
  {
    openapi: {
      description: "Verify and rate-limit an API key",
      requestBody: BetterAuth::OpenAPI.json_request_body(
        BetterAuth::OpenAPI.object_schema(
          {
            key: {type: "string", description: "The API key to verify"},
            configId: {type: "string", description: "Configuration ID to use for the lookup"},
            permissions: api_key_permissions_schema.merge(description: "Permissions required for the request")
          },
          required: ["key"]
        )
      ),
      responses: {
        "200" => BetterAuth::OpenAPI.json_response(
          "API key verification result",
          BetterAuth::OpenAPI.object_schema(
            {
              valid: {type: "boolean"},
              error: {type: ["object", "null"], additionalProperties: true},
              key: api_key_record_schema(include_secret: false).merge(type: ["object", "null"])
            },
            required: ["valid", "error", "key"]
          )
        )
      }
    }
  }
end