Skip to content

Commit 9060b27

Browse files
committed
Add AuthenticateWithApiKey command
1 parent eed241a commit 9060b27

File tree

7 files changed

+136
-22
lines changed

7 files changed

+136
-22
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## [0.0.10] - 2025-04-28
2+
3+
- Add AuthenticateWithApiKey command
4+
15
## [0.0.9] - 2025-04-22
26

37
- Handle malformed jwt tokens

Gemfile.lock

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
foobara-auth (0.0.9)
4+
foobara-auth (0.0.11)
55
argon2
66
foobara (~> 0.0.1)
77
jwt
@@ -26,7 +26,7 @@ GEM
2626
diff-lcs (1.6.1)
2727
docile (1.4.1)
2828
dotenv (3.1.8)
29-
extract-repo (0.0.10)
29+
extract-repo (0.0.11)
3030
foobara (~> 0.0.102)
3131
foobara-sh-cli-connector (~> 0.0.1)
3232
ffi (1.17.2)
@@ -63,7 +63,7 @@ GEM
6363
foobara-type-generator (~> 0.0.1)
6464
foobara-typescript-react-command-form-generator (~> 0.0.1)
6565
foobara-typescript-remote-command-generator (~> 0.0.1)
66-
foobara (0.0.108)
66+
foobara (0.0.114)
6767
bigdecimal
6868
foobara-lru-cache (~> 0.0.2)
6969
foobara-util (~> 0.0.11)
@@ -79,7 +79,7 @@ GEM
7979
foobara-files-generator
8080
foobara-dotenv-loader (0.0.3)
8181
dotenv
82-
foobara-empty-ruby-project-generator (0.0.17)
82+
foobara-empty-ruby-project-generator (0.0.18)
8383
extract-repo (~> 0.0.1)
8484
foobara (~> 0.0.94)
8585
foobara-files-generator (~> 0.0.1)
@@ -114,7 +114,7 @@ GEM
114114
foobara-rubocop-rules (0.0.8)
115115
rubocop
116116
rubocop-rspec
117-
foobara-sh-cli-connector (0.0.15)
117+
foobara-sh-cli-connector (0.0.16)
118118
foobara (~> 0.0.88)
119119
foobara-sh-cli-connector-generator (0.0.4)
120120
foobara-files-generator (~> 0.0.1)
@@ -125,7 +125,7 @@ GEM
125125
foobara-files-generator
126126
foobara-typescript-react-command-form-generator (0.0.12)
127127
foobara-typescript-remote-command-generator (~> 0.0.1)
128-
foobara-typescript-remote-command-generator (0.0.18)
128+
foobara-typescript-remote-command-generator (0.0.19)
129129
foobara-files-generator (~> 0.0.1)
130130
foobara-util (0.0.11)
131131
formatador (1.1.0)
@@ -152,7 +152,7 @@ GEM
152152
pp (>= 0.6.0)
153153
rdoc (>= 4.0.0)
154154
reline (>= 0.4.2)
155-
json (2.10.2)
155+
json (2.11.3)
156156
jwt (2.10.1)
157157
base64
158158
language_server-protocol (3.17.0.4)
@@ -182,10 +182,10 @@ GEM
182182
pry-byebug (3.11.0)
183183
byebug (~> 12.0)
184184
pry (>= 0.13, < 0.16)
185-
psych (5.2.3)
185+
psych (5.2.4)
186186
date
187187
stringio
188-
public_suffix (6.0.1)
188+
public_suffix (6.0.2)
189189
racc (1.8.1)
190190
rainbow (3.1.1)
191191
rake (13.2.1)
@@ -204,17 +204,17 @@ GEM
204204
rspec-mocks (~> 3.13.0)
205205
rspec-core (3.13.3)
206206
rspec-support (~> 3.13.0)
207-
rspec-expectations (3.13.3)
207+
rspec-expectations (3.13.4)
208208
diff-lcs (>= 1.2.0, < 2.0)
209209
rspec-support (~> 3.13.0)
210210
rspec-its (2.0.0)
211211
rspec-core (>= 3.13.0)
212212
rspec-expectations (>= 3.13.0)
213-
rspec-mocks (3.13.2)
213+
rspec-mocks (3.13.3)
214214
diff-lcs (>= 1.2.0, < 2.0)
215215
rspec-support (~> 3.13.0)
216-
rspec-support (3.13.2)
217-
rubocop (1.75.3)
216+
rspec-support (3.13.3)
217+
rubocop (1.75.4)
218218
json (~> 2.3)
219219
language_server-protocol (~> 3.17.0.2)
220220
lint_roller (~> 1.1.0)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
RSpec.describe Foobara::Auth::AuthenticateWithApiKey do
2+
after { Foobara.reset_alls }
3+
4+
before do
5+
Foobara::Persistence.default_crud_driver = Foobara::Persistence::CrudDrivers::InMemory.new
6+
end
7+
8+
let(:command) { described_class.new(inputs) }
9+
let(:outcome) { command.run }
10+
let(:result) { outcome.result }
11+
let(:errors) { outcome.errors }
12+
let(:errors_hash) { outcome.errors_hash }
13+
14+
let(:user) do
15+
Foobara::Auth::CreateUser.run!(username: "Basil", email: "[email protected]", plaintext_password:)
16+
end
17+
let(:plaintext_password) { "somepassword" }
18+
19+
context "when authenticating with api key" do
20+
let(:api_key) do
21+
Foobara::Auth::CreateApiKey.run!(user: user.id)
22+
end
23+
24+
let(:inputs) do
25+
{ api_key: }
26+
end
27+
28+
it "is successful" do
29+
expect(outcome).to be_success
30+
expect(result.size).to eq(2)
31+
auth_user, token = result
32+
expect(auth_user).to be_a(Foobara::Auth::Types::User)
33+
expect(token).to be_a(Foobara::Auth::Types::Token)
34+
expect(auth_user.username).to eq(user.username)
35+
end
36+
37+
context "with an invalid api key" do
38+
let(:inputs) do
39+
{ api_key: invalid_token }
40+
end
41+
let(:invalid_token) do
42+
text = api_key.dup
43+
text[-5] = text[-5] == "x" ? "y" : "x"
44+
text
45+
end
46+
47+
it "fails with appropriate error" do
48+
expect(outcome).to_not be_success
49+
expect(outcome.errors_hash).to include("runtime.invalid_api_key")
50+
end
51+
end
52+
end
53+
end

src/authenticate_with_api_key.rb

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
require_relative "verify_token"
2+
3+
module Foobara
4+
module Auth
5+
class AuthenticateWithApiKey < Foobara::Command
6+
class InvalidApiKeyError < Foobara::RuntimeError
7+
context api_key_id: :string
8+
message "Invalid api key"
9+
end
10+
11+
depends_on VerifyToken
12+
depends_on_entities Types::Token
13+
14+
inputs do
15+
api_key :string, :required, :sensitive
16+
access_token_ttl :integer, default: 30 * 60
17+
api_key_ttl :integer, default: 7 * 24 * 60 * 60
18+
end
19+
20+
result [Types::User, Types::Token]
21+
22+
def execute
23+
determine_api_key_id_and_secret
24+
load_api_key_record
25+
verify_api_key
26+
27+
load_user
28+
29+
user_and_credential
30+
end
31+
32+
attr_accessor :expires_at, :api_key_record, :api_key_id, :api_key_secret, :user
33+
34+
def determine_api_key_id_and_secret
35+
self.api_key_id, self.api_key_secret = api_key.split("_")
36+
end
37+
38+
def load_api_key_record
39+
self.api_key_record = Types::Token.load(api_key_id)
40+
end
41+
42+
def verify_api_key
43+
valid = run_subcommand!(VerifyToken, token_string: api_key)
44+
45+
unless valid[:verified]
46+
add_runtime_error(InvalidApiKeyError.new(context: { api_key_id: }))
47+
end
48+
end
49+
50+
def load_user
51+
self.user ||= Types::User.that_owns(api_key_record, "api_keys")
52+
end
53+
54+
def user_and_credential
55+
[user, api_key_record]
56+
end
57+
end
58+
end
59+
end

src/refresh_login.rb

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,6 @@ class InvalidRefreshTokenError < Foobara::RuntimeError
66
message "Invalid refresh token"
77
end
88

9-
class RefreshTokenNotOwnedByUser < Foobara::RuntimeError
10-
context refresh_token_id: :string
11-
message "This refresh token is not owned by this user"
12-
end
13-
149
depends_on CreateRefreshToken, VerifyToken, BuildAccessToken
1510
depends_on_entities Types::Token
1611

@@ -32,14 +27,16 @@ def execute
3227
# Delete it instead maybe?
3328
mark_refresh_token_as_used
3429

30+
load_user
31+
3532
generate_access_token
3633
generate_new_refresh_token
3734

3835
tokens
3936
end
4037

4138
attr_accessor :access_token, :new_refresh_token, :expires_at, :refresh_token_record,
42-
:refresh_token_id, :refresh_token_secret
39+
:refresh_token_id, :refresh_token_secret, :user
4340

4441
def determine_refresh_token_id_and_secret
4542
self.refresh_token_id, self.refresh_token_secret = refresh_token.split("_")
@@ -65,8 +62,8 @@ def generate_access_token
6562
self.access_token = run_subcommand!(BuildAccessToken, user:, token_ttl: access_token_ttl)
6663
end
6764

68-
def user
69-
@user ||= Types::User.that_owns(refresh_token_record, "refresh_tokens")
65+
def load_user
66+
self.user ||= Types::User.that_owns(refresh_token_record, "refresh_tokens")
7067
end
7168

7269
def generate_new_refresh_token

src/register.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
module Foobara
44
module Auth
5+
# TODO: should raise error if username or email already in use!
56
class Register < Foobara::Command
67
depends_on CreateUser, SetPassword
78

version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module Foobara
22
module Auth
3-
VERSION = "0.0.9".freeze
3+
VERSION = "0.0.10".freeze
44

55
local_ruby_version = File.read("#{__dir__}/.ruby-version").chomp
66
local_ruby_version_minor = local_ruby_version[/\A(\d+\.\d+)\.\d+\z/, 1]

0 commit comments

Comments
 (0)