Skip to content

Commit

Permalink
Add storage interface with reference memory implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
jhuckabee committed Dec 6, 2024
1 parent fd13c61 commit e8dd2f1
Show file tree
Hide file tree
Showing 17 changed files with 646 additions and 19 deletions.
13 changes: 11 additions & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@ AllCops:
SuggestExtensions: false
TargetRubyVersion: 3.0

Metrics/BlockLength:
Enabled: false

Metrics/ClassLength:
Max: 500
Enabled: false

Metrics/ModuleLength:
Enabled: false

Metrics/MethodLength:
Max: 50
Enabled: false

Metrics/AbcSize:
Enabled: false

Style/StringLiterals:
EnforcedStyle: double_quotes
Expand Down
2 changes: 1 addition & 1 deletion bin/snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class SnapshotGenerator
file.puts(tree)
end

def generate_tree_structure(path, prefix = "") # rubocop:disable Metrics/AbcSize
def generate_tree_structure(path, prefix = "")
entries = Dir.entries(path).sort
entries.delete(".")
entries.delete("..")
Expand Down
2 changes: 1 addition & 1 deletion examples/confidential_client/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class ExampleApp < Sinatra::Base
end

# Show authorized state and test API call
get "/authorized" do # rubocop:disable Metrics/BlockLength
get "/authorized" do
return redirect "/" unless session[:tokens]

begin
Expand Down
4 changes: 4 additions & 0 deletions lib/atproto_auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
require "atproto_auth/http_client"
require "atproto_auth/pkce"

require "atproto_auth/storage/interface"
require "atproto_auth/storage/key_builder"
require "atproto_auth/storage/memory"

require "atproto_auth/server_metadata"
require "atproto_auth/server_metadata/origin_url"
require "atproto_auth/server_metadata/authorization_server"
Expand Down
2 changes: 1 addition & 1 deletion lib/atproto_auth/client.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

# rubocop:disable Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity

module AtprotoAuth
# Main client class for AT Protocol OAuth implementation. Handles the complete
Expand Down
2 changes: 1 addition & 1 deletion lib/atproto_auth/client_metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def confidential?

private

def validate_and_set_metadata!(metadata) # rubocop:disable Metrics/AbcSize
def validate_and_set_metadata!(metadata)
# Required fields
@application_type = validate_application_type(metadata["application_type"])
@client_id = validate_client_id!(metadata["client_id"])
Expand Down
2 changes: 1 addition & 1 deletion lib/atproto_auth/dpop/key_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def sign_message(message)
asn1_to_raw(signature)
end

def extract_ec_key # rubocop:disable Metrics/AbcSize
def extract_ec_key
# Extract the raw EC key from JOSE JWK
key_data = @keypair.to_map
raise KeyError, "Private key required for signing" unless key_data["d"] # Private key component
Expand Down
4 changes: 2 additions & 2 deletions lib/atproto_auth/http_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def make_request(uri, headers = {}, redirect_count = 0)
raise HttpError.new("Request failed: #{e.message}", nil)
end

def make_post_request(uri, body, headers = {}, redirect_count = 0) # rubocop:disable Metrics/AbcSize
def make_post_request(uri, body, headers = {}, redirect_count = 0)
http = Net::HTTP.new(uri.host, uri.port)
configure_http_client!(http)

Expand Down Expand Up @@ -182,7 +182,7 @@ def add_security_headers!(request, headers)
# - headers: Hash of headers from the original request
# - redirect_count: Number of redirects so far
# - body: Request body for POST requests
def handle_redirect(**kwargs) # rubocop:disable Metrics/AbcSize
def handle_redirect(**kwargs)
response = kwargs[:response]
redirect_count = kwargs[:redirect_count]

Expand Down
2 changes: 1 addition & 1 deletion lib/atproto_auth/server_metadata/authorization_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def self.from_issuer(issuer)

private

def validate_and_set_metadata!(metadata) # rubocop:disable Metrics/AbcSize
def validate_and_set_metadata!(metadata)
REQUIRED_FIELDS.each do |field|
raise InvalidAuthorizationServer, "#{field} is required" unless metadata[field]
end
Expand Down
112 changes: 112 additions & 0 deletions lib/atproto_auth/storage/interface.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# frozen_string_literal: true

module AtprotoAuth
module Storage
# Base storage interface that all implementations must conform to
class Interface
# Store a value with optional TTL
# @param key [String] Storage key
# @param value [Object] Value to store
# @param ttl [Integer, nil] Time-to-live in seconds
# @return [Boolean] Success status
# @raise [StorageError] if operation fails
def set(key, value, ttl: nil)
raise NotImplementedError
end

# Retrieve a value
# @param key [String] Storage key
# @return [Object, nil] Stored value or nil if not found
# @raise [StorageError] if operation fails
def get(key)
raise NotImplementedError
end

# Delete a value
# @param key [String] Storage key
# @return [Boolean] Success status
# @raise [StorageError] if operation fails
def delete(key)
raise NotImplementedError
end

# Check if key exists
# @param key [String] Storage key
# @return [Boolean] True if key exists
# @raise [StorageError] if operation fails
def exists?(key)
raise NotImplementedError
end

# Get multiple values
# @param keys [Array<String>] Storage keys
# @return [Hash<String, Object>] Key-value pairs
# @raise [StorageError] if operation fails
def multi_get(keys)
raise NotImplementedError
end

# Store multiple values
# @param hash [Hash<String, Object>] Key-value pairs
# @param ttl [Integer, nil] Time-to-live in seconds
# @return [Boolean] Success status
# @raise [StorageError] if operation fails
def multi_set(hash, ttl: nil)
raise NotImplementedError
end

# Acquire a lock
# @param key [String] Lock key
# @param ttl [Integer] Lock timeout in seconds
# @return [Boolean] True if lock acquired
# @raise [StorageError] if operation fails
def acquire_lock(key, ttl:)
raise NotImplementedError
end

# Release a lock
# @param key [String] Lock key
# @return [Boolean] Success status
# @raise [StorageError] if operation fails
def release_lock(key)
raise NotImplementedError
end

# Execute block with lock
# @param key [String] Lock key
# @param ttl [Integer] Lock timeout in seconds
# @yield Block to execute with lock
# @return [Object] Block result
# @raise [StorageError] if operation fails
def with_lock(key, ttl: 30)
raise NotImplementedError
end

protected

# Validate key format
# @param key [String] Key to validate
# @raise [StorageError] if key is invalid
def validate_key!(key)
raise StorageError, "Key cannot be nil" if key.nil?
raise StorageError, "Key must be a string" unless key.is_a?(String)
raise StorageError, "Key cannot be empty" if key.empty?
raise StorageError, "Invalid key format" unless key.start_with?("atproto:")
end

# Validate TTL value
# @param ttl [Integer, nil] TTL to validate
# @raise [StorageError] if TTL is invalid
def validate_ttl!(ttl)
return if ttl.nil?
raise StorageError, "TTL must be a positive integer" unless ttl.is_a?(Integer) && ttl.positive?
end
end

# Base error class for storage operations
class StorageError < AtprotoAuth::Error; end

# Error for lock-related operations
class LockError < StorageError; end
end
end
39 changes: 39 additions & 0 deletions lib/atproto_auth/storage/key_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

module AtprotoAuth
module Storage
# Utility for building storage keys with correct format
class KeyBuilder
NAMESPACE_SEPARATOR = ":"
NAMESPACE_PREFIX = "atproto"

class << self
def session_key(id)
build_key("session", id)
end

def state_key(token)
build_key("state", token)
end

def nonce_key(server_url)
build_key("nonce", server_url)
end

def dpop_key(client_id)
build_key("dpop", client_id)
end

def lock_key(namespace, id)
build_key("lock", namespace, id)
end

private

def build_key(*parts)
[NAMESPACE_PREFIX, *parts].join(NAMESPACE_SEPARATOR)
end
end
end
end
end
Loading

0 comments on commit e8dd2f1

Please sign in to comment.