Skip to content

Commit

Permalink
Refactor grants to use Strategy and Factory Pattern
Browse files Browse the repository at this point in the history
Replaced the existing grant handling logic with a combination of the Factory and Strategy patterns, creating a more scalable and maintainable architecture for dealing with various grant types. Updated specs to align with the new structure, ensuring comprehensive test coverage. Also, improved configuration accessibility by renaming `owner_client` to `providers`. This enhances code readability and future extensibility.
  • Loading branch information
eliasjpr committed Oct 8, 2024
1 parent d129e71 commit e25e89e
Show file tree
Hide file tree
Showing 16 changed files with 233 additions and 122 deletions.
4 changes: 2 additions & 2 deletions spec/configuration_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ describe Authly::Configuration do

describe Authly::ProvidersConfiguration do
it "creates default owners and clients" do
provider_config = Authly::Configuration.instance.owner_client
provider_config = Authly::Configuration.instance.providers
provider_config.owners.should_not be_nil
provider_config.clients.should_not be_nil
end

it "sets a default JTI provider" do
provider_config = Authly::Configuration.instance.owner_client
provider_config = Authly::Configuration.instance.providers
provider_config.jti_provider.should be_a(Authly::InMemoryJTIProvider)
end
end
Expand Down
89 changes: 89 additions & 0 deletions spec/grant_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
require "./spec_helper"

module Authly
describe Grant do
describe "AuthorizationCodeGrant" do
it "authorizes with valid client, redirect_uri, and verifier" do
grant = Grant.new("authorization_code",
client_id: "1",
client_secret: "secret",
redirect_uri: "https://www.example.com/callback",
challenge: "valid_challenge",
method: "S256")
grant.authorized?.should be_true
end

it "raises error with invalid redirect_uri" do
grant = Grant.new("authorization_code",
client_id: "valid_client_id",
client_secret: "valid_secret",
redirect_uri: "https://invalid.redirect.uri",
challenge: "valid_challenge",
method: "S256",
verifier: "valid_verifier")

expect_raises(Authly::Error(400)) { grant.authorized? }
end
end

describe "ClientCredentialsGrant" do
it "authorizes with valid client credentials" do
grant = Grant.new("client_credentials",
client_id: "1",
client_secret: "secret")

grant.authorized?.should be_true
end

it "raises error with invalid client credentials" do
grant = Grant.new("client_credentials",
client_id: "invalid_client_id",
client_secret: "invalid_secret")
expect_raises(Authly::Error(401)) { grant.authorized? }
end
end

describe "PasswordGrant" do
it "authorizes with valid client and user credentials" do
grant = Grant.new("password",
client_id: "1",
client_secret: "secret",
username: "username",
password: "password")
grant.authorized?.should be_true
end

it "raises error with invalid user credentials" do
grant = Grant.new("password",
client_id: "valid_client_id",
client_secret: "valid_secret",
username: "invalid_user",
password: "invalid_password")
expect_raises(Authly::Error(401)) { grant.authorized? }
end
end

describe "RefreshTokenGrant" do
it "authorizes with valid refresh token" do
client_id, secret, scope = "1", "secret", "read write"
token = AccessToken.new(client_id, scope).refresh_token

grant = Grant.new("refresh_token",
client_id: client_id,
client_secret: secret,
refresh_token: token)

grant.authorized?.should be_true
end

it "raises error with invalid refresh token" do
grant = Grant.new("refresh_token",
client_id: "valid_client_id",
client_secret: "valid_secret",
refresh_token: "invalid_refresh_token")

expect_raises(Authly::Error(400)) { grant.authorized? }
end
end
end
end
14 changes: 7 additions & 7 deletions spec/grants/authorization_code_spec.cr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require "../spec_helper"

module Authly
describe AuthorizationCode do
describe AuthorizationCodeGrant do
code_data = {
"client_id" => "1",
"secret" => "secret",
Expand All @@ -18,28 +18,28 @@ module Authly
it "peforms plain code challenge authorization" do
code_challenge = code_verifier
code = Code.new(code_challenge, "plain", scope, "user_id", uri, client_id).to_s
auth_code = AuthorizationCode.new(client_id, secret, uri, code, code_verifier)
auth_code = AuthorizationCodeGrant.new(client_id, secret, uri, code, code_verifier)
auth_code.authorized?.should be_truthy
end

it "peforms S256 code challenge authorization" do
code_challenge = Digest::SHA256.base64digest(code_verifier)
code = Code.new(code_challenge, "s256", scope, "user_id", uri, client_id).to_s
authorization_code = AuthorizationCode.new(client_id, secret, uri, code, code_verifier)
authorization_code = AuthorizationCodeGrant.new(client_id, secret, uri, code, code_verifier)

authorization_code.authorized?.should be_truthy
end
end

it "returns true" do
code = Code.new(client_id, scope, uri).to_s
authorization_code = AuthorizationCode.new(client_id, secret, uri, code)
authorization_code = AuthorizationCodeGrant.new(client_id, secret, uri, code)
authorization_code.authorized?.should be_truthy
end

it "raises error for invalid client credentials" do
code = Code.new(client_id, scope, uri).to_s
authorization_code = AuthorizationCode.new(client_id, "invalid", uri, code)
authorization_code = AuthorizationCodeGrant.new(client_id, "invalid", uri, code)

expect_raises Error, ERROR_MSG[:unauthorized_client] do
authorization_code.authorized?
Expand All @@ -48,7 +48,7 @@ module Authly

it "raises Error for client id" do
code = Code.new(client_id, scope, uri).to_s
authorization_code = AuthorizationCode.new("invalid_client", "invalid_client_secret", uri, code)
authorization_code = AuthorizationCodeGrant.new("invalid_client", "invalid_client_secret", uri, code)

expect_raises Error, ERROR_MSG[:invalid_redirect_uri] do
authorization_code.authorized?
Expand All @@ -57,7 +57,7 @@ module Authly

it "raises Error for redirect uri" do
code = Code.new(client_id, scope, uri).to_s
authorization_code = AuthorizationCode.new(client_id, secret, "", code)
authorization_code = AuthorizationCodeGrant.new(client_id, secret, "", code)

expect_raises Error, ERROR_MSG[:invalid_redirect_uri] do
authorization_code.authorized?
Expand Down
6 changes: 3 additions & 3 deletions spec/grants/client_credentials_spec.cr
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
require "../spec_helper"

module Authly
describe ClientCredentials do
describe ClientCredentialsGrant do
cid, secret = "1", "secret"

it "returns AccessToken" do
client_credentials = ClientCredentials.new(
client_credentials = ClientCredentialsGrant.new(
client_id: cid, client_secret: secret
)
client_credentials.authorized?.should be_truthy
end

it "raises error for invalid client credentials" do
client_credentials = ClientCredentials.new(
client_credentials = ClientCredentialsGrant.new(
client_id: cid, client_secret: "invalid"
)
expect_raises Error, ERROR_MSG[:unauthorized_client] do
Expand Down
8 changes: 4 additions & 4 deletions spec/grants/password_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ require "../spec_helper"
module Authly
cid, secret, username, password = "1", "secret", "username", "password"

describe Password do
password_authorization = Password.new(
describe PasswordGrant do
password_authorization = PasswordGrant.new(
client_id: cid, client_secret: secret, username: username, password: password
)

Expand All @@ -13,7 +13,7 @@ module Authly
end

it "raises error for invalid client credentials" do
invalid_auth = Password.new(
invalid_auth = PasswordGrant.new(
client_id: cid, client_secret: "bad secret", username: username, password: password
)

Expand All @@ -23,7 +23,7 @@ module Authly
end

it "raises error for invalid owner credentials" do
invalid_owner = Password.new(
invalid_owner = PasswordGrant.new(
client_id: cid, client_secret: secret, username: username, password: "bad password"
)

Expand Down
6 changes: 3 additions & 3 deletions spec/grants/refresh_token_spec.cr
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
require "../spec_helper"

module Authly
describe RefreshToken do
describe RefreshTokenGrant do
client_id, secret, scope = "1", "secret", "read write"

it "returns RefreshToken" do
token = AccessToken.new(client_id, scope).refresh_token
refresh_token = RefreshToken.new(
refresh_token = RefreshTokenGrant.new(
client_id: client_id, client_secret: secret, refresh_token: token,
)
refresh_token.authorized?.should be_truthy
Expand All @@ -15,7 +15,7 @@ module Authly
it "raises error for invalid client credentials" do
token = AccessToken.new(client_id, scope).refresh_token

refresh_token = RefreshToken.new(
refresh_token = RefreshTokenGrant.new(
client_id: client_id, client_secret: "BAD", refresh_token: token
)

Expand Down
5 changes: 5 additions & 0 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,10 @@ require "../src/authly"
# Configure
secret_key = Random::Secure.hex(16)

Authly.configure do |config|
config.security.secret_key = secret_key
config.security.public_key = secret_key
end

Authly.clients << Authly::Client.new("example", "secret", "https://www.example.com/callback", "1")
Authly.owners << Authly::Owner.new("username", "password")
14 changes: 7 additions & 7 deletions src/authly.cr
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ module Authly
end

def self.clients
CONFIG.owner_client.clients
CONFIG.providers.clients
end

def self.owners
CONFIG.owner_client.owners
CONFIG.providers.owners
end

def self.code(response_type, *args)
Expand All @@ -34,19 +34,19 @@ module Authly
end

def self.jwt_encode(payload)
JWT.encode(payload, config.secret.secret_key, config.secret.algorithm)
JWT.encode(payload, config.security.secret_key, config.security.algorithm)
end

def self.jwt_decode(token, secret_key = config.secret.public_key)
JWT.decode token, secret_key, config.algorithm
def self.jwt_decode(token, secret_key = config.security.public_key)
JWT.decode token, secret_key, config.security.algorithm
end

def self.revoke(jti)
Authly.config.jti_provider.revoke(jti)
Authly.config.providers.jti_provider.revoke(jti)
end

def self.revoked?(jti)
Authly.config.jti_provider.revoked?(jti)
Authly.config.providers.jti_provider.revoked?(jti)
end

def self.valid?(token)
Expand Down
2 changes: 1 addition & 1 deletion src/authly/code.cr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Authly
struct Code
include JSON::Serializable
CODE_TTL = Authly.config.code_ttl
CODE_TTL = Authly.config.ttl.code_ttl

getter code : String = Random::Secure.hex(16),
client_id : String,
Expand Down
3 changes: 1 addition & 2 deletions src/authly/configuration.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ module Authly
property secret_key : String = Random::Secure.hex(16)
property public_key : String = Random::Secure.hex(16)
property algorithm : JWT::Algorithm = JWT::Algorithm::HS256

end

# Time-To-Live Configuration
Expand All @@ -26,7 +25,7 @@ module Authly
property issuer : String = "The Authority Server Provider"
property security : SecurityConfiguration = SecurityConfiguration.new
property ttl : TTLConfiguration = TTLConfiguration.new
property owner_client : ProvidersConfiguration = ProvidersConfiguration.new
property providers : ProvidersConfiguration = ProvidersConfiguration.new

# Singleton instance
@@instance : Configuration?
Expand Down
Loading

0 comments on commit e25e89e

Please sign in to comment.