@_linked/server 2.21.0 → 2.22.0

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.
@@ -16,21 +16,6 @@ export const JSON_BODY_LIMIT = '50mb';
16
16
  * the behaviour pinned by `json-body-parser.test.ts`.
17
17
  */
18
18
  export function createJsonBodyParser() {
19
- return express.json({ limit: JSON_BODY_LIMIT, verify: keepRawBody });
20
- }
21
- /** The routes whose handlers get the body as received, on `request.rawBody`. */
22
- const RAW_BODY_PATHS = new Set(['/api/select-raw']);
23
- /**
24
- * Keep the unparsed bytes of a raw SPARQL request on `request.rawBody`, so a
25
- * raw query authorizer (`registerRawQueryAuthorizer` in server-utils) can
26
- * verify a signature over the exact body. Only for those routes: every other
27
- * body is parsed and its bytes dropped as before.
28
- */
29
- function keepRawBody(req, _res, buf) {
30
- var _a, _b;
31
- const path = String((_b = (_a = req.originalUrl) !== null && _a !== void 0 ? _a : req.url) !== null && _b !== void 0 ? _b : '').split('?')[0];
32
- if (req.method === 'POST' && RAW_BODY_PATHS.has(path)) {
33
- req.rawBody = buf;
34
- }
19
+ return express.json({ limit: JSON_BODY_LIMIT });
35
20
  }
36
21
  //# sourceMappingURL=jsonBodyParser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"jsonBodyParser.js","sourceRoot":"","sources":["../../../src/utils/jsonBodyParser.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AAEtC;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,gFAAgF;AAChF,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAEpD;;;;;GAKG;AACH,SAAS,WAAW,CAAC,GAAQ,EAAE,IAAa,EAAE,GAAW;;IACvD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAA,MAAA,GAAG,CAAC,WAAW,mCAAI,GAAG,CAAC,GAAG,mCAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC;IACpB,CAAC;AACH,CAAC","sourcesContent":["import express from 'express';\n\n/**\n * Largest JSON body the server accepts. `Server.call` sends its arguments as\n * one JSON document, and some callers (bulk imports, base64 file payloads)\n * send far more than the 100kb default.\n */\nexport const JSON_BODY_LIMIT = '50mb';\n\n/**\n * The request body parser LinkedServer mounts in front of every route.\n *\n * It is Express's own `express.json()`, i.e. the body-parser 1.x that ships\n * inside Express 4 — the server does not depend on body-parser directly. It\n * parses `application/json` only: multipart uploads (handled with formidable\n * by the upload utilities) and urlencoded forms pass through with their stream\n * unread. Changing parser, or moving to Express 5 (body-parser 2), must keep\n * the behaviour pinned by `json-body-parser.test.ts`.\n */\nexport function createJsonBodyParser() {\n return express.json({ limit: JSON_BODY_LIMIT, verify: keepRawBody });\n}\n\n/** The routes whose handlers get the body as received, on `request.rawBody`. */\nconst RAW_BODY_PATHS = new Set(['/api/select-raw']);\n\n/**\n * Keep the unparsed bytes of a raw SPARQL request on `request.rawBody`, so a\n * raw query authorizer (`registerRawQueryAuthorizer` in server-utils) can\n * verify a signature over the exact body. Only for those routes: every other\n * body is parsed and its bytes dropped as before.\n */\nfunction keepRawBody(req: any, _res: unknown, buf: Buffer) {\n const path = String(req.originalUrl ?? req.url ?? '').split('?')[0];\n if (req.method === 'POST' && RAW_BODY_PATHS.has(path)) {\n req.rawBody = buf;\n }\n}\n"]}
1
+ {"version":3,"file":"jsonBodyParser.js","sourceRoot":"","sources":["../../../src/utils/jsonBodyParser.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AAEtC;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;AAClD,CAAC","sourcesContent":["import express from 'express';\n\n/**\n * Largest JSON body the server accepts. `Server.call` sends its arguments as\n * one JSON document, and some callers (bulk imports, base64 file payloads)\n * send far more than the 100kb default.\n */\nexport const JSON_BODY_LIMIT = '50mb';\n\n/**\n * The request body parser LinkedServer mounts in front of every route.\n *\n * It is Express's own `express.json()`, i.e. the body-parser 1.x that ships\n * inside Express 4 — the server does not depend on body-parser directly. It\n * parses `application/json` only: multipart uploads (handled with formidable\n * by the upload utilities) and urlencoded forms pass through with their stream\n * unread. Changing parser, or moving to Express 5 (body-parser 2), must keep\n * the behaviour pinned by `json-body-parser.test.ts`.\n */\nexport function createJsonBodyParser() {\n return express.json({ limit: JSON_BODY_LIMIT });\n}\n"]}
@@ -54,5 +54,5 @@ export declare function setProtectedNodeProbe(fn: ProtectedNodeProbe | undefined
54
54
  * async function with one would run it.
55
55
  */
56
56
  export declare function authorizeGenericQuery(operation: QueryOperation, query: unknown, builder: any, endpoint: string): Promise<void>;
57
- /** Check a raw SPARQL query; see `registerRawQueryAuthorizer`. */
57
+ /** Check a raw SPARQL query against the `rawQueries` setting (see utils/rawQueries). */
58
58
  export declare function authorizeRawQuery(query: unknown, endpoint: string): Promise<void>;
@@ -15,6 +15,7 @@ import { LinkedStorage } from '@_linked/core/utils/LinkedStorage';
15
15
  import { getShapeClass } from '@_linked/core/utils/ShapeClass';
16
16
  import { checkQueryAccess, } from '@_linked/server-utils/utils/QueryAccess';
17
17
  import { ServerCallError } from '@_linked/server-utils/utils/ServerCallError';
18
+ import { getRawQueriesMode } from './rawQueries.js';
18
19
  import { getRpcExposureMode } from './rpcExposure.js';
19
20
  /** The DSL-JSON tag of a query-context reference (`CONTEXT_REF_KEY` in core). */
20
21
  const CONTEXT_REF_KEY = '@ctx';
@@ -189,7 +190,7 @@ export async function authorizeGenericQuery(operation, query, builder, endpoint)
189
190
  probeProtectedNodes: probe,
190
191
  });
191
192
  }
192
- /** Check a raw SPARQL query; see `registerRawQueryAuthorizer`. */
193
+ /** Check a raw SPARQL query against the `rawQueries` setting (see utils/rawQueries). */
193
194
  export async function authorizeRawQuery(query, endpoint) {
194
195
  if (typeof query !== 'string') {
195
196
  throw new ServerCallError(400, 'Invalid query');
@@ -199,6 +200,7 @@ export async function authorizeRawQuery(query, endpoint) {
199
200
  query,
200
201
  ir: undefined,
201
202
  raw: true,
203
+ rawQueries: getRawQueriesMode(),
202
204
  mode: getRpcExposureMode(),
203
205
  endpoint,
204
206
  });
@@ -1 +1 @@
1
- {"version":3,"file":"queryPlane.js","sourceRoot":"","sources":["../../../src/utils/queryPlane.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Authorization for the generic query plane: the endpoints that run a query the\n * client built (`LincdServerBackendProvider.*Query` behind\n * `/call/@_linked/server/*Query`, and `LincdAPI` `post_*` behind `/api/*`).\n *\n * The rules themselves (protected shapes, authorizers) are registered through\n * `@_linked/server-utils/utils/QueryAccess`; see `checkQueryAccess` there for the\n * order they apply in. This module turns the incoming query into the builder the\n * store runs and the IR the rules read, and answers the rules' protected-node\n * lookup with a SPARQL `ASK` against the store the mutation routes to.\n */\nimport { fromJSON } from '@_linked/core';\nimport { lower } from '@_linked/core/queries/lower';\nimport { LinkedStorage } from '@_linked/core/utils/LinkedStorage';\nimport { getShapeClass } from '@_linked/core/utils/ShapeClass';\nimport {\n checkQueryAccess,\n type ProtectedNodeCheck,\n type ProtectedNodeProbe,\n type QueryOperation,\n} from '@_linked/server-utils/utils/QueryAccess';\nimport { ServerCallError } from '@_linked/server-utils/utils/ServerCallError';\nimport { getRpcExposureMode } from './rpcExposure.js';\n\n/** The DSL-JSON tag of a query-context reference (`CONTEXT_REF_KEY` in core). */\nconst CONTEXT_REF_KEY = '@ctx';\n\n/**\n * Whether DSL-JSON carries a query-context reference (`{\"@ctx\": name}`) in any\n * position: a select or count subject, an update target, a delete id, a\n * where-clause operand or a mutation field value. Every DSL-JSON decoder in\n * core reads a context reference from this one key.\n */\nexport function containsContextRef(json: unknown): boolean {\n const seen = new Set<object>();\n const stack: unknown[] = [json];\n while (stack.length) {\n const value = stack.pop();\n if (!value || typeof value !== 'object' || seen.has(value)) continue;\n seen.add(value);\n if (Array.isArray(value)) {\n for (const item of value) stack.push(item);\n continue;\n }\n if (Object.prototype.hasOwnProperty.call(value, CONTEXT_REF_KEY)) return true;\n for (const key of Object.keys(value)) stack.push((value as any)[key]);\n }\n return false;\n}\n\n/**\n * A rehydrated query builder from the DSL-JSON a client sent. The result is\n * what the store runs, so the access check and the store see the same query.\n * A body that is not a query answers 400.\n *\n * A query-context reference (`{\"@ctx\": \"user\"}`) also answers 400. Core\n * resolves one at lowering time against a process-wide context map, which on a\n * server holds whatever some code last set there, not the caller's identity,\n * and lowering runs twice (once for the access check, once in the store), so\n * the two could even resolve it differently. A query built from client JSON\n * names its nodes by id.\n *\n * Without context references, lowering a rehydrated builder is a pure function\n * of the builder (closed and never mutated after `fromJSON`) and the shape\n * registry: the IR `authorizeGenericQuery` checks is the IR the store lowers\n * from the same builder.\n */\nexport function toQueryBuilder(query: any): any {\n // A live builder passed by backend code (never a plain object from JSON).\n if (\n query &&\n typeof query === 'object' &&\n Object.getPrototypeOf(query) !== Object.prototype &&\n typeof query.__queryKind === 'string'\n ) {\n return query;\n }\n if (containsContextRef(query)) {\n throw new ServerCallError(\n 400,\n 'Query context references (@ctx) are not accepted by the generic query endpoints'\n );\n }\n try {\n const builder = fromJSON(query);\n if (!builder || typeof builder !== 'object') throw new Error('not a query');\n return builder;\n } catch {\n throw new ServerCallError(400, 'Invalid query');\n }\n}\n\n/** The lowered IR of a query, or `undefined` when it cannot be lowered (refused). */\nexport function lowerForAccessCheck(builder: any): unknown {\n try {\n return lower(builder);\n } catch {\n return undefined;\n }\n}\n\nconst RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';\nconst RDFS_SUBCLASS_OF = 'http://www.w3.org/2000/01/rdf-schema#subClassOf';\n\n/** `<iri>`, refusing anything that is not a plain absolute IRI. */\nfunction iriTerm(value: unknown): string {\n if (\n typeof value !== 'string' ||\n !/^[A-Za-z][A-Za-z0-9+.-]*:[^\\s<>\"{}|^`\\\\\\u0000-\\u001f\\u007f]*$/.test(value)\n ) {\n throw new ServerCallError(400, 'Invalid node id');\n }\n return `<${value}>`;\n}\n\n/**\n * The `ASK` that answers a protected-node check: is any node the mutation would\n * write or delete typed with a protected class, or a subclass of one?\n * `undefined` when there is nothing to ask.\n */\nexport function protectedNodeAsk(check: ProtectedNodeCheck): string | undefined {\n if (!check.classes.length) return undefined;\n const branches: string[] = [];\n const owned = check.containsPredicates.length\n ? '(' + check.containsPredicates.map(iriTerm).join('|') + ')+'\n : undefined;\n if (check.ids.length) {\n branches.push(`{ VALUES ?n { ${check.ids.map(iriTerm).join(' ')} } }`);\n }\n if (owned && check.ownerIds.length) {\n branches.push(\n `{ VALUES ?owner { ${check.ownerIds.map(iriTerm).join(' ')} } ?owner ${owned} ?n . }`\n );\n }\n for (const cls of check.scanClasses) {\n branches.push(`{ ?n <${RDF_TYPE}> ${iriTerm(cls)} . }`);\n if (owned) branches.push(`{ ?s <${RDF_TYPE}> ${iriTerm(cls)} . ?s ${owned} ?n . }`);\n }\n if (!branches.length) return undefined;\n return (\n `ASK WHERE {\\n ${branches.join('\\n UNION ')}\\n` +\n ` ?n <${RDF_TYPE}> ?t .\\n` +\n ` VALUES ?c { ${check.classes.map(iriTerm).join(' ')} }\\n` +\n ` ?t <${RDFS_SUBCLASS_OF}>* ?c .\\n}`\n );\n}\n\n/**\n * The default protected-node lookup: one `ASK` on the dataset the mutation's\n * shape routes to (run in the current call's context, so a routing dataset\n * resolves it exactly as it will resolve the mutation). A dataset that cannot\n * run SPARQL (`rawQuery`) cannot be checked, and the mutation is refused.\n */\nexport const askProtectedNodes: ProtectedNodeProbe = async (check) => {\n const sparql = protectedNodeAsk(check);\n if (!sparql) return false;\n let shapeClass: any;\n try {\n shapeClass = getShapeClass(check.shape as any);\n } catch {\n shapeClass = undefined;\n }\n const dataset: any =\n LinkedStorage.getDatasetForShapeClass(shapeClass) ?? LinkedStorage.getDefaultDataset();\n if (!dataset || typeof dataset.rawQuery !== 'function') {\n throw new Error(\n `the dataset for ${check.shape} cannot run SPARQL, so the nodes this mutation writes cannot be checked`\n );\n }\n const json = await dataset.rawQuery(sparql);\n if (typeof json?.boolean !== 'boolean') {\n throw new Error('the dataset did not answer the ASK with a boolean');\n }\n return json.boolean;\n};\n\nlet probe: ProtectedNodeProbe = askProtectedNodes;\n\n/**\n * Replace the protected-node lookup (see `checkQueryAccess`): an app whose\n * protected nodes live in a known store can answer it there. Pass `undefined`\n * to restore the default `ASK`.\n */\nexport function setProtectedNodeProbe(fn: ProtectedNodeProbe | undefined): void {\n if (fn !== undefined && typeof fn !== 'function') {\n throw new TypeError('setProtectedNodeProbe: expected a function');\n }\n probe = fn ?? askProtectedNodes;\n}\n\n/**\n * Check `builder` (rehydrated from `query` with `toQueryBuilder`) against the\n * generic-plane rules. Throws a `ServerCallError` (400/401/403) when the current\n * call may not run it; the caller then hands the same builder to the store.\n *\n * Deliberately returns nothing: a query builder is a thenable, so resolving an\n * async function with one would run it.\n */\nexport async function authorizeGenericQuery(\n operation: QueryOperation,\n query: unknown,\n builder: any,\n endpoint: string\n): Promise<void> {\n await checkQueryAccess({\n operation,\n query,\n ir: lowerForAccessCheck(builder),\n mode: getRpcExposureMode(),\n endpoint,\n probeProtectedNodes: probe,\n });\n}\n\n/** Check a raw SPARQL query; see `registerRawQueryAuthorizer`. */\nexport async function authorizeRawQuery(query: unknown, endpoint: string): Promise<void> {\n if (typeof query !== 'string') {\n throw new ServerCallError(400, 'Invalid query');\n }\n await checkQueryAccess({\n operation: 'select',\n query,\n ir: undefined,\n raw: true,\n mode: getRpcExposureMode(),\n endpoint,\n });\n}\n"]}
1
+ {"version":3,"file":"queryPlane.js","sourceRoot":"","sources":["../../../src/utils/queryPlane.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Authorization for the generic query plane: the endpoints that run a query the\n * client built (`LincdServerBackendProvider.*Query` behind\n * `/call/@_linked/server/*Query`, and `LincdAPI` `post_*` behind `/api/*`).\n *\n * The rules themselves (protected shapes, authorizers) are registered through\n * `@_linked/server-utils/utils/QueryAccess`; see `checkQueryAccess` there for the\n * order they apply in. This module turns the incoming query into the builder the\n * store runs and the IR the rules read, and answers the rules' protected-node\n * lookup with a SPARQL `ASK` against the store the mutation routes to.\n */\nimport { fromJSON } from '@_linked/core';\nimport { lower } from '@_linked/core/queries/lower';\nimport { LinkedStorage } from '@_linked/core/utils/LinkedStorage';\nimport { getShapeClass } from '@_linked/core/utils/ShapeClass';\nimport {\n checkQueryAccess,\n type ProtectedNodeCheck,\n type ProtectedNodeProbe,\n type QueryOperation,\n} from '@_linked/server-utils/utils/QueryAccess';\nimport { ServerCallError } from '@_linked/server-utils/utils/ServerCallError';\nimport { getRawQueriesMode } from './rawQueries.js';\nimport { getRpcExposureMode } from './rpcExposure.js';\n\n/** The DSL-JSON tag of a query-context reference (`CONTEXT_REF_KEY` in core). */\nconst CONTEXT_REF_KEY = '@ctx';\n\n/**\n * Whether DSL-JSON carries a query-context reference (`{\"@ctx\": name}`) in any\n * position: a select or count subject, an update target, a delete id, a\n * where-clause operand or a mutation field value. Every DSL-JSON decoder in\n * core reads a context reference from this one key.\n */\nexport function containsContextRef(json: unknown): boolean {\n const seen = new Set<object>();\n const stack: unknown[] = [json];\n while (stack.length) {\n const value = stack.pop();\n if (!value || typeof value !== 'object' || seen.has(value)) continue;\n seen.add(value);\n if (Array.isArray(value)) {\n for (const item of value) stack.push(item);\n continue;\n }\n if (Object.prototype.hasOwnProperty.call(value, CONTEXT_REF_KEY)) return true;\n for (const key of Object.keys(value)) stack.push((value as any)[key]);\n }\n return false;\n}\n\n/**\n * A rehydrated query builder from the DSL-JSON a client sent. The result is\n * what the store runs, so the access check and the store see the same query.\n * A body that is not a query answers 400.\n *\n * A query-context reference (`{\"@ctx\": \"user\"}`) also answers 400. Core\n * resolves one at lowering time against a process-wide context map, which on a\n * server holds whatever some code last set there, not the caller's identity,\n * and lowering runs twice (once for the access check, once in the store), so\n * the two could even resolve it differently. A query built from client JSON\n * names its nodes by id.\n *\n * Without context references, lowering a rehydrated builder is a pure function\n * of the builder (closed and never mutated after `fromJSON`) and the shape\n * registry: the IR `authorizeGenericQuery` checks is the IR the store lowers\n * from the same builder.\n */\nexport function toQueryBuilder(query: any): any {\n // A live builder passed by backend code (never a plain object from JSON).\n if (\n query &&\n typeof query === 'object' &&\n Object.getPrototypeOf(query) !== Object.prototype &&\n typeof query.__queryKind === 'string'\n ) {\n return query;\n }\n if (containsContextRef(query)) {\n throw new ServerCallError(\n 400,\n 'Query context references (@ctx) are not accepted by the generic query endpoints'\n );\n }\n try {\n const builder = fromJSON(query);\n if (!builder || typeof builder !== 'object') throw new Error('not a query');\n return builder;\n } catch {\n throw new ServerCallError(400, 'Invalid query');\n }\n}\n\n/** The lowered IR of a query, or `undefined` when it cannot be lowered (refused). */\nexport function lowerForAccessCheck(builder: any): unknown {\n try {\n return lower(builder);\n } catch {\n return undefined;\n }\n}\n\nconst RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';\nconst RDFS_SUBCLASS_OF = 'http://www.w3.org/2000/01/rdf-schema#subClassOf';\n\n/** `<iri>`, refusing anything that is not a plain absolute IRI. */\nfunction iriTerm(value: unknown): string {\n if (\n typeof value !== 'string' ||\n !/^[A-Za-z][A-Za-z0-9+.-]*:[^\\s<>\"{}|^`\\\\\\u0000-\\u001f\\u007f]*$/.test(value)\n ) {\n throw new ServerCallError(400, 'Invalid node id');\n }\n return `<${value}>`;\n}\n\n/**\n * The `ASK` that answers a protected-node check: is any node the mutation would\n * write or delete typed with a protected class, or a subclass of one?\n * `undefined` when there is nothing to ask.\n */\nexport function protectedNodeAsk(check: ProtectedNodeCheck): string | undefined {\n if (!check.classes.length) return undefined;\n const branches: string[] = [];\n const owned = check.containsPredicates.length\n ? '(' + check.containsPredicates.map(iriTerm).join('|') + ')+'\n : undefined;\n if (check.ids.length) {\n branches.push(`{ VALUES ?n { ${check.ids.map(iriTerm).join(' ')} } }`);\n }\n if (owned && check.ownerIds.length) {\n branches.push(\n `{ VALUES ?owner { ${check.ownerIds.map(iriTerm).join(' ')} } ?owner ${owned} ?n . }`\n );\n }\n for (const cls of check.scanClasses) {\n branches.push(`{ ?n <${RDF_TYPE}> ${iriTerm(cls)} . }`);\n if (owned) branches.push(`{ ?s <${RDF_TYPE}> ${iriTerm(cls)} . ?s ${owned} ?n . }`);\n }\n if (!branches.length) return undefined;\n return (\n `ASK WHERE {\\n ${branches.join('\\n UNION ')}\\n` +\n ` ?n <${RDF_TYPE}> ?t .\\n` +\n ` VALUES ?c { ${check.classes.map(iriTerm).join(' ')} }\\n` +\n ` ?t <${RDFS_SUBCLASS_OF}>* ?c .\\n}`\n );\n}\n\n/**\n * The default protected-node lookup: one `ASK` on the dataset the mutation's\n * shape routes to (run in the current call's context, so a routing dataset\n * resolves it exactly as it will resolve the mutation). A dataset that cannot\n * run SPARQL (`rawQuery`) cannot be checked, and the mutation is refused.\n */\nexport const askProtectedNodes: ProtectedNodeProbe = async (check) => {\n const sparql = protectedNodeAsk(check);\n if (!sparql) return false;\n let shapeClass: any;\n try {\n shapeClass = getShapeClass(check.shape as any);\n } catch {\n shapeClass = undefined;\n }\n const dataset: any =\n LinkedStorage.getDatasetForShapeClass(shapeClass) ?? LinkedStorage.getDefaultDataset();\n if (!dataset || typeof dataset.rawQuery !== 'function') {\n throw new Error(\n `the dataset for ${check.shape} cannot run SPARQL, so the nodes this mutation writes cannot be checked`\n );\n }\n const json = await dataset.rawQuery(sparql);\n if (typeof json?.boolean !== 'boolean') {\n throw new Error('the dataset did not answer the ASK with a boolean');\n }\n return json.boolean;\n};\n\nlet probe: ProtectedNodeProbe = askProtectedNodes;\n\n/**\n * Replace the protected-node lookup (see `checkQueryAccess`): an app whose\n * protected nodes live in a known store can answer it there. Pass `undefined`\n * to restore the default `ASK`.\n */\nexport function setProtectedNodeProbe(fn: ProtectedNodeProbe | undefined): void {\n if (fn !== undefined && typeof fn !== 'function') {\n throw new TypeError('setProtectedNodeProbe: expected a function');\n }\n probe = fn ?? askProtectedNodes;\n}\n\n/**\n * Check `builder` (rehydrated from `query` with `toQueryBuilder`) against the\n * generic-plane rules. Throws a `ServerCallError` (400/401/403) when the current\n * call may not run it; the caller then hands the same builder to the store.\n *\n * Deliberately returns nothing: a query builder is a thenable, so resolving an\n * async function with one would run it.\n */\nexport async function authorizeGenericQuery(\n operation: QueryOperation,\n query: unknown,\n builder: any,\n endpoint: string\n): Promise<void> {\n await checkQueryAccess({\n operation,\n query,\n ir: lowerForAccessCheck(builder),\n mode: getRpcExposureMode(),\n endpoint,\n probeProtectedNodes: probe,\n });\n}\n\n/** Check a raw SPARQL query against the `rawQueries` setting (see utils/rawQueries). */\nexport async function authorizeRawQuery(query: unknown, endpoint: string): Promise<void> {\n if (typeof query !== 'string') {\n throw new ServerCallError(400, 'Invalid query');\n }\n await checkQueryAccess({\n operation: 'select',\n query,\n ir: undefined,\n raw: true,\n rawQueries: getRawQueriesMode(),\n mode: getRpcExposureMode(),\n endpoint,\n });\n}\n"]}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Who may run raw SPARQL through the generic query plane (`/api/select-raw`).
3
+ *
4
+ * Raw SPARQL cannot be analysed for the shapes it touches, so the protected
5
+ * shapes and query authorizers of `@_linked/server-utils/utils/QueryAccess`
6
+ * do not apply to it. This one setting decides instead:
7
+ *
8
+ * - `'session'` (the default): any signed-in session may run it; 401 without one.
9
+ * - `'off'`: every raw query is refused (403).
10
+ *
11
+ * Set it with `server.rawQueries` in the app's config, or `LINKED_RAW_QUERIES`.
12
+ */
13
+ import type { RawQueriesMode } from '@_linked/server-utils/utils/QueryAccess';
14
+ export type { RawQueriesMode };
15
+ /**
16
+ * Set from the server's config (`server.rawQueries`). An invalid value turns
17
+ * raw queries off, with a warning: a typo must not open them up.
18
+ */
19
+ export declare function setRawQueriesMode(mode: unknown): void;
20
+ /**
21
+ * `server.rawQueries`, else `LINKED_RAW_QUERIES`, else `'session'`. An invalid
22
+ * `LINKED_RAW_QUERIES` turns raw queries off.
23
+ */
24
+ export declare function getRawQueriesMode(): RawQueriesMode;
@@ -0,0 +1,42 @@
1
+ let configuredMode;
2
+ let invalidEnvWarned = false;
3
+ function parseMode(value) {
4
+ return value === 'off' || value === 'session' ? value : undefined;
5
+ }
6
+ /**
7
+ * Set from the server's config (`server.rawQueries`). An invalid value turns
8
+ * raw queries off, with a warning: a typo must not open them up.
9
+ */
10
+ export function setRawQueriesMode(mode) {
11
+ if (mode === undefined || mode === null) {
12
+ configuredMode = undefined;
13
+ return;
14
+ }
15
+ const parsed = parseMode(mode);
16
+ if (!parsed) {
17
+ console.warn(`[linked] server.rawQueries must be 'off' or 'session', got ${JSON.stringify(mode)}; raw queries are off`);
18
+ configuredMode = 'off';
19
+ return;
20
+ }
21
+ configuredMode = parsed;
22
+ }
23
+ /**
24
+ * `server.rawQueries`, else `LINKED_RAW_QUERIES`, else `'session'`. An invalid
25
+ * `LINKED_RAW_QUERIES` turns raw queries off.
26
+ */
27
+ export function getRawQueriesMode() {
28
+ if (configuredMode)
29
+ return configuredMode;
30
+ const env = process.env.LINKED_RAW_QUERIES;
31
+ if (env === undefined || env === '')
32
+ return 'session';
33
+ const parsed = parseMode(env);
34
+ if (parsed)
35
+ return parsed;
36
+ if (!invalidEnvWarned) {
37
+ invalidEnvWarned = true;
38
+ console.warn(`[linked] LINKED_RAW_QUERIES must be 'off' or 'session', got ${JSON.stringify(env)}; raw queries are off`);
39
+ }
40
+ return 'off';
41
+ }
42
+ //# sourceMappingURL=rawQueries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rawQueries.js","sourceRoot":"","sources":["../../../src/utils/rawQueries.ts"],"names":[],"mappings":"AAgBA,IAAI,cAA0C,CAAC;AAC/C,IAAI,gBAAgB,GAAG,KAAK,CAAC;AAE7B,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAa;IAC7C,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QACxC,cAAc,GAAG,SAAS,CAAC;QAC3B,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CACV,8DAA8D,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,uBAAuB,CAC1G,CAAC;QACF,cAAc,GAAG,KAAK,CAAC;QACvB,OAAO;IACT,CAAC;IACD,cAAc,GAAG,MAAM,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,IAAI,cAAc;QAAE,OAAO,cAAc,CAAC;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC3C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IACtD,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,gBAAgB,GAAG,IAAI,CAAC;QACxB,OAAO,CAAC,IAAI,CACV,+DAA+D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,uBAAuB,CAC1G,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["/**\n * Who may run raw SPARQL through the generic query plane (`/api/select-raw`).\n *\n * Raw SPARQL cannot be analysed for the shapes it touches, so the protected\n * shapes and query authorizers of `@_linked/server-utils/utils/QueryAccess`\n * do not apply to it. This one setting decides instead:\n *\n * - `'session'` (the default): any signed-in session may run it; 401 without one.\n * - `'off'`: every raw query is refused (403).\n *\n * Set it with `server.rawQueries` in the app's config, or `LINKED_RAW_QUERIES`.\n */\nimport type { RawQueriesMode } from '@_linked/server-utils/utils/QueryAccess';\n\nexport type { RawQueriesMode };\n\nlet configuredMode: RawQueriesMode | undefined;\nlet invalidEnvWarned = false;\n\nfunction parseMode(value: unknown): RawQueriesMode | undefined {\n return value === 'off' || value === 'session' ? value : undefined;\n}\n\n/**\n * Set from the server's config (`server.rawQueries`). An invalid value turns\n * raw queries off, with a warning: a typo must not open them up.\n */\nexport function setRawQueriesMode(mode: unknown): void {\n if (mode === undefined || mode === null) {\n configuredMode = undefined;\n return;\n }\n const parsed = parseMode(mode);\n if (!parsed) {\n console.warn(\n `[linked] server.rawQueries must be 'off' or 'session', got ${JSON.stringify(mode)}; raw queries are off`\n );\n configuredMode = 'off';\n return;\n }\n configuredMode = parsed;\n}\n\n/**\n * `server.rawQueries`, else `LINKED_RAW_QUERIES`, else `'session'`. An invalid\n * `LINKED_RAW_QUERIES` turns raw queries off.\n */\nexport function getRawQueriesMode(): RawQueriesMode {\n if (configuredMode) return configuredMode;\n const env = process.env.LINKED_RAW_QUERIES;\n if (env === undefined || env === '') return 'session';\n const parsed = parseMode(env);\n if (parsed) return parsed;\n if (!invalidEnvWarned) {\n invalidEnvWarned = true;\n console.warn(\n `[linked] LINKED_RAW_QUERIES must be 'off' or 'session', got ${JSON.stringify(env)}; raw queries are off`\n );\n }\n return 'off';\n}\n"]}
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/linked-fw/server.git"
12
12
  },
13
- "version": "2.21.0",
13
+ "version": "2.22.0",
14
14
  "packageManager": "npm@11.20.0",
15
15
  "linkedPackage": true,
16
16
  "type": "module",
@@ -37,7 +37,7 @@
37
37
  "dependencies": {
38
38
  "@_linked/cli": "^1.2",
39
39
  "@_linked/core": "^2.22.8",
40
- "@_linked/server-utils": "^1.9.0",
40
+ "@_linked/server-utils": "^1.10.0",
41
41
  "autoprefixer": "^10.4.18",
42
42
  "compression": "^1.7.4",
43
43
  "cors": "^2.8.5",
@@ -97,6 +97,7 @@
97
97
  "./shapes/LinkedServer",
98
98
  "./shapes/filestores/*",
99
99
  "./utils/queryPlane",
100
+ "./utils/rawQueries",
100
101
  "./utils/releaseManifest",
101
102
  "./utils/routeAssets",
102
103
  "./utils/rpcExposure"