Skip to content

Commit 2fc0dc9

Browse files
mvndmclaude
andcommitted
Fix all RuboCop offenses and add CI workflow
- Resolve 289 RuboCop offenses (0 remaining) - Add .rubocop.yml with NewCops enabled - Refactor controller, verifier, and generator for reduced complexity - Split large test classes for maintainability - Add GitHub Actions CI: tests on Ruby 3.2/3.3/3.4 + RuboCop Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 53ee93d commit 2fc0dc9

26 files changed

Lines changed: 844 additions & 560 deletions

File tree

.github/workflows/ci.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
ruby-version: ['3.2', '3.3', '3.4']
15+
steps:
16+
- uses: actions/checkout@v4
17+
- name: Set up Ruby ${{ matrix.ruby-version }}
18+
uses: ruby/setup-ruby@v1
19+
with:
20+
ruby-version: ${{ matrix.ruby-version }}
21+
bundler-cache: true
22+
- name: Run tests
23+
run: bundle exec ruby -Ilib:test -e 'Dir.glob("test/**/*_test.rb").each { |f| require "./#{f}" }'
24+
25+
rubocop:
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/checkout@v4
29+
- name: Set up Ruby
30+
uses: ruby/setup-ruby@v1
31+
with:
32+
ruby-version: '3.4'
33+
bundler-cache: true
34+
- name: RuboCop
35+
run: bundle exec rubocop

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
AllCops:
2+
NewCops: enable
3+
SuggestExtensions: false

Gemfile

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# frozen_string_literal: true
22

3-
source "https://rubygems.org"
3+
source 'https://rubygems.org'
44

55
# Specify your gem's dependencies in solrengine-auth.gemspec
66
gemspec
77

8-
gem "irb"
9-
gem "rake", "~> 13.0"
10-
gem "sqlite3"
8+
gem 'irb'
9+
gem 'rake', '~> 13.0'
10+
gem 'rubocop', require: false
11+
gem 'sqlite3'

Gemfile.lock

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ GEM
8383
securerandom (>= 0.3)
8484
tzinfo (~> 2.0, >= 2.0.5)
8585
uri (>= 0.13.1)
86+
addressable (2.8.9)
87+
public_suffix (>= 2.0.2, < 8.0)
88+
ast (2.4.3)
8689
base58 (0.2.3)
8790
base64 (0.3.0)
8891
bigdecimal (4.0.1)
@@ -106,6 +109,11 @@ GEM
106109
rdoc (>= 4.0.0)
107110
reline (>= 0.4.2)
108111
json (2.19.1)
112+
json-schema (6.2.0)
113+
addressable (~> 2.8)
114+
bigdecimal (>= 3.1, < 5)
115+
language_server-protocol (3.17.0.5)
116+
lint_roller (1.1.0)
109117
logger (1.7.0)
110118
loofah (2.25.1)
111119
crass (~> 1.0.2)
@@ -117,6 +125,8 @@ GEM
117125
net-pop
118126
net-smtp
119127
marcel (1.1.0)
128+
mcp (0.9.0)
129+
json-schema (>= 4.1)
120130
mini_mime (1.1.5)
121131
minitest (6.0.2)
122132
drb (~> 2.0)
@@ -147,13 +157,18 @@ GEM
147157
racc (~> 1.4)
148158
nokogiri (1.19.1-x86_64-linux-musl)
149159
racc (~> 1.4)
160+
parallel (1.27.0)
161+
parser (3.3.10.2)
162+
ast (~> 2.4.1)
163+
racc
150164
pp (0.6.3)
151165
prettyprint
152166
prettyprint (0.2.0)
153167
prism (1.9.0)
154168
psych (5.3.1)
155169
date
156170
stringio
171+
public_suffix (7.0.5)
157172
racc (1.8.1)
158173
rack (3.2.5)
159174
rack-session (2.1.1)
@@ -193,13 +208,31 @@ GEM
193208
thor (~> 1.0, >= 1.2.2)
194209
tsort (>= 0.2)
195210
zeitwerk (~> 2.6)
211+
rainbow (3.1.1)
196212
rake (13.3.1)
197213
rdoc (7.2.0)
198214
erb
199215
psych (>= 4.0.0)
200216
tsort
217+
regexp_parser (2.11.3)
201218
reline (0.6.3)
202219
io-console (~> 0.5)
220+
rubocop (1.85.1)
221+
json (~> 2.3)
222+
language_server-protocol (~> 3.17.0.2)
223+
lint_roller (~> 1.1.0)
224+
mcp (~> 0.6)
225+
parallel (~> 1.10)
226+
parser (>= 3.3.0.2)
227+
rainbow (>= 2.2.2, < 4.0)
228+
regexp_parser (>= 2.9.3, < 3.0)
229+
rubocop-ast (>= 1.49.0, < 2.0)
230+
ruby-progressbar (~> 1.7)
231+
unicode-display_width (>= 2.4.0, < 4.0)
232+
rubocop-ast (1.49.1)
233+
parser (>= 3.3.7.2)
234+
prism (~> 1.7)
235+
ruby-progressbar (1.13.0)
203236
securerandom (0.4.1)
204237
sqlite3 (2.9.2-aarch64-linux-gnu)
205238
sqlite3 (2.9.2-aarch64-linux-musl)
@@ -215,6 +248,9 @@ GEM
215248
tsort (0.2.0)
216249
tzinfo (2.0.6)
217250
concurrent-ruby (~> 1.0)
251+
unicode-display_width (3.2.0)
252+
unicode-emoji (~> 4.1)
253+
unicode-emoji (4.2.0)
218254
uri (1.1.1)
219255
useragent (0.16.11)
220256
websocket-driver (0.8.0)
@@ -236,6 +272,7 @@ PLATFORMS
236272
DEPENDENCIES
237273
irb
238274
rake (~> 13.0)
275+
rubocop
239276
solrengine-auth!
240277
sqlite3
241278

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# frozen_string_literal: true
22

3-
require "bundler/gem_tasks"
3+
require 'bundler/gem_tasks'
44
task default: %i[]

app/controllers/solrengine/auth/concerns/controller_helpers.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
module Solrengine
44
module Auth
55
module Concerns
6+
# Provides authentication helper methods (current_user, logged_in?, authenticate!)
7+
# for inclusion in ApplicationController.
68
module ControllerHelpers
79
extend ActiveSupport::Concern
810

@@ -13,7 +15,9 @@ module ControllerHelpers
1315
private
1416

1517
def current_user
16-
@current_user ||= Solrengine::Auth.configuration.user_model.find_by(id: session[:user_id]) if session[:user_id]
18+
return unless session[:user_id]
19+
20+
@current_user ||= Solrengine::Auth.configuration.user_model.find_by(id: session[:user_id])
1721
end
1822

1923
def logged_in?

app/controllers/solrengine/auth/sessions_controller.rb

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
module Solrengine
44
module Auth
5+
# Handles Solana wallet authentication sessions: nonce generation,
6+
# SIWS signature verification, and session lifecycle.
57
class SessionsController < ApplicationController
6-
78
def new
89
# Renders the wallet connect view
910
end
@@ -12,68 +13,81 @@ def nonce
1213
wallet_address = params[:wallet_address]
1314

1415
unless wallet_address.match?(Concerns::Authenticatable::SOLANA_ADDRESS_FORMAT)
15-
return render json: { error: "Invalid wallet address" }, status: :unprocessable_entity
16+
return render json: { error: 'Invalid wallet address' }, status: :unprocessable_entity
1617
end
1718

18-
user_class = _user_class
19-
user = user_class.find_or_initialize_by(wallet_address: wallet_address)
20-
user.generate_nonce! if user.persisted?
21-
user.save! unless user.persisted?
22-
23-
domain = Solrengine::Auth.configuration.domain
24-
message = SiwsMessageBuilder.new(
25-
domain: domain,
26-
wallet_address: wallet_address,
27-
nonce: user.nonce,
28-
uri: request.base_url
29-
).build
19+
user = find_or_create_user(wallet_address)
20+
message = build_nonce_message(wallet_address, user.nonce)
3021

3122
render json: { message: message, nonce: user.nonce }
3223
end
3324

3425
def create
35-
wallet_address = params[:wallet_address]
36-
message = params[:message]
37-
signature = params[:signature]
38-
39-
user = _user_class.find_by(wallet_address: wallet_address)
26+
user = _user_class.find_by(wallet_address: params[:wallet_address])
4027

4128
unless user&.nonce_valid?
42-
return render json: { error: "Authentication expired. Please try again." }, status: :unprocessable_entity
29+
return render json: { error: 'Authentication expired. Please try again.' }, status: :unprocessable_entity
4330
end
4431

45-
verifier = SiwsVerifier.new(
32+
return render_verification_failed unless verify_signature(user)
33+
return render_nonce_mismatch unless nonce_matches?(params[:message], user)
34+
35+
complete_authentication(user)
36+
end
37+
38+
def destroy
39+
reset_session
40+
redirect_to Solrengine::Auth.configuration.after_sign_out_path, notice: 'Disconnected'
41+
end
42+
43+
private
44+
45+
def _user_class
46+
Solrengine::Auth.configuration.user_model
47+
end
48+
49+
def find_or_create_user(wallet_address)
50+
user = _user_class.find_or_initialize_by(wallet_address: wallet_address)
51+
user.generate_nonce! if user.persisted?
52+
user.save! unless user.persisted?
53+
user
54+
end
55+
56+
def build_nonce_message(wallet_address, nonce)
57+
SiwsMessageBuilder.new(
58+
domain: Solrengine::Auth.configuration.domain,
4659
wallet_address: wallet_address,
47-
message: message,
48-
signature: signature
49-
)
60+
nonce: nonce,
61+
uri: request.base_url
62+
).build
63+
end
5064

51-
unless verifier.verify
52-
return render json: { error: "Signature verification failed" }, status: :unauthorized
53-
end
65+
def verify_signature(user)
66+
SiwsVerifier.new(
67+
wallet_address: user.wallet_address,
68+
message: params[:message],
69+
signature: params[:signature]
70+
).verify
71+
end
5472

55-
# Verify the nonce in the signed message matches the database nonce
73+
def nonce_matches?(message, user)
5674
message_nonce = message.match(/Nonce: ([a-f0-9]+)/)&.captures&.first
57-
unless message_nonce.present? && ActiveSupport::SecurityUtils.secure_compare(message_nonce, user.nonce)
58-
return render json: { error: "Nonce mismatch" }, status: :unauthorized
59-
end
75+
message_nonce.present? && ActiveSupport::SecurityUtils.secure_compare(message_nonce, user.nonce)
76+
end
6077

78+
def complete_authentication(user)
6179
user.generate_nonce!
62-
6380
reset_session
6481
session[:user_id] = user.id
6582
render json: { success: true, wallet_address: user.wallet_address }
6683
end
6784

68-
def destroy
69-
reset_session
70-
redirect_to Solrengine::Auth.configuration.after_sign_out_path, notice: "Disconnected"
85+
def render_verification_failed
86+
render json: { error: 'Signature verification failed' }, status: :unauthorized
7187
end
7288

73-
private
74-
75-
def _user_class
76-
Solrengine::Auth.configuration.user_model
89+
def render_nonce_mismatch
90+
render json: { error: 'Nonce mismatch' }, status: :unauthorized
7791
end
7892
end
7993
end

app/models/solrengine/auth/concerns/authenticatable.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
module Solrengine
44
module Auth
55
module Concerns
6+
# Adds Solana wallet authentication to a User model: wallet address validation,
7+
# nonce generation and expiry for SIWS challenge-response flow.
68
module Authenticatable
79
extend ActiveSupport::Concern
810

911
SOLANA_ADDRESS_FORMAT = /\A[1-9A-HJ-NP-Za-km-z]{32,44}\z/
1012

1113
included do
1214
validates :wallet_address, presence: true, uniqueness: true,
13-
format: { with: SOLANA_ADDRESS_FORMAT, message: "is not a valid Solana address" }
15+
format: { with: SOLANA_ADDRESS_FORMAT, message: 'is not a valid Solana address' }
1416

1517
before_create :generate_nonce
1618
end

bin/console

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#!/usr/bin/env ruby
22
# frozen_string_literal: true
33

4-
require "bundler/setup"
5-
require "solrengine/auth"
4+
require 'bundler/setup'
5+
require 'solrengine/auth'
66

77
# You can add fixtures and/or initialization code here to make experimenting
88
# with your gem easier. You can also use a different console, if you like.
99

10-
require "irb"
10+
require 'irb'
1111
IRB.start(__FILE__)

config/routes.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# frozen_string_literal: true
22

33
Solrengine::Auth::Engine.routes.draw do
4-
get "login", to: "sessions#new", as: :login
5-
post "nonce", to: "sessions#nonce", as: :nonce
6-
post "verify", to: "sessions#create", as: :verify
7-
delete "logout", to: "sessions#destroy", as: :logout
4+
get 'login', to: 'sessions#new', as: :login
5+
post 'nonce', to: 'sessions#nonce', as: :nonce
6+
post 'verify', to: 'sessions#create', as: :verify
7+
delete 'logout', to: 'sessions#destroy', as: :logout
88
end

0 commit comments

Comments
 (0)