solrengine-auth 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e74477d86c34bcdd7d1e3aec0296d766c1dff71a64aabfabf122e47677a969e9
4
- data.tar.gz: 5936982e40d446e51a142c1151346037aa2f26a9750d4c0a0547a3d5350f3a69
3
+ metadata.gz: 6d8f95b7e835cc386f50d7acf2deadea30d8483dfcbd63fda661c16269f423bc
4
+ data.tar.gz: 97143c16f74468083d1fbb19a857402b54db398f81a703d99b79bf45c1897b58
5
5
  SHA512:
6
- metadata.gz: 1d69599d414495c813be1eac44eb2b51b102a984081201a7a736e752127a52937cd63284c07d493ef514bf6957f8ad03a637d5b2187831c8d19d892368dfc1d9
7
- data.tar.gz: 8c468e7aeaa865762cf973b807597e1f5ea0959ae98410bc121dc8639dc5ae98bbddb8c4e91de8f90a37d088201d65139596c29a9ec9ba4c09fbb257be89bb6b
6
+ metadata.gz: 967003f7900f46356cc26c1302ffeb26c56d642c87c5617b7ee4edede3122c2f04e0760da9ba66a18c806bda05d3aaa28ff87b80b172fd8a2aa6ca1e8f10d5f6
7
+ data.tar.gz: 0404ce446e41f45956f48074a59d8138b8d5ade9f4026b743f2db1d763c3f6bcf75abc4e24931a84f2251aa748551ff78f977a7d201efc86e28eb389791843e9
data/README.md CHANGED
@@ -63,12 +63,17 @@ end
63
63
 
64
64
  1. User clicks "Connect Wallet" -- Stimulus discovers installed wallets via Wallet Standard
65
65
  2. User selects a wallet -- extension popup opens
66
- 3. Rails generates a SIWS message with a nonce (POST `/auth/nonce`)
66
+ 3. Rails generates a SIWS message with a nonce (GET `/auth/nonce`)
67
67
  4. Wallet signs the message (Ed25519)
68
68
  5. Rails verifies the signature, validates the nonce, and creates a session (POST `/auth/verify`)
69
69
 
70
70
  No passwords. No emails. The wallet is the identity.
71
71
 
72
+ The challenge nonce is held in the signed-cookie session, so `POST /auth/nonce`
73
+ performs no database writes — the `User` row is created only after the signature
74
+ is verified in step 5. The `nonce` / `nonce_expires_at` columns are retained for
75
+ backward compatibility but are not used by the bundled controller.
76
+
72
77
  ## Usage in Controllers
73
78
 
74
79
  ```ruby
@@ -165,17 +165,13 @@ export default class extends Controller {
165
165
  }
166
166
  }
167
167
 
168
- // Step 2: Request a nonce from the server
168
+ // Step 2: Request a nonce from the server. GET with a query param, to
169
+ // match @solrengine/wallet-utils — the controller most host apps actually
170
+ // register. The challenge is a read from the server's perspective; it
171
+ // writes only to the signed-cookie session, never the database.
169
172
  this.showSigning()
170
- const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content
171
- const nonceResponse = await fetch(this.nonceUrlValue, {
172
- method: "POST",
173
- headers: {
174
- "Content-Type": "application/json",
175
- "Accept": "application/json",
176
- "X-CSRF-Token": csrfToken
177
- },
178
- body: JSON.stringify({ wallet_address: publicKey })
173
+ const nonceResponse = await fetch(`${this.nonceUrlValue}?wallet_address=${publicKey}`, {
174
+ headers: { "Accept": "application/json" }
179
175
  })
180
176
 
181
177
  if (!nonceResponse.ok) {
@@ -191,6 +187,7 @@ export default class extends Controller {
191
187
  // Step 4: Send the signed message to the server for verification
192
188
  const signatureString = Array.from(signatureBytes).join(",")
193
189
 
190
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content
194
191
  const verifyResponse = await fetch(this.verifyUrlValue, {
195
192
  method: "POST",
196
193
  headers: {
@@ -22,36 +22,46 @@ module Solrengine
22
22
  # Renders the wallet connect view
23
23
  end
24
24
 
25
+ # Issues a SIWS challenge. The nonce lives in the signed-cookie session,
26
+ # NOT the database — an unauthenticated request must never write a row.
27
+ # The user record is created later, in #create, and only after the
28
+ # wallet proves ownership of the key by signing this nonce.
25
29
  def nonce
26
- user = _user_class.find_or_create_by!(wallet_address: params[:wallet_address])
27
- user.generate_nonce!
30
+ wallet_address = params[:wallet_address].to_s
31
+
32
+ unless wallet_address.match?(Solrengine::Auth::Concerns::Authenticatable::SOLANA_ADDRESS_FORMAT)
33
+ return render json: { error: "Invalid wallet address", code: "invalid_wallet_address" },
34
+ status: :unprocessable_entity
35
+ end
36
+
37
+ nonce = SecureRandom.hex(16)
38
+ session[:siws_nonce] = nonce
39
+ session[:siws_wallet] = wallet_address
40
+ session[:siws_nonce_expires_at] = Solrengine::Auth.configuration.nonce_ttl.from_now.iso8601
28
41
 
29
42
  message = SiwsMessageBuilder.new(
30
43
  domain: Solrengine::Auth.configuration.domain,
31
- wallet_address: user.wallet_address,
32
- nonce: user.nonce,
44
+ wallet_address: wallet_address,
45
+ nonce: nonce,
33
46
  uri: request.base_url
34
47
  ).build
35
48
 
36
- render json: { message: message, nonce: user.nonce }
37
- rescue ActiveRecord::RecordInvalid
38
- render json: { error: "Invalid wallet address", code: "invalid_wallet_address" },
39
- status: :unprocessable_entity
49
+ render json: { message: message, nonce: nonce }
40
50
  end
41
51
 
42
52
  def create
43
- user = _user_class.find_by(wallet_address: params[:wallet_address])
53
+ wallet_address = params[:wallet_address].to_s
44
54
 
45
- unless user&.nonce_valid?
55
+ unless session_nonce_valid?(wallet_address)
46
56
  return render json: { error: "Could not sign in", code: "nonce_expired" },
47
57
  status: :unprocessable_entity
48
58
  end
49
59
 
50
60
  verifier = SiwsVerifier.new(
51
- wallet_address: params[:wallet_address],
61
+ wallet_address: wallet_address,
52
62
  message: params[:message],
53
63
  signature: params[:signature],
54
- expected_nonce: user.nonce
64
+ expected_nonce: session[:siws_nonce]
55
65
  )
56
66
 
57
67
  unless verifier.verify
@@ -59,8 +69,11 @@ module Solrengine
59
69
  status: :unauthorized
60
70
  end
61
71
 
62
- user.generate_nonce!
72
+ # Key ownership proven — only now do we touch the database.
73
+ user = _user_class.find_or_create_by!(wallet_address: wallet_address)
63
74
 
75
+ # reset_session clears the (now-consumed) nonce, making it single-use,
76
+ # and rotates the session id to prevent fixation.
64
77
  reset_session
65
78
  session[:user_id] = user.id
66
79
  render json: { success: true, wallet_address: user.wallet_address }
@@ -76,6 +89,19 @@ module Solrengine
76
89
 
77
90
  private
78
91
 
92
+ # The nonce is only valid for the wallet it was issued to, and only
93
+ # until it expires. The signature itself is checked separately, against
94
+ # this nonce, by SiwsVerifier.
95
+ def session_nonce_valid?(wallet_address)
96
+ return false if session[:siws_nonce].blank?
97
+ return false unless session[:siws_wallet].to_s == wallet_address
98
+
99
+ expires_at = session[:siws_nonce_expires_at]
100
+ expires_at.present? && Time.iso8601(expires_at.to_s) > Time.current
101
+ rescue ArgumentError
102
+ false
103
+ end
104
+
79
105
  def _user_class
80
106
  Solrengine::Auth.configuration.user_model
81
107
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Solrengine
4
4
  module Auth
5
- VERSION = "0.2.0"
5
+ VERSION = "0.2.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solrengine-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jose Ferrer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-19 00:00:00.000000000 Z
11
+ date: 2026-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails