FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
JavaScriptSolidServer/src/auth/token.js at gh-pages · JavaScriptSolidServer/JavaScriptSolidServer · GitHub
JavaScriptSolidServer
JavaScriptSolidServer
Repository navigation
Code
Issues
164
(164)
Pull requests
14
(14)
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
JavaScriptSolidServer
/
src
/
auth
/
token.js
Copy path
More file actions
More file actions
Latest commit
History
History
History
263 lines (224 loc) · 7.33 KB
Breadcrumbs
JavaScriptSolidServer
/
src
/
auth
/
token.js
Copy path
File metadata and controls
263 lines (224 loc) · 7.33 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
/**
* Token-based authentication
*
* Supports multiple modes:
* 1. Simple tokens (for local/dev use): base64(JSON({webId, iat, exp})) + HMAC signature
* 2. Solid-OIDC DPoP tokens (for federation): verified via external IdP JWKS
* 3. LWS10-CID JWTs (FPWD 2026-04-23): kid points at a verificationMethod
* in the subject's WebID profile; signed with a JWS alg (ES256K, ES256, …)
* 4. Nostr NIP-98 tokens: Schnorr signatures, returns did:nostr identity
*/
import
crypto
from
'crypto'
;
import
{
verifySolidOidc
,
hasSolidOidcAuth
}
from
'./solid-oidc.js'
;
import
{
verifyLwsCidAuth
,
hasLwsCidAuth
}
from
'./lws-cid.js'
;
import
{
verifyNostrAuth
,
hasNostrAuth
}
from
'./nostr.js'
;
import
{
webIdTlsAuth
,
hasClientCertificate
}
from
'./webid-tls.js'
;
import
{
resolveTokenSecret
}
from
'./token-secret.js'
;
// Initialize secret once at module load. See token-secret.js for the
// resolution order (env → ~/.jss/token.secret → exit-or-ephemeral).
const
SECRET
=
resolveTokenSecret
(
)
;
/**
* Create a simple token for a WebID
*
@param
{
string
} webId - The WebID to create token for
*
@param
{
number
} [expiresIn] - Expiration time in seconds (default: no expiry)
*
@returns
{
string
} Token string
*/
export
function
createToken
(
webId
,
expiresIn
)
{
const
payload
=
{
webId
,
iat
:
Math
.
floor
(
Date
.
now
(
)
/
1000
)
,
}
;
if
(
expiresIn
!==
undefined
&&
expiresIn
>
0
)
{
payload
.
exp
=
Math
.
floor
(
Date
.
now
(
)
/
1000
)
+
expiresIn
;
}
const
data
=
Buffer
.
from
(
JSON
.
stringify
(
payload
)
)
.
toString
(
'base64url'
)
;
const
signature
=
crypto
.
createHmac
(
'sha256'
,
SECRET
)
.
update
(
data
)
.
digest
(
'base64url'
)
;
return
`
${
data
}
.
${
signature
}
`
;
}
/**
* Verify and decode a simple token (2-part HMAC-signed)
*
* SECURITY: Only accepts 2-part simple tokens signed with HMAC.
* JWT tokens (3-part) require async verification via verifyTokenAsync().
*
*
@param
{
string
} token - The token to verify
*
@returns
{
{webId: string, iat: number, exp?: number} | null
} Decoded payload or null
*/
export
function
verifyToken
(
token
)
{
if
(
!
token
||
typeof
token
!==
'string'
)
{
return
null
;
}
const
parts
=
token
.
split
(
'.'
)
;
// JWT tokens (3 parts) require async verification - reject in sync function
if
(
parts
.
length
===
3
)
{
return
null
;
}
if
(
parts
.
length
!==
2
)
{
return
null
;
}
const
[
data
,
signature
]
=
parts
;
// Verify HMAC signature
const
expectedSig
=
crypto
.
createHmac
(
'sha256'
,
SECRET
)
.
update
(
data
)
.
digest
(
'base64url'
)
;
// Constant-time comparison to prevent timing attacks
try
{
if
(
!
crypto
.
timingSafeEqual
(
Buffer
.
from
(
signature
)
,
Buffer
.
from
(
expectedSig
)
)
)
{
return
null
;
}
}
catch
{
// If lengths don't match, timingSafeEqual throws
return
null
;
}
// Decode payload
try
{
const
payload
=
JSON
.
parse
(
Buffer
.
from
(
data
,
'base64url'
)
.
toString
(
)
)
;
// Check expiration
if
(
payload
.
exp
&&
payload
.
exp
<
Math
.
floor
(
Date
.
now
(
)
/
1000
)
)
{
return
null
;
}
return
payload
;
}
catch
{
return
null
;
}
}
/**
* Verify a JWT token from the credentials endpoint
* Properly verifies signature against IdP's JWKS
*
*
@param
{
string
} token - JWT token
*
@returns
{
Promise<{webId: string, iat: number, exp: number} | null>
}
*/
async
function
verifyJwtFromIdp
(
token
)
{
try
{
// Dynamically import to avoid circular dependencies
const
{
getPublicJwks
}
=
await
import
(
'../idp/keys.js'
)
;
const
jose
=
await
import
(
'jose'
)
;
const
jwks
=
await
getPublicJwks
(
)
;
if
(
!
jwks
||
!
jwks
.
keys
||
jwks
.
keys
.
length
===
0
)
{
return
null
;
}
// Create JWKS for verification
const
keySet
=
jose
.
createLocalJWKSet
(
jwks
)
;
// Verify the token
const
{
payload
}
=
await
jose
.
jwtVerify
(
token
,
keySet
,
{
// Allow some clock skew
clockTolerance
:
60
,
}
)
;
// Extract webid claim
const
webId
=
payload
.
webid
||
payload
.
webId
||
payload
.
sub
;
if
(
!
webId
)
{
return
null
;
}
return
{
webId
,
iat
:
payload
.
iat
,
exp
:
payload
.
exp
}
;
}
catch
(
err
)
{
// Verification failed - invalid signature, expired, etc.
return
null
;
}
}
/**
* Extract token from Authorization header
*
@param
{
string
} authHeader - Authorization header value
*
@returns
{
string | null
} Token or null
*/
export
function
extractToken
(
authHeader
)
{
if
(
!
authHeader
||
typeof
authHeader
!==
'string'
)
{
return
null
;
}
// Support "Bearer <token>" format
if
(
authHeader
.
startsWith
(
'Bearer '
)
)
{
return
authHeader
.
slice
(
7
)
;
}
// Also support raw token
return
authHeader
;
}
/**
* Extract WebID from request (sync version for simple tokens only)
*
@param
{
object
} request - Fastify request object
*
@returns
{
string | null
} WebID or null if not authenticated
*/
export
function
getWebIdFromRequest
(
request
)
{
const
authHeader
=
request
.
headers
.
authorization
;
// Skip DPoP tokens - use async version for those
if
(
authHeader
&&
authHeader
.
startsWith
(
'DPoP '
)
)
{
return
null
;
}
// Skip Nostr tokens - use async version for those
if
(
authHeader
&&
authHeader
.
startsWith
(
'Nostr '
)
)
{
return
null
;
}
const
token
=
extractToken
(
authHeader
)
;
if
(
!
token
)
{
return
null
;
}
const
payload
=
verifyToken
(
token
)
;
return
payload
?.
webId
||
null
;
}
/**
* Extract WebID from request (async version supporting Solid-OIDC)
*
@param
{
object
} request - Fastify request object
*
@returns
{
Promise<{webId: string|null, error: string|null}>
}
*/
export
async
function
getWebIdFromRequestAsync
(
request
)
{
const
authHeader
=
request
.
headers
.
authorization
;
// Try Authorization header methods first
if
(
authHeader
)
{
// Try Solid-OIDC first (DPoP tokens)
if
(
hasSolidOidcAuth
(
request
)
)
{
return
verifySolidOidc
(
request
)
;
}
// Try LWS10-CID (Bearer JWT whose kid is a fragment URL into a CID
// document). Detected by header shape, so it doesn't conflict with
// the IDP-issued JWTs handled in the Bearer fallback below — those
// use opaque fingerprint kids, not URLs.
if
(
hasLwsCidAuth
(
request
)
)
{
return
verifyLwsCidAuth
(
request
)
;
}
// Try Nostr NIP-98 (Schnorr signatures)
if
(
hasNostrAuth
(
request
)
)
{
return
verifyNostrAuth
(
request
)
;
}
// Fall back to Bearer tokens
const
token
=
extractToken
(
authHeader
)
;
if
(
token
)
{
// Try simple 2-part token first
const
payload
=
verifyToken
(
token
)
;
if
(
payload
?.
webId
)
{
return
{
webId
:
payload
.
webId
,
error
:
null
}
;
}
// If 3-part JWT, verify against IdP's JWKS
const
parts
=
token
.
split
(
'.'
)
;
if
(
parts
.
length
===
3
)
{
const
jwtPayload
=
await
verifyJwtFromIdp
(
token
)
;
if
(
jwtPayload
?.
webId
)
{
return
{
webId
:
jwtPayload
.
webId
,
error
:
null
}
;
}
return
{
webId
:
null
,
error
:
'Invalid or unverifiable JWT token'
}
;
}
return
{
webId
:
null
,
error
:
'Invalid token'
}
;
}
}
// Try WebID-TLS (client certificate authentication)
// This works even without Authorization header
if
(
hasClientCertificate
(
request
)
)
{
try
{
const
webId
=
await
webIdTlsAuth
(
request
)
;
if
(
webId
)
{
return
{
webId
,
error
:
null
}
;
}
// Certificate present but verification failed
return
{
webId
:
null
,
error
:
'WebID-TLS certificate verification failed'
}
;
}
catch
(
err
)
{
return
{
webId
:
null
,
error
:
`WebID-TLS error:
${
err
.
message
}
`
}
;
}
}
return
{
webId
:
null
,
error
:
null
}
;
}
Back
|
FazBrowse Home
|
New Git URL