@absolutejs/auth 0.74.0 → 0.75.1
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.
- package/README.md +21 -6
- package/dist/agents/index.js +46 -11
- package/dist/agents/index.js.map +4 -4
- package/dist/agents/oidcAdapter.d.ts +3 -1
- package/dist/apikeys/routes.d.ts +1 -1
- package/dist/compliance/routes.d.ts +1 -1
- package/dist/credentials/emailVerification.d.ts +1 -1
- package/dist/credentials/login.d.ts +1 -1
- package/dist/credentials/passwordReset.d.ts +1 -1
- package/dist/credentials/register.d.ts +1 -1
- package/dist/htmx/configuredRoutes.d.ts +1 -1
- package/dist/htmx/routes.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +166 -68
- package/dist/index.js.map +8 -8
- package/dist/mfa/challenge.d.ts +1 -1
- package/dist/mfa/management.d.ts +1 -1
- package/dist/mfa/sms.d.ts +1 -1
- package/dist/mfa/totp.d.ts +1 -1
- package/dist/nativePush.d.ts +500 -20
- package/dist/oidc/dpop.d.ts +6 -2
- package/dist/oidc/index.d.ts +1 -0
- package/dist/oidc/index.js +130 -1
- package/dist/oidc/index.js.map +4 -3
- package/dist/organizations/routes.d.ts +1 -1
- package/dist/portal/routes.d.ts +1 -1
- package/dist/roles/routes.d.ts +1 -1
- package/dist/routes/authorize.d.ts +1 -1
- package/dist/routes/callback.d.ts +1 -1
- package/dist/routes/profile.d.ts +1 -1
- package/dist/routes/refresh.d.ts +1 -1
- package/dist/routes/revoke.d.ts +1 -1
- package/dist/routes/sessions.d.ts +1 -1
- package/dist/routes/signout.d.ts +1 -1
- package/dist/routes/userStatus.d.ts +1 -1
- package/dist/scim/routes.d.ts +1 -1
- package/dist/server.js +164 -68
- package/dist/server.js.map +8 -8
- package/dist/sso/discoveryRoute.d.ts +1 -1
- package/dist/sso/oidcRoutes.d.ts +1 -1
- package/dist/sso/samlIdpRoutes.d.ts +1 -1
- package/dist/sso/samlRoutes.d.ts +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/vc/statusListRoutes.d.ts +1 -1
- package/dist/vc/vpRoutes.d.ts +1 -1
- package/dist/webauthn/routes.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,13 +118,14 @@ stacked and reversible, and web/server runtimes never install the registry.
|
|
|
118
118
|
When portable push is enabled, Auth also owns its authenticated installation
|
|
119
119
|
boundary. Pass the existing Dispatch push lifecycle directly; the server derives
|
|
120
120
|
the principal, tenant, and authorized topics and returns an opaque installation
|
|
121
|
-
identity. The native shell
|
|
122
|
-
ordinary
|
|
121
|
+
identity. The native shell handles APNs/FCM tokens, while the generated PWA
|
|
122
|
+
runtime handles structured browser subscriptions; ordinary page code reads
|
|
123
|
+
neither provider credential:
|
|
123
124
|
|
|
124
125
|
```ts
|
|
125
126
|
const authApplication = await auth({
|
|
126
127
|
// ...normal Auth + OIDC configuration
|
|
127
|
-
|
|
128
|
+
push: {
|
|
128
129
|
registrar: pushLifecycle,
|
|
129
130
|
tenant: (principal) => principal.user.organizationId,
|
|
130
131
|
topics: (principal) => topicsFor(principal.user)
|
|
@@ -132,9 +133,10 @@ const authApplication = await auth({
|
|
|
132
133
|
});
|
|
133
134
|
```
|
|
134
135
|
|
|
135
|
-
The fixed `/auth/
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
The fixed `/auth/push` route accepts bearer-authenticated native clients and
|
|
137
|
+
cookie-authenticated web clients, but never accepts user ID, tenant, or topics
|
|
138
|
+
from either. The prior `/auth/mobile/push` path remains an installed-client
|
|
139
|
+
compatibility alias. Credential rotation and deletion require the server-issued
|
|
138
140
|
installation identity and Dispatch verifies that it belongs to the current
|
|
139
141
|
principal. Native sign-out attempts removal while credentials still exist and
|
|
140
142
|
always clears the local provider registration even when the network is down.
|
|
@@ -279,8 +281,11 @@ const authPlugin = await auth({
|
|
|
279
281
|
resource: 'https://api.example.com',
|
|
280
282
|
scopes: ['documents:read', 'documents:write'],
|
|
281
283
|
verifyCredential: createOidcAgentCredentialVerifier({
|
|
284
|
+
// Atomically insert a hash of jkt + jti; return false on conflict.
|
|
285
|
+
consumeDpopJti: replayStore.consume,
|
|
282
286
|
issuer: 'https://auth.example.com',
|
|
283
287
|
publicJwk: signingKey.publicJwk,
|
|
288
|
+
requireDpop: true,
|
|
284
289
|
resource: 'https://api.example.com'
|
|
285
290
|
})
|
|
286
291
|
},
|
|
@@ -298,6 +303,16 @@ registration. Approval through the existing RFC 8628 device flow creates the
|
|
|
298
303
|
user-to-agent delegation. The agent can then use RFC 8693 token exchange to get
|
|
299
304
|
a narrowed, audience-bound access token for the protected API.
|
|
300
305
|
|
|
306
|
+
When `requireDpop` is enabled, the adapter accepts only an RFC 9449-bound
|
|
307
|
+
access token using the `DPoP` authorization scheme, verifies its per-request
|
|
308
|
+
proof and `ath` token hash, and requires the proof key to match `cnf.jkt`.
|
|
309
|
+
Provide `consumeDpopJti` as an atomic shared-store insertion in clustered
|
|
310
|
+
deployments; returning `false` rejects a replay. Proofs without `jti`, proofs
|
|
311
|
+
whose JWK contains private key material, oversized identifiers, and `htu`
|
|
312
|
+
claims containing query or fragment components fail closed. Resource servers
|
|
313
|
+
that require RFC 9449 nonces can use the nonce helpers exported by
|
|
314
|
+
`@absolutejs/auth/oidc` to issue a separate resource nonce challenge.
|
|
315
|
+
|
|
301
316
|
```ts
|
|
302
317
|
app.get('/documents', ({ protectAgent }) =>
|
|
303
318
|
protectAgent(['documents:read'], (agent) => ({
|
package/dist/agents/index.js
CHANGED
|
@@ -1349,6 +1349,7 @@ var validateClientIdMetadataDocument = (document, expectedClientId) => {
|
|
|
1349
1349
|
// src/oidc/dpop.ts
|
|
1350
1350
|
init_constants();
|
|
1351
1351
|
var DEFAULT_MAX_AGE_MS = 60000;
|
|
1352
|
+
var MAX_JTI_LENGTH = 128;
|
|
1352
1353
|
var SECONDS_TO_MS = 1000;
|
|
1353
1354
|
var NONCE_WINDOW_SECONDS = 120;
|
|
1354
1355
|
var NONCE_WINDOW_MS = NONCE_WINDOW_SECONDS * MILLISECONDS_IN_A_SECOND;
|
|
@@ -1389,17 +1390,38 @@ var verifyDpopNonce = async ({
|
|
|
1389
1390
|
const candidates = await Promise.all(Array.from({ length: NONCE_PREVIOUS_WINDOWS_ACCEPTED + 1 }, (_, offset) => hmacSha256(secret, String(currentWindow - offset))));
|
|
1390
1391
|
return candidates.some((expected) => expected === nonce);
|
|
1391
1392
|
};
|
|
1392
|
-
var decodeHeader = (segment) =>
|
|
1393
|
-
|
|
1393
|
+
var decodeHeader = (segment) => {
|
|
1394
|
+
try {
|
|
1395
|
+
const value = JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
|
|
1396
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
1397
|
+
} catch {
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
var normalizeHtu = (value, proofClaim = false) => {
|
|
1394
1402
|
try {
|
|
1395
1403
|
const url = new URL(String(value));
|
|
1404
|
+
if (url.username || url.password || proofClaim && (url.search.length > 0 || url.hash.length > 0))
|
|
1405
|
+
return;
|
|
1396
1406
|
return `${url.origin}${url.pathname}`;
|
|
1397
1407
|
} catch {
|
|
1398
|
-
return
|
|
1408
|
+
return;
|
|
1399
1409
|
}
|
|
1400
1410
|
};
|
|
1411
|
+
var isPublicEs256Jwk = (value) => {
|
|
1412
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
1413
|
+
return false;
|
|
1414
|
+
const kty = Reflect.get(value, "kty");
|
|
1415
|
+
const crv = Reflect.get(value, "crv");
|
|
1416
|
+
const xCoordinate = Reflect.get(value, "x");
|
|
1417
|
+
const yCoordinate = Reflect.get(value, "y");
|
|
1418
|
+
const privateComponent = Reflect.get(value, "d");
|
|
1419
|
+
const alg = Reflect.get(value, "alg");
|
|
1420
|
+
return kty === "EC" && crv === "P-256" && typeof xCoordinate === "string" && xCoordinate.length > 0 && typeof yCoordinate === "string" && yCoordinate.length > 0 && privateComponent === undefined && (alg === undefined || alg === "ES256");
|
|
1421
|
+
};
|
|
1401
1422
|
var verifyDpopProof = async ({
|
|
1402
1423
|
accessToken,
|
|
1424
|
+
consumeJti,
|
|
1403
1425
|
htm,
|
|
1404
1426
|
htu,
|
|
1405
1427
|
isUsedJti,
|
|
@@ -1409,14 +1431,20 @@ var verifyDpopProof = async ({
|
|
|
1409
1431
|
}) => {
|
|
1410
1432
|
if (proof === undefined)
|
|
1411
1433
|
return;
|
|
1412
|
-
const
|
|
1434
|
+
const segments = proof.split(".");
|
|
1435
|
+
if (segments.length !== 3)
|
|
1436
|
+
return;
|
|
1437
|
+
const [headerSegment] = segments;
|
|
1413
1438
|
if (headerSegment === undefined)
|
|
1414
1439
|
return;
|
|
1415
1440
|
const header = decodeHeader(headerSegment);
|
|
1416
|
-
|
|
1441
|
+
const publicJwk = header === undefined ? undefined : Reflect.get(header, "jwk");
|
|
1442
|
+
if (header === undefined || Reflect.get(header, "typ") !== "dpop+jwt" || Reflect.get(header, "alg") !== "ES256" || !isPublicEs256Jwk(publicJwk)) {
|
|
1417
1443
|
return;
|
|
1418
1444
|
}
|
|
1419
|
-
const verified = await verifyJwt(proof,
|
|
1445
|
+
const verified = await verifyJwt(proof, publicJwk).catch(() => {
|
|
1446
|
+
return;
|
|
1447
|
+
});
|
|
1420
1448
|
if (verified === undefined)
|
|
1421
1449
|
return;
|
|
1422
1450
|
const { payload } = verified;
|
|
@@ -1426,14 +1454,19 @@ var verifyDpopProof = async ({
|
|
|
1426
1454
|
return;
|
|
1427
1455
|
}
|
|
1428
1456
|
const iatMs = typeof payload.iat === "number" ? payload.iat * SECONDS_TO_MS : 0;
|
|
1429
|
-
|
|
1457
|
+
const claimedHtu = normalizeHtu(payload.htu, true);
|
|
1458
|
+
const expectedHtu = normalizeHtu(htu);
|
|
1459
|
+
const jti = typeof payload.jti === "string" ? payload.jti : undefined;
|
|
1460
|
+
if (payload.htm !== htm || claimedHtu === undefined || expectedHtu === undefined || claimedHtu !== expectedHtu || iatMs === 0 || Math.abs(now - iatMs) > maxAgeMs || jti === undefined || jti.length === 0 || jti.length > MAX_JTI_LENGTH) {
|
|
1430
1461
|
return;
|
|
1431
1462
|
}
|
|
1432
|
-
const
|
|
1433
|
-
if (
|
|
1463
|
+
const jkt = await jwkThumbprint(publicJwk);
|
|
1464
|
+
if (isUsedJti !== undefined && await isUsedJti(jti)) {
|
|
1434
1465
|
return;
|
|
1435
1466
|
}
|
|
1436
|
-
|
|
1467
|
+
if (consumeJti !== undefined && !await consumeJti({ expiresAt: iatMs + maxAgeMs, jkt, jti }))
|
|
1468
|
+
return;
|
|
1469
|
+
return { jkt, jti };
|
|
1437
1470
|
};
|
|
1438
1471
|
|
|
1439
1472
|
// src/agents/oidcAdapter.ts
|
|
@@ -1449,6 +1482,7 @@ var readAudience = (audience) => {
|
|
|
1449
1482
|
};
|
|
1450
1483
|
var createOidcAgentCredentialVerifier = ({
|
|
1451
1484
|
issuer,
|
|
1485
|
+
consumeDpopJti,
|
|
1452
1486
|
isUsedDpopJti,
|
|
1453
1487
|
maxDpopAgeMs,
|
|
1454
1488
|
publicJwk,
|
|
@@ -1477,6 +1511,7 @@ var createOidcAgentCredentialVerifier = ({
|
|
|
1477
1511
|
return;
|
|
1478
1512
|
const proof = await verifyDpopProof({
|
|
1479
1513
|
accessToken: token,
|
|
1514
|
+
consumeJti: consumeDpopJti,
|
|
1480
1515
|
htm: request.method,
|
|
1481
1516
|
htu: request.url,
|
|
1482
1517
|
isUsedJti: isUsedDpopJti,
|
|
@@ -13763,5 +13798,5 @@ export {
|
|
|
13763
13798
|
AGENT_CLAIM_GRANT_TYPE
|
|
13764
13799
|
};
|
|
13765
13800
|
|
|
13766
|
-
//# debugId=
|
|
13801
|
+
//# debugId=801AFAC38E904B9A64756E2164756E21
|
|
13767
13802
|
//# sourceMappingURL=index.js.map
|