@hatk/hatk 0.0.1-alpha.61 → 0.0.1-alpha.62
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/dist/oauth/server.d.ts +14 -0
- package/dist/oauth/server.d.ts.map +1 -1
- package/dist/oauth/server.js +30 -9
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +14 -5
- package/package.json +1 -1
package/dist/oauth/server.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import type { OAuthConfig } from '../config.ts';
|
|
2
|
+
/**
|
|
3
|
+
* RFC 6749 §5.2 OAuth token-endpoint error.
|
|
4
|
+
*
|
|
5
|
+
* Surfaces as HTTP `status` (default 400) with body
|
|
6
|
+
* `{ error: code, error_description: description }` so OAuth clients can
|
|
7
|
+
* distinguish a permanently-rejected refresh token (`invalid_grant`) from
|
|
8
|
+
* a transient server failure and decide whether to log the user out.
|
|
9
|
+
*/
|
|
10
|
+
export declare class OAuthError extends Error {
|
|
11
|
+
readonly code: 'invalid_request' | 'invalid_client' | 'invalid_grant' | 'unauthorized_client' | 'unsupported_grant_type' | 'invalid_scope';
|
|
12
|
+
readonly description: string;
|
|
13
|
+
readonly status: number;
|
|
14
|
+
constructor(code: 'invalid_request' | 'invalid_client' | 'invalid_grant' | 'unauthorized_client' | 'unsupported_grant_type' | 'invalid_scope', description: string, status?: number);
|
|
15
|
+
}
|
|
2
16
|
export declare function initOAuth(_config: OAuthConfig, plcUrl: string, relayUrl: string): Promise<void>;
|
|
3
17
|
export declare function getAuthServerMetadata(issuer: string, config: OAuthConfig): {
|
|
4
18
|
issuer: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/oauth/server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/oauth/server.ts"],"names":[],"mappings":"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"}
|
package/dist/oauth/server.js
CHANGED
|
@@ -9,6 +9,26 @@ import { emit } from "../logger.js";
|
|
|
9
9
|
import { querySQL } from "../database/db.js";
|
|
10
10
|
import { fireOnLoginHook } from "../hooks.js";
|
|
11
11
|
const SERVER_KEY_KID = 'appview-oauth-key';
|
|
12
|
+
/**
|
|
13
|
+
* RFC 6749 §5.2 OAuth token-endpoint error.
|
|
14
|
+
*
|
|
15
|
+
* Surfaces as HTTP `status` (default 400) with body
|
|
16
|
+
* `{ error: code, error_description: description }` so OAuth clients can
|
|
17
|
+
* distinguish a permanently-rejected refresh token (`invalid_grant`) from
|
|
18
|
+
* a transient server failure and decide whether to log the user out.
|
|
19
|
+
*/
|
|
20
|
+
export class OAuthError extends Error {
|
|
21
|
+
code;
|
|
22
|
+
description;
|
|
23
|
+
status;
|
|
24
|
+
constructor(code, description, status = 400) {
|
|
25
|
+
super(description);
|
|
26
|
+
this.code = code;
|
|
27
|
+
this.description = description;
|
|
28
|
+
this.status = status;
|
|
29
|
+
this.name = 'OAuthError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
12
32
|
async function resolveHandleForDid(did) {
|
|
13
33
|
const rows = (await querySQL('SELECT handle FROM _repos WHERE did = $1', [did]));
|
|
14
34
|
return rows[0]?.handle || undefined;
|
|
@@ -530,14 +550,14 @@ export async function handleCallback(config, code, state, iss) {
|
|
|
530
550
|
export async function handleToken(config, body, dpopHeader, requestUrl) {
|
|
531
551
|
const grantType = body.grant_type;
|
|
532
552
|
if (!grantType)
|
|
533
|
-
throw new
|
|
553
|
+
throw new OAuthError('invalid_request', 'grant_type is required');
|
|
534
554
|
if (grantType === 'authorization_code') {
|
|
535
555
|
return handleAuthorizationCodeGrant(config, body, dpopHeader, requestUrl);
|
|
536
556
|
}
|
|
537
557
|
else if (grantType === 'refresh_token') {
|
|
538
558
|
return handleRefreshTokenGrant(config, body, dpopHeader, requestUrl);
|
|
539
559
|
}
|
|
540
|
-
throw new
|
|
560
|
+
throw new OAuthError('unsupported_grant_type', `Unsupported grant_type: ${grantType}`);
|
|
541
561
|
}
|
|
542
562
|
async function handleAuthorizationCodeGrant(config, body, dpopHeader, requestUrl) {
|
|
543
563
|
const dpop = await parseDpopProof(dpopHeader, 'POST', requestUrl);
|
|
@@ -610,20 +630,21 @@ async function handleRefreshTokenGrant(config, body, dpopHeader, requestUrl) {
|
|
|
610
630
|
const dpop = await parseDpopProof(dpopHeader, 'POST', requestUrl);
|
|
611
631
|
const fresh = await checkAndStoreDpopJti(dpop.jti, dpop.iat + 300);
|
|
612
632
|
if (!fresh)
|
|
613
|
-
throw new
|
|
633
|
+
throw new OAuthError('invalid_request', 'DPoP jti replay detected');
|
|
614
634
|
const { refresh_token, client_id } = body;
|
|
615
635
|
if (!refresh_token || !client_id)
|
|
616
|
-
throw new
|
|
617
|
-
// Look up and validate refresh token
|
|
636
|
+
throw new OAuthError('invalid_request', 'Missing required parameters');
|
|
637
|
+
// Look up and validate refresh token. Per RFC 6749 §5.2, all of these are
|
|
638
|
+
// `invalid_grant` — the client must treat them as terminal and re-authenticate.
|
|
618
639
|
const stored = await getRefreshToken(refresh_token);
|
|
619
640
|
if (!stored)
|
|
620
|
-
throw new
|
|
641
|
+
throw new OAuthError('invalid_grant', 'Invalid refresh token');
|
|
621
642
|
if (stored.revoked)
|
|
622
|
-
throw new
|
|
643
|
+
throw new OAuthError('invalid_grant', 'Refresh token revoked');
|
|
623
644
|
if (stored.expires_at && stored.expires_at < Math.floor(Date.now() / 1000))
|
|
624
|
-
throw new
|
|
645
|
+
throw new OAuthError('invalid_grant', 'Refresh token expired');
|
|
625
646
|
if (stored.client_id !== client_id)
|
|
626
|
-
throw new
|
|
647
|
+
throw new OAuthError('invalid_grant', 'client_id mismatch');
|
|
627
648
|
// Revoke old refresh token (rotation)
|
|
628
649
|
await revokeRefreshToken(refresh_token);
|
|
629
650
|
const did = stored.did;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AA0DA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AA2B9C;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAgM3F;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;IACzB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACxF,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA00B5F;AAGD,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EAAE,EACrB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,KAAK,EAAE,WAAW,GAAG,IAAI,EACzB,MAAM,GAAE,MAAM,EAAO,EACrB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,GACpB,OAAO,WAAW,EAAE,MAAM,CAG5B"}
|
package/dist/server.js
CHANGED
|
@@ -9,7 +9,7 @@ import { handleOpengraphRequest, buildOgMeta } from "./opengraph.js";
|
|
|
9
9
|
import { getLabelDefinitions, rescanLabels } from "./labels.js";
|
|
10
10
|
import { triggerAutoBackfill } from "./indexer.js";
|
|
11
11
|
import { emit, timer } from "./logger.js";
|
|
12
|
-
import { getAuthServerMetadata, getProtectedResourceMetadata, getJwks, getClientMetadata, handlePar, buildAuthorizeRedirect, handleCallback, serverLogin, handleToken, authenticate, } from "./oauth/server.js";
|
|
12
|
+
import { getAuthServerMetadata, getProtectedResourceMetadata, getJwks, getClientMetadata, handlePar, buildAuthorizeRedirect, handleCallback, serverLogin, handleToken, authenticate, OAuthError, } from "./oauth/server.js";
|
|
13
13
|
import { createSessionCookie, sessionCookieHeader, clearSessionCookieHeader, parseSessionCookie, } from "./oauth/session.js";
|
|
14
14
|
import { getOAuthRequest } from "./oauth/db.js";
|
|
15
15
|
import { pdsCreateRecord, pdsDeleteRecord, pdsPutRecord, pdsApplyWrites, pdsUploadBlob, ProxyError, ScopeMissingProxyError, } from "./pds-proxy.js";
|
|
@@ -849,10 +849,19 @@ export function createHandler(config) {
|
|
|
849
849
|
body = JSON.parse(rawBody);
|
|
850
850
|
}
|
|
851
851
|
const dpopHeader = request.headers.get('dpop');
|
|
852
|
-
if (!dpopHeader)
|
|
853
|
-
return withCors(
|
|
854
|
-
|
|
855
|
-
|
|
852
|
+
if (!dpopHeader) {
|
|
853
|
+
return withCors(json({ error: 'invalid_request', error_description: 'DPoP header required' }, 400, acceptEncoding));
|
|
854
|
+
}
|
|
855
|
+
try {
|
|
856
|
+
const result = await handleToken(oauth, body, dpopHeader, `${requestOrigin}/oauth/token`);
|
|
857
|
+
return withCors(json(result, 200, acceptEncoding));
|
|
858
|
+
}
|
|
859
|
+
catch (err) {
|
|
860
|
+
if (err instanceof OAuthError) {
|
|
861
|
+
return withCors(json({ error: err.code, error_description: err.description }, err.status, acceptEncoding));
|
|
862
|
+
}
|
|
863
|
+
throw err;
|
|
864
|
+
}
|
|
856
865
|
}
|
|
857
866
|
// POST /xrpc/dev.hatk.createRecord — proxy write to user's PDS
|
|
858
867
|
if (url.pathname === coreXrpc('createRecord') && request.method === 'POST' && oauth) {
|