@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.
@@ -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;AA4E/C,wBAAsB,SAAS,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBrG;AAID,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;;;;;;;;;;;;;;;EAqBxE;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;EAO/E;AAED,wBAAgB,OAAO;;;;;;;;;;;;;;;;;;;;;;EAWtB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;;;;;EAcpE;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAwKtD;AAID,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM,CAShF;AAID;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1C,OAAO,CAAC,MAAM,CAAC,CA6HjB;AAID,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GACjB,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CA2HrG;AAID,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,GAAG,CAAC,CAUd;AA0JD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAChH,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAsEpF;AAID,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA0BjC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/oauth/server.ts"],"names":[],"mappings":"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"}
@@ -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 Error('grant_type is required');
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 Error(`Unsupported grant_type: ${grantType}`);
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 Error('DPoP jti replay detected');
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 Error('Missing required parameters');
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 Error('Invalid refresh token');
641
+ throw new OAuthError('invalid_grant', 'Invalid refresh token');
621
642
  if (stored.revoked)
622
- throw new Error('Refresh token revoked');
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 Error('Refresh token expired');
645
+ throw new OAuthError('invalid_grant', 'Refresh token expired');
625
646
  if (stored.client_id !== client_id)
626
- throw new Error('client_id mismatch');
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;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAyDA,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,CA+zB5F;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"}
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(jsonError(400, 'DPoP header required', acceptEncoding));
854
- const result = await handleToken(oauth, body, dpopHeader, `${requestOrigin}/oauth/token`);
855
- return withCors(json(result, 200, acceptEncoding));
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatk/hatk",
3
- "version": "0.0.1-alpha.61",
3
+ "version": "0.0.1-alpha.62",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "hatk": "dist/cli.js"