From e08d177d872e3f224a115b0834951266b5cd92ba Mon Sep 17 00:00:00 2001 From: Ali Naqvi Date: Tue, 16 Jun 2026 10:33:34 +0800 Subject: [PATCH] feat: [PPT-2540] add expiry support to api key --- shard.lock | 12 ++--- spec/controllers/api_key_spec.cr | 45 +++++++++++++++++++ .../utilities/current-user.cr | 3 ++ 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/shard.lock b/shard.lock index ad8319cd..8efc87c0 100644 --- a/shard.lock +++ b/shard.lock @@ -71,7 +71,7 @@ shards: dns: git: https://github.com/spider-gazelle/dns.git - version: 1.5.0 + version: 1.6.0 dtls: git: https://github.com/spider-gazelle/crystal-dtls.git @@ -135,7 +135,7 @@ shards: json-schema: git: https://github.com/spider-gazelle/json-schema.git - version: 1.3.2 + version: 1.3.3 jwt: git: https://github.com/crystal-community/jwt.git @@ -203,7 +203,7 @@ shards: placeos-core: git: https://github.com/placeos/core.git - version: 4.21.0+git.commit.f40e0c95a141d0522ae30e5468277dd4a45429f7 + version: 4.21.0+git.commit.3bf2437a3c73fcddcc95a115872be43263c99358 placeos-core-client: # Overridden git: https://github.com/placeos/core-client.git @@ -215,7 +215,7 @@ shards: placeos-frontend-loader: git: https://github.com/placeos/frontend-loader.git - version: 2.7.1+git.commit.9ccf9bdbd6cb9c43a7f75e39341a6194998e14e1 + version: 2.7.1+git.commit.b5dedf3d4e009df9af493a67ae9e4076674029a7 placeos-log-backend: git: https://github.com/place-labs/log-backend.git @@ -223,7 +223,7 @@ shards: placeos-models: git: https://github.com/placeos/models.git - version: 9.97.0 + version: 9.98.0 placeos-resource: git: https://github.com/place-labs/resource.git @@ -275,7 +275,7 @@ shards: search-ingest: git: https://github.com/placeos/search-ingest.git - version: 2.11.3+git.commit.dfd41822529477a4b1eb3add166b225249eb83ac + version: 2.11.3+git.commit.9959e925d388338d0d784efb412c5db869ee9b1b secrets-env: # Overridden git: https://github.com/spider-gazelle/secrets-env.git diff --git a/spec/controllers/api_key_spec.cr b/spec/controllers/api_key_spec.cr index 16d76f8b..5d115ba9 100644 --- a/spec/controllers/api_key_spec.cr +++ b/spec/controllers/api_key_spec.cr @@ -18,5 +18,50 @@ module PlaceOS::Api describe "scopes" do Spec.test_controller_scope(ApiKeys) end + + describe "API key expiry", tags: "expiry" do + it "rejects expired API keys with 401" do + user, headers = Spec::Authentication.x_api_authentication + api_key = PlaceOS::Model::ApiKey.where(name: user.email.to_s).first + api_key.expires_at = Time.utc + 1.second + api_key.save! + sleep 1.5 + + result = client.get(path: ApiKeys.base_route + "inspect", headers: headers) + result.status_code.should eq 401 + end + + it "accepts non-expired API keys" do + user, headers = Spec::Authentication.x_api_authentication + api_key = PlaceOS::Model::ApiKey.where(name: user.email.to_s).first + api_key.expires_at = Time.utc + 1.hour + api_key.save! + + result = client.get(path: ApiKeys.base_route + "inspect", headers: headers) + result.status_code.should eq 200 + end + + it "accepts API keys with no expiry set" do + user, headers = Spec::Authentication.x_api_authentication + api_key = PlaceOS::Model::ApiKey.where(name: user.email.to_s).first + api_key.expires_at = nil + api_key.save! + + result = client.get(path: ApiKeys.base_route + "inspect", headers: headers) + result.status_code.should eq 200 + end + + it "shows expired keys in index listing" do + user, _ = Spec::Authentication.x_api_authentication + api_key = PlaceOS::Model::ApiKey.where(name: user.email.to_s).first + api_key.expires_at = Time.utc + 1.second + api_key.save! + sleep 1.5 + + admin_headers = Spec::Authentication.headers + result = client.get(path: ApiKeys.base_route, headers: admin_headers) + result.status_code.should eq 200 + end + end end end diff --git a/src/placeos-rest-api/utilities/current-user.cr b/src/placeos-rest-api/utilities/current-user.cr index 03c2ac00..20fb6e27 100644 --- a/src/placeos-rest-api/utilities/current-user.cr +++ b/src/placeos-rest-api/utilities/current-user.cr @@ -23,12 +23,15 @@ module PlaceOS::Api if token = request.headers["X-API-Key"]? || params["api-key"]? || cookies["api-key"]?.try(&.value) begin api_key = ::PlaceOS::Model::ApiKey.find_key!(token) + raise Error::Unauthorized.new "API key has expired" if api_key.expired? user_token = api_key.build_jwt Log.context.set(api_key_id: api_key.id, api_key_name: api_key.name) ensure_matching_domain(user_token) @user_token = user_token @current_user = ::PlaceOS::Model::User.find(user_token.id) return user_token + rescue e : Error::Unauthorized + raise e rescue e Log.warn(exception: e) { {message: "bad or unknown X-API-Key", action: "authorize!"} } raise Error::Unauthorized.new "unknown X-API-Key"