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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d8f95b7e835cc386f50d7acf2deadea30d8483dfcbd63fda661c16269f423bc
|
|
4
|
+
data.tar.gz: 97143c16f74468083d1fbb19a857402b54db398f81a703d99b79bf45c1897b58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 (
|
|
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
|
|
171
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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:
|
|
32
|
-
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:
|
|
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
|
-
|
|
53
|
+
wallet_address = params[:wallet_address].to_s
|
|
44
54
|
|
|
45
|
-
unless
|
|
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:
|
|
61
|
+
wallet_address: wallet_address,
|
|
52
62
|
message: params[:message],
|
|
53
63
|
signature: params[:signature],
|
|
54
|
-
expected_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
|
-
|
|
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
|
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.
|
|
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-
|
|
11
|
+
date: 2026-06-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|