@hatk/hatk 0.0.1-alpha.95 → 0.0.1-alpha.96

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/main.js CHANGED
@@ -14,7 +14,7 @@ import { log } from "./logger.js";
14
14
  import { loadConfig } from "./config.js";
15
15
  import { validatableLexicons, loadLexicons, storeLexicons, discoverCollections, buildSchemas, } from "./database/schema.js";
16
16
  import { discoverViews } from "./views.js";
17
- import { initDatabase, getCursor, querySQL, getSqlDialect, getSchemaDump, migrateSchema } from "./database/db.js";
17
+ import { initDatabase, getCursor, querySQL, getSqlDialect, getSchemaDump } from "./database/db.js";
18
18
  import { createAdapter } from "./database/adapter-factory.js";
19
19
  import { getDialect } from "./database/dialect.js";
20
20
  import { setSearchPort } from "./database/fts.js";
@@ -22,6 +22,7 @@ import { initFeeds, listFeeds } from "./feeds.js";
22
22
  import { initXrpc, listXrpc, configureRelay, configureCdn, configureOAuth, callXrpc } from "./xrpc.js";
23
23
  import { initOpengraph } from "./opengraph.js";
24
24
  import { initLabels, getLabelDefinitions } from "./labels.js";
25
+ import { initClassifiers, listClassifiers, configureSystemOne } from "./classifiers.js";
25
26
  import { auxCursorKey, jetstreamCursorKey, startAuxIndexer, startIndexer, sweepReferences } from "./indexer.js";
26
27
  import { startJetstreamIndexer } from "./jetstream.js";
27
28
  import { rebuildAllIndexes } from "./database/fts.js";
@@ -67,7 +68,7 @@ log(`[main] Loaded config: ${collections.length} collections`);
67
68
  // Discover view defs from lexicons
68
69
  discoverViews();
69
70
  const engineDialect = getDialect(config.databaseEngine);
70
- const { schemas, ddlStatements } = buildSchemas(lexicons, collections, engineDialect);
71
+ const { schemas, ddlStatements, indexStatements } = buildSchemas(lexicons, collections, engineDialect);
71
72
  for (const s of schemas) {
72
73
  if (s.columns.length === 0) {
73
74
  log(`[main] No lexicon found for ${s.collection}, using generic JSON storage`);
@@ -82,10 +83,9 @@ if (config.database !== ':memory:') {
82
83
  }
83
84
  const { adapter, searchPort } = await createAdapter(config.databaseEngine);
84
85
  setSearchPort(searchPort);
85
- await initDatabase(adapter, config.database, schemas, ddlStatements);
86
+ // Migrates as part of opening, between creating the tables and indexing them.
87
+ const migrationChanges = await initDatabase(adapter, config.database, schemas, ddlStatements, indexStatements);
86
88
  log(`[main] Database initialized (${config.databaseEngine}, ${config.database === ':memory:' ? 'in-memory' : config.database})`);
87
- // Auto-migrate schema if lexicons changed
88
- const migrationChanges = await migrateSchema(schemas);
89
89
  if (migrationChanges.length > 0) {
90
90
  log(`[main] Applied ${migrationChanges.length} schema migration(s)`);
91
91
  }
@@ -109,8 +109,11 @@ else {
109
109
  log(`[main] OpenGraph initialized`);
110
110
  await initLabels(resolve(configDir, 'labels'));
111
111
  log(`[main] Labels initialized: ${getLabelDefinitions().length} definitions`);
112
+ await initClassifiers(resolve(configDir, 'classifiers'));
113
+ log(`[main] Classifiers initialized: ${listClassifiers().join(', ') || 'none'}`);
112
114
  }
113
115
  // Register built-in dev.hatk.* handlers so callXrpc() can find them
116
+ configureSystemOne(config.typesafe);
114
117
  setPrivateCollections(config.privateCollections);
115
118
  registerCoreHandlers(collections, config.oauth);
116
119
  configureOAuth(config.oauth);
@@ -160,6 +160,42 @@ export declare function handleCallback(config: OAuthConfig, code: string, state:
160
160
  */
161
161
  export declare function handleCallbackError(config: OAuthConfig, error: string, errorDescription: string | null, state: string | null): Promise<string>;
162
162
  export declare function handleToken(config: OAuthConfig, body: Record<string, string>, dpopHeader: string, requestUrl: string): Promise<any>;
163
+ /** A refusal from the endpoint {@link obtainSession} called, or an answer it cannot use. */
164
+ export declare class ObtainSessionError extends Error {
165
+ /** The endpoint's error code, e.g. an XRPC error name. */
166
+ readonly error: string;
167
+ readonly status: number;
168
+ constructor(
169
+ /** The endpoint's error code, e.g. an XRPC error name. */
170
+ error: string, message: string, status: number);
171
+ }
172
+ /** What {@link obtainSession} returns: the endpoint's whole response, and whose session it held. */
173
+ export interface ObtainedSession {
174
+ did: string;
175
+ response: Record<string, unknown>;
176
+ }
177
+ /**
178
+ * Obtain a session for another account straight from its authorization server,
179
+ * outside the redirect flow: a group host creating an account for this app and
180
+ * handing it a session, say.
181
+ *
182
+ * `body` is POSTed as JSON to `url` as this app's OAuth client, the way the
183
+ * token endpoint is called: `client_id` and, for a confidential client, a client
184
+ * assertion addressed to the endpoint's origin, are added to it, and the request
185
+ * carries a DPoP proof from the app's key, answering a nonce challenge once.
186
+ * `headers` are sent too, for whatever else the endpoint wants to know (service
187
+ * auth from the viewer, typically).
188
+ *
189
+ * The response must hold an OAuth token response, at `session` or as the whole
190
+ * body. That session is stored for its `sub` exactly as a login's is, bound to
191
+ * the same DPoP key, so refresh and the PDS helpers work for that DID from here
192
+ * on. It is only accepted if the account's own authorization server is the one
193
+ * that answered: an endpoint cannot hand over a session for an account it does
194
+ * not serve.
195
+ */
196
+ export declare function obtainSession(config: OAuthConfig, url: string, body: Record<string, unknown>, opts?: {
197
+ headers?: Record<string, string>;
198
+ }): Promise<ObtainedSession>;
163
199
  export declare function refreshPdsSession(config: OAuthConfig, session: {
164
200
  did: string;
165
201
  pds_endpoint: 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;AAgD/C;;;;;;;GAOG;AACH,qBAAa,UAAW,SAAQ,KAAK;aAEjB,IAAI,EAChB,iBAAiB,GACjB,gBAAgB,GAChB,eAAe,GACf,qBAAqB,GACrB,wBAAwB,GACxB,eAAe;aACH,WAAW,EAAE,MAAM;aACnB,MAAM,EAAE,MAAM;gBARd,IAAI,EAChB,iBAAiB,GACjB,gBAAgB,GAChB,eAAe,GACf,qBAAqB,GACrB,wBAAwB,GACxB,eAAe,EACH,WAAW,EAAE,MAAM,EACnB,MAAM,GAAE,MAAY;CAKvC;AA4FD,wBAAsB,SAAS,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCrG;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;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,sFAAsF;AACtF,wBAAgB,aAAa;;;;;;;;;;;;;;;;;;;;;;EAW5B;AA0CD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;EAuBpE;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,CAwLtD;AAID,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM,CAYhF;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,CAuIjB;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,CAmIrG;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,gBAAgB,EAAE,MAAM,GAAG,IAAI,EAC/B,KAAK,EAAE,MAAM,GAAG,IAAI,GACnB,OAAO,CAAC,MAAM,CAAC,CAsBjB;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;AA4JD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE;IACP,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;CACjB,GACA,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAoFpF;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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"}
@@ -859,6 +859,84 @@ async function handleRefreshTokenGrant(config, body, dpopHeader, requestUrl) {
859
859
  handle,
860
860
  };
861
861
  }
862
+ // --- Sessions obtained outside the redirect flow ---
863
+ /** A refusal from the endpoint {@link obtainSession} called, or an answer it cannot use. */
864
+ export class ObtainSessionError extends Error {
865
+ error;
866
+ status;
867
+ constructor(
868
+ /** The endpoint's error code, e.g. an XRPC error name. */
869
+ error, message, status) {
870
+ super(message);
871
+ this.error = error;
872
+ this.status = status;
873
+ }
874
+ }
875
+ /**
876
+ * Obtain a session for another account straight from its authorization server,
877
+ * outside the redirect flow: a group host creating an account for this app and
878
+ * handing it a session, say.
879
+ *
880
+ * `body` is POSTed as JSON to `url` as this app's OAuth client, the way the
881
+ * token endpoint is called: `client_id` and, for a confidential client, a client
882
+ * assertion addressed to the endpoint's origin, are added to it, and the request
883
+ * carries a DPoP proof from the app's key, answering a nonce challenge once.
884
+ * `headers` are sent too, for whatever else the endpoint wants to know (service
885
+ * auth from the viewer, typically).
886
+ *
887
+ * The response must hold an OAuth token response, at `session` or as the whole
888
+ * body. That session is stored for its `sub` exactly as a login's is, bound to
889
+ * the same DPoP key, so refresh and the PDS helpers work for that DID from here
890
+ * on. It is only accepted if the account's own authorization server is the one
891
+ * that answered: an endpoint cannot hand over a session for an account it does
892
+ * not serve.
893
+ */
894
+ export async function obtainSession(config, url, body, opts = {}) {
895
+ const origin = new URL(url).origin;
896
+ const payload = {
897
+ ...body,
898
+ ...(await withClientAuth({ client_id: pdsClientId(config.issuer, config) }, config.issuer, config, origin)),
899
+ };
900
+ const send = async (nonce) => fetch(url, {
901
+ method: 'POST',
902
+ headers: {
903
+ ...opts.headers,
904
+ 'Content-Type': 'application/json',
905
+ DPoP: await createDpopProof(serverPrivateJwk, serverPublicJwk, 'POST', url, undefined, nonce),
906
+ },
907
+ body: JSON.stringify(payload),
908
+ });
909
+ let res = await send();
910
+ let out = (await res.json().catch(() => ({})));
911
+ const nonce = res.headers.get('DPoP-Nonce');
912
+ if (!res.ok && out.error === 'use_dpop_nonce' && nonce) {
913
+ res = await send(nonce);
914
+ out = (await res.json().catch(() => ({})));
915
+ }
916
+ if (!res.ok) {
917
+ throw new ObtainSessionError(typeof out.error === 'string' ? out.error : 'UpstreamFailure', String(out.message ?? out.error_description ?? `${url} failed (${res.status})`), res.status);
918
+ }
919
+ const token = (typeof out.session === 'object' && out.session ? out.session : out);
920
+ const did = token.sub;
921
+ if (typeof did !== 'string' || typeof token.access_token !== 'string') {
922
+ throw new ObtainSessionError('NoSession', `${url} returned no session`, 502);
923
+ }
924
+ const { pdsEndpoint, authServerEndpoint, authServerMetadata } = await discoverAuthServer(did, _plcUrl);
925
+ if (new URL(authServerEndpoint).origin !== origin) {
926
+ throw new ObtainSessionError('WrongAuthServer', `${origin} is not the authorization server for ${did}`, 502);
927
+ }
928
+ await storeSession(did, {
929
+ pdsEndpoint,
930
+ pdsAuthServer: authServerEndpoint,
931
+ pdsTokenEndpoint: authServerMetadata.token_endpoint,
932
+ accessToken: token.access_token,
933
+ refreshToken: typeof token.refresh_token === 'string' ? token.refresh_token : undefined,
934
+ dpopJkt: serverJkt,
935
+ tokenExpiresAt: typeof token.expires_in === 'number' ? Math.floor(Date.now() / 1000) + token.expires_in : undefined,
936
+ });
937
+ emit('oauth', 'session_obtained', { did, from: origin });
938
+ return { did, response: out };
939
+ }
862
940
  // --- PDS Session Refresh ---
863
941
  export async function refreshPdsSession(config, session) {
864
942
  if (!session.refresh_token)
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAsEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAuE9C;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CA2M3F;AAgDD,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;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;IACrB,mFAAmF;IACnF,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAC5B;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA0qC5F;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":"AA4EA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAuE9C;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CA2M3F;AAgDD,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;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;IACrB,mFAAmF;IACnF,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAC5B;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAmwC5F;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
@@ -3,7 +3,8 @@ import { existsSync } from 'node:fs';
3
3
  import { readFile } from 'node:fs/promises';
4
4
  import { join, extname } from 'node:path';
5
5
  import { gzipSync } from 'node:zlib';
6
- import { queryRecords, getRecordByUri, searchRecords, getSchema, reshapeRow, setRepoStatus, getRepoStatus, getRepoRetryInfo, queryLabelsForUris, insertLabels, searchAccounts, listReposPaginated, getCollectionCounts, getRepoStatusCounts, getDatabaseSize, deleteLabels, getRecentRecords, listActiveRepoDids, removeRepo, getRepoHandle, getPreferences, putPreference, insertReport, queryReports, resolveReport, getOpenReportCount, } from "./database/db.js";
6
+ import { queryRecords, getRecordByUri, searchRecords, getSchema, reshapeRow, setRepoStatus, getRepoStatus, getRepoRetryInfo, queryLabelsForUris, insertLabels, searchAccounts, listReposPaginated, getCollectionCounts, getRepoStatusCounts, getDatabaseSize, deleteLabels, getRecentRecords, listActiveRepoDids, removeRepo, getRepoHandle, getPreferences, putPreference, insertReport, queryReports, resolveReport, getOpenReportCount, queryClassifications, getClassificationStats, getRecordsByDid, getRecordCountsByDid, findOpenReportsForSubject, } from "./database/db.js";
7
+ import { runScan, getScanProgress, listClassifiers, isSystemOneConfigured } from "./classifiers.js";
7
8
  import { executeFeed, listFeeds } from "./feeds.js";
8
9
  import { executeXrpc, InvalidRequestError, NotFoundError, registerCoreXrpcHandler, isLocalRelay, paramsFromSearch } from "./xrpc.js";
9
10
  import { pdsFor } from "./backfill.js";
@@ -825,6 +826,93 @@ export function createHandler(config) {
825
826
  invalidateAdminStats();
826
827
  return withCors(json({ ok: true }, 200, acceptEncoding));
827
828
  }
829
+ // GET /admin/review — the classifier review queue
830
+ if (url.pathname === '/admin/review' && request.method === 'GET') {
831
+ const denied = requireAdmin(viewer, acceptEncoding);
832
+ if (denied)
833
+ return denied;
834
+ const result = await queryClassifications({
835
+ classifier: url.searchParams.get('classifier') || undefined,
836
+ signal: url.searchParams.get('signal') || undefined,
837
+ minScore: url.searchParams.has('minScore') ? Number(url.searchParams.get('minScore')) : undefined,
838
+ state: url.searchParams.get('state') || 'pending',
839
+ limit: parseInt(url.searchParams.get('limit') || '25'),
840
+ offset: parseInt(url.searchParams.get('offset') || '0'),
841
+ });
842
+ return withCors(json(result, 200, acceptEncoding));
843
+ }
844
+ // GET /admin/review/stats — rollups, classifier names, scan progress
845
+ if (url.pathname === '/admin/review/stats' && request.method === 'GET') {
846
+ const denied = requireAdmin(viewer, acceptEncoding);
847
+ if (denied)
848
+ return denied;
849
+ const stats = await getClassificationStats();
850
+ return withCors(json({ ...stats, classifiers: listClassifiers(), configured: isSystemOneConfigured(), scan: getScanProgress() }, 200, acceptEncoding));
851
+ }
852
+ // POST /admin/review/scan — start a scan. Returns as soon as it is under
853
+ // way; a scan of a whole network outlives any sensible request timeout, so
854
+ // progress is polled from /admin/review/stats rather than awaited here.
855
+ if (url.pathname === '/admin/review/scan' && request.method === 'POST') {
856
+ const denied = requireAdmin(viewer, acceptEncoding);
857
+ if (denied)
858
+ return denied;
859
+ if (!isSystemOneConfigured())
860
+ return withCors(jsonError(400, 'System One is not configured — set `typesafe` in hatk.config.ts', acceptEncoding));
861
+ const body = JSON.parse((await request.text()) || '{}');
862
+ const running = getScanProgress();
863
+ if (running?.running)
864
+ return withCors(jsonError(409, 'A scan is already running', acceptEncoding));
865
+ runScan({ classifier: body.classifier, force: body.force === true, limit: body.limit })
866
+ .then(() => invalidateAdminStats())
867
+ .catch(() => { });
868
+ return withCors(json({ ok: true, started: true }, 202, acceptEncoding));
869
+ }
870
+ // GET /admin/review/subject/:did — everything needed to judge one subject
871
+ if (url.pathname.startsWith('/admin/review/subject/')) {
872
+ const denied = requireAdmin(viewer, acceptEncoding);
873
+ if (denied)
874
+ return denied;
875
+ const did = decodeURIComponent(url.pathname.slice('/admin/review/subject/'.length));
876
+ if (!did.startsWith('did:'))
877
+ return withCors(jsonError(400, 'Expected a DID', acceptEncoding));
878
+ const [records, counts, handle, status, labels] = await Promise.all([
879
+ getRecordsByDid(did, collections, 6),
880
+ getRecordCountsByDid(did, collections),
881
+ getRepoHandle(did),
882
+ getRepoStatus(did),
883
+ queryLabelsForUris([did]),
884
+ ]);
885
+ return withCors(json({ did, handle, status, records, counts, labels }, 200, acceptEncoding));
886
+ }
887
+ // POST /admin/review/act — resolve the report and act, in one step
888
+ if (url.pathname === '/admin/review/act' && request.method === 'POST') {
889
+ const denied = requireAdmin(viewer, acceptEncoding);
890
+ if (denied)
891
+ return denied;
892
+ const { did, uri, action, label } = JSON.parse(await request.text());
893
+ const subjectUri = uri || did;
894
+ if (!subjectUri || !action)
895
+ return withCors(jsonError(400, 'Missing subject or action', acceptEncoding));
896
+ if (!['takedown', 'label', 'dismiss'].includes(action))
897
+ return withCors(jsonError(400, 'Action must be takedown, label or dismiss', acceptEncoding));
898
+ if (action === 'takedown') {
899
+ if (!did)
900
+ return withCors(jsonError(400, 'Takedown needs a did', acceptEncoding));
901
+ await setRepoStatus(did, 'takendown');
902
+ }
903
+ else if (action === 'label') {
904
+ if (!label)
905
+ return withCors(jsonError(400, 'Label action needs a label', acceptEncoding));
906
+ await insertLabels([{ src: 'admin', uri: subjectUri, val: label }]);
907
+ }
908
+ // Close whatever the classifier filed, so the subject leaves the queue
909
+ // whichever way it was decided.
910
+ for (const id of await findOpenReportsForSubject(subjectUri)) {
911
+ await resolveReport(id, action === 'dismiss' ? 'dismissed' : 'resolved', viewer.did);
912
+ }
913
+ invalidateAdminStats();
914
+ return withCors(json({ ok: true }, 200, acceptEncoding));
915
+ }
828
916
  // GET /admin/info/:did — repo status info
829
917
  if (url.pathname.startsWith('/admin/info/did:')) {
830
918
  const denied = requireAdmin(viewer, acceptEncoding);
package/dist/xrpc.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { BaseContext } from './hydrate.ts';
2
2
  import type { Row, FlatRow } from './lex-types.ts';
3
3
  import type { OAuthConfig, CdnConfig } from './config.ts';
4
4
  import type { PdsXrpcOptions } from './pds-proxy.ts';
5
+ import { type ObtainedSession } from './oauth/server.ts';
5
6
  export type { Row, FlatRow };
6
7
  /** Set the OAuth config used for record write helpers. Called once during boot. */
7
8
  export declare function configureOAuth(config: OAuthConfig | null): void;
@@ -81,7 +82,27 @@ export interface XrpcContext<P = Record<string, string>, Records extends Record<
81
82
  * the granted scopes still apply. See {@link pdsXrpc}.
82
83
  */
83
84
  pds: (nsid: string, options?: PdsXrpcOptions) => Promise<Record<string, unknown>>;
85
+ /**
86
+ * The record helpers and `pds` above, acting as another account this app
87
+ * holds a session for — one it obtained with {@link obtainSession}, say.
88
+ * Nothing here checks that the viewer may act for that account: that is the
89
+ * app's decision, made before calling.
90
+ */
91
+ asAccount: (did: string) => AccountHelpers;
92
+ /**
93
+ * Obtain a session for another account straight from its authorization
94
+ * server, outside the redirect flow — a group host creating an account for
95
+ * this app, for instance. POSTs `body` to `url` as this app's OAuth client,
96
+ * with DPoP, and stores the session the response carries for its `sub`, so
97
+ * {@link asAccount} works for it afterwards. See `obtainSession` in
98
+ * `oauth/server.ts`.
99
+ */
100
+ obtainSession: (url: string, body: Record<string, unknown>, opts?: {
101
+ headers?: Record<string, string>;
102
+ }) => Promise<ObtainedSession>;
84
103
  }
104
+ /** The PDS helpers on {@link XrpcContext}, for one account. */
105
+ export type AccountHelpers = Pick<XrpcContext, 'createRecord' | 'putRecord' | 'deleteRecord' | 'applyWrites' | 'pds'>;
85
106
  /** Set the relay URL used for blob URL generation. Called once during boot. */
86
107
  /** True when the relay is a loopback URL — a dev PDS rather than the network. */
87
108
  export declare function isLocalRelay(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"xrpc.d.ts","sourceRoot":"","sources":["../src/xrpc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"xrpc.d.ts","sourceRoot":"","sources":["../src/xrpc.ts"],"names":[],"mappings":"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"}
package/dist/xrpc.js CHANGED
@@ -35,6 +35,7 @@ import { resolveRecords, buildBaseContext } from "./hydrate.js";
35
35
  import { getLexicon } from "./database/schema.js";
36
36
  import { createHmac } from 'node:crypto';
37
37
  import { pdsCreateRecord, pdsPutRecord, pdsDeleteRecord, pdsApplyWrites, pdsXrpc } from "./pds-proxy.js";
38
+ import { obtainSession, ObtainSessionError } from "./oauth/server.js";
38
39
  let _oauthConfig = null;
39
40
  /** Set the OAuth config used for record write helpers. Called once during boot. */
40
41
  export function configureOAuth(config) {
@@ -136,40 +137,54 @@ export function buildXrpcContext(params, cursor, limit, viewer, input) {
136
137
  const uri = await findUriByFields(collection, conditions);
137
138
  return uri !== null;
138
139
  },
139
- createRecord: async (collection, record, opts) => {
140
+ ...accountHelpers(viewer),
141
+ asAccount: (did) => accountHelpers({ did }),
142
+ obtainSession: async (url, body, opts) => {
140
143
  if (!_oauthConfig)
141
- throw new Error('No OAuth config — cannot write to PDS');
142
- if (!viewer)
143
- throw new Error('Authentication required to write records');
144
- return pdsCreateRecord(_oauthConfig, viewer, { collection, record, rkey: opts?.rkey });
144
+ throw new Error('No OAuth config — cannot obtain a session');
145
+ try {
146
+ return await obtainSession(_oauthConfig, url, body, opts);
147
+ }
148
+ catch (err) {
149
+ if (err instanceof ObtainSessionError)
150
+ throw new InvalidRequestError(err.message, err.error);
151
+ throw err;
152
+ }
153
+ },
154
+ };
155
+ }
156
+ /**
157
+ * The PDS helpers for one account, with the session this app holds for it: the
158
+ * viewer's, or another account's through {@link XrpcContext.asAccount}.
159
+ */
160
+ function accountHelpers(account) {
161
+ const ready = (what) => {
162
+ if (!_oauthConfig)
163
+ throw new Error(`No OAuth config — cannot ${what}`);
164
+ if (!account)
165
+ throw new Error(`Authentication required to ${what}`);
166
+ return { config: _oauthConfig, account };
167
+ };
168
+ return {
169
+ createRecord: async (collection, record, opts) => {
170
+ const { config, account } = ready('write records');
171
+ return pdsCreateRecord(config, account, { collection, record, rkey: opts?.rkey });
145
172
  },
146
173
  putRecord: async (collection, rkey, record) => {
147
- if (!_oauthConfig)
148
- throw new Error('No OAuth config — cannot write to PDS');
149
- if (!viewer)
150
- throw new Error('Authentication required to write records');
151
- return pdsPutRecord(_oauthConfig, viewer, { collection, rkey, record });
174
+ const { config, account } = ready('write records');
175
+ return pdsPutRecord(config, account, { collection, rkey, record });
152
176
  },
153
177
  deleteRecord: async (collection, rkey) => {
154
- if (!_oauthConfig)
155
- throw new Error('No OAuth config — cannot write to PDS');
156
- if (!viewer)
157
- throw new Error('Authentication required to write records');
158
- await pdsDeleteRecord(_oauthConfig, viewer, { collection, rkey });
178
+ const { config, account } = ready('write records');
179
+ await pdsDeleteRecord(config, account, { collection, rkey });
159
180
  },
160
181
  applyWrites: async (writes) => {
161
- if (!_oauthConfig)
162
- throw new Error('No OAuth config — cannot write to PDS');
163
- if (!viewer)
164
- throw new Error('Authentication required to write records');
165
- return pdsApplyWrites(_oauthConfig, viewer, { writes });
182
+ const { config, account } = ready('write records');
183
+ return pdsApplyWrites(config, account, { writes });
166
184
  },
167
185
  pds: async (nsid, options) => {
168
- if (!_oauthConfig)
169
- throw new Error('No OAuth config — cannot call the PDS');
170
- if (!viewer)
171
- throw new Error('Authentication required to call the PDS');
172
- return pdsXrpc(_oauthConfig, viewer, nsid, options);
186
+ const { config, account } = ready('call the PDS');
187
+ return pdsXrpc(config, account, nsid, options);
173
188
  },
174
189
  };
175
190
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatk/hatk",
3
- "version": "0.0.1-alpha.95",
3
+ "version": "0.0.1-alpha.96",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "hatk": "dist/cli.js"
@@ -26,7 +26,8 @@
26
26
  "./config": "./dist/config.js",
27
27
  "./vite-plugin": "./dist/vite-plugin.js",
28
28
  "./renderer": "./dist/renderer.js",
29
- "./spaces": "./dist/spaces/index.js"
29
+ "./spaces": "./dist/spaces/index.js",
30
+ "./classifiers": "./dist/classifiers.js"
30
31
  },
31
32
  "scripts": {
32
33
  "clean": "rm -rf dist",