-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #263 from dxw/70-restrict-access-to-requests-with-…
…api-key (70) Restrict access to requests with API key
- Loading branch information
Showing
13 changed files
with
282 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,19 @@ | ||
class Api::LandingApplicationsController < ApplicationController | ||
before_action :authenticate_api_key | ||
|
||
def index | ||
render json: LandingApplication.includes(:destination).map { |la| | ||
LandingApplicationEntity.new(la).represent | ||
} | ||
end | ||
|
||
private | ||
|
||
def authenticate_api_key | ||
head :unauthorized unless ApiClientAuthenticator.authenticate?(api_key) | ||
end | ||
|
||
def api_key | ||
request.headers["HTTP_X_API_KEY"] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
class ApiClientAuthenticator | ||
def self.authenticate?(api_key) | ||
return false if api_key.blank? | ||
|
||
begin | ||
ApiKey.find_by_token!(api_key) | ||
rescue ActiveRecord::RecordNotFound | ||
return false | ||
end | ||
|
||
true | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
class ApiKey < ApplicationRecord | ||
before_create :generate_unhashed_token | ||
before_create :generate_token_digest | ||
|
||
validates :api_client_name, presence: true | ||
|
||
# Attribute for storing and accessing the unhashed | ||
# token value directly after creation -- ONLY | ||
attr_accessor :unhashed_token | ||
|
||
def self.find_by_token!(token) | ||
find_by!(token_digest: generate_digest(token)) | ||
end | ||
|
||
def self.find_by_token(token) | ||
find_by(token_digest: generate_digest(token)) | ||
end | ||
|
||
def self.generate_digest(token) | ||
OpenSSL::HMAC.hexdigest( | ||
"SHA256", | ||
ENV.fetch("API_KEY_HMAC_SECRET_KEY"), | ||
token | ||
) | ||
end | ||
|
||
private | ||
|
||
def generate_unhashed_token | ||
self.unhashed_token = SecureRandom.base58(30) | ||
end | ||
|
||
def generate_token_digest | ||
self.token_digest = self.class.generate_digest(unhashed_token) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
class CreateApiKeys < ActiveRecord::Migration[7.2] | ||
def change | ||
create_table :api_keys, id: :uuid do |t| | ||
t.string :api_client_name, null: false | ||
t.string :token_digest, null: false | ||
|
||
t.timestamps | ||
end | ||
add_index :api_keys, :token_digest, unique: true | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 16 additions & 1 deletion
17
spec/controllers/api/landing_applications_controller_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
RSpec.describe ApiClientAuthenticator do | ||
describe "::authenticate?(api_key)" do | ||
let(:token) { "321cba" } | ||
|
||
it "uses ApiKey::find_by_token!(token) to look up the given token" do | ||
allow(ApiKey).to receive(:find_by_token!) | ||
|
||
ApiClientAuthenticator.authenticate?(token) | ||
|
||
expect(ApiKey).to have_received(:find_by_token!).with("321cba") | ||
end | ||
|
||
context "when the token is blank" do | ||
it "returns _false_" do | ||
aggregate_failures do | ||
expect(ApiClientAuthenticator.authenticate?(nil)).to be false | ||
expect(ApiClientAuthenticator.authenticate?("")).to be false | ||
end | ||
end | ||
end | ||
|
||
context "when the key exists" do | ||
before { allow(ApiKey).to receive(:find_by_token!).and_return(double) } | ||
|
||
it "returns _true_ " do | ||
expect(ApiClientAuthenticator.authenticate?(token)).to be true | ||
end | ||
end | ||
|
||
context "when the key does NOT exist" do | ||
before do | ||
allow(ApiKey).to receive(:find_by_token!).and_raise(ActiveRecord::RecordNotFound) | ||
end | ||
|
||
it "returns _false_ " do | ||
expect(ApiClientAuthenticator.authenticate?(token)).to be false | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
RSpec.describe ApiKey do | ||
let(:token) { "unhashed-token" } | ||
let(:secret_key) { "secret-key" } | ||
let(:hashed_token) { "hashed-token" } | ||
|
||
around do |example| | ||
ClimateControl.modify API_KEY_HMAC_SECRET_KEY: "secret-key" do | ||
example.run | ||
end | ||
end | ||
|
||
before do | ||
allow(SecureRandom).to receive(:base58).and_return(token) | ||
allow(OpenSSL::HMAC).to receive(:hexdigest).and_return(hashed_token) | ||
end | ||
|
||
def expects_token_digest_to_have_been_generated | ||
expect(OpenSSL::HMAC).to have_received(:hexdigest).with( | ||
"SHA256", | ||
secret_key, | ||
token | ||
) | ||
end | ||
|
||
describe "class methods" do | ||
describe "when creating the API key" do | ||
it "generates a Base58 string of 30 chars as the token" do | ||
ApiKey.new(api_client_name: "Client name").save | ||
|
||
expect(SecureRandom).to have_received(:base58).with(30) | ||
end | ||
|
||
it "uses OpenSSL::HMAC.hexdigest to generate a hash of the token" do | ||
ApiKey.new(api_client_name: "Client name").save | ||
|
||
expects_token_digest_to_have_been_generated | ||
end | ||
end | ||
|
||
describe "when retrieving the API key" do | ||
describe "::find_by_token(token)" do | ||
it "generates the digest of the given token" do | ||
ApiKey.find_by_token(token) | ||
|
||
expects_token_digest_to_have_been_generated | ||
end | ||
end | ||
|
||
describe "::find_by_token!(token)" do | ||
it "generates the digest of the given token" do | ||
ApiKey.find_by_token!(token) | ||
rescue ActiveRecord::RecordNotFound | ||
# what happened before ActiveRecord::RecordNotFound | ||
expects_token_digest_to_have_been_generated | ||
end | ||
end | ||
end | ||
|
||
describe "::generate_digest(token)" do | ||
it "uses the secret key to generate a SHA156 digest of the given token" do | ||
ApiKey.generate_digest(token) | ||
|
||
expects_token_digest_to_have_been_generated | ||
end | ||
end | ||
end | ||
|
||
describe "instance_methods" do | ||
describe "#unhashed_token" do | ||
it "returns the unhashed token immediately after creation" do | ||
key = ApiKey.create(api_client_name: "Client name") | ||
expect(key.unhashed_token).to eq(token) | ||
end | ||
|
||
it "does NOT return unhashed on subsequent retrieval" do | ||
ApiKey.create(api_client_name: "Client name") | ||
key = ApiKey.find_by!(api_client_name: "Client name") | ||
|
||
expect(key.unhashed_token).to be_nil | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,8 @@ paths: | |
summary: Retrieves a list of landing applications | ||
tags: | ||
- Landing applications | ||
security: | ||
- api_key: [] | ||
responses: | ||
"200": | ||
description: success | ||
|
@@ -18,16 +20,26 @@ paths: | |
test_example: | ||
value: | ||
- application_id: f5f81284-e377-4017-aab1-1efac2119a2c | ||
destination: Planet X | ||
pilot: | ||
name: Fred Smith | ||
email: [email protected] | ||
licence_id: 1233ABC00123 | ||
permit_issued_at: "2024-09-18T07:53:21Z" | ||
permit_issued_at: "2024-09-24T10:28:31Z" | ||
permit_id: LP-3522-HNWD | ||
destination: Planet X | ||
landing_date: "2024-10-17" | ||
departure_date: "2024-10-24" | ||
spacecraft_registration_id: ABC123A | ||
landing_date: "2024-10-11" | ||
departure_date: "2024-10-18" | ||
"401": | ||
description: invalid credentials | ||
components: | ||
securitySchemes: | ||
api_key: | ||
type: apiKey | ||
name: X-API-KEY | ||
in: header | ||
security: | ||
- api_key: [] | ||
servers: | ||
- url: https://{defaultHost} | ||
variables: | ||
|