@anchrd/intel-api 0.6.5 → 0.6.7

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.
@@ -128,6 +128,19 @@ export function createFlowRepository(deps) {
128
128
  ORDER BY lower(flow.title), flow.id${bounded ? " LIMIT ?" : ""}`;
129
129
  return {
130
130
  async listVisible(actor, input = {}) {
131
+ // ⚠️ `archivedOnly` overrides the folder rather than narrowing within it: somebody looking
132
+ // for what they archived does not know where it was filed (#113). The visibility predicate
133
+ // is unchanged, so the archive shows no flow the tree would have hidden.
134
+ if (input.archivedOnly === true) {
135
+ const result = await deps.db
136
+ .prepare(`${subtreeCte}
137
+ SELECT ${flowColumns} FROM flows flow
138
+ WHERE ${flowInSubtree} AND flow.archived_at IS NOT NULL
139
+ ORDER BY flow.archived_at DESC, lower(flow.title), flow.id`)
140
+ .bind(...readableBindings(actor), ...flowInSubtreeBindings(actor))
141
+ .all();
142
+ return (result.results ?? []).map(mapFlow);
143
+ }
131
144
  const scope = scopeOf(input.parentId);
132
145
  const result = await deps.db
133
146
  .prepare(visibleFlows(scope.clause, false, input.includeArchived === true))
@@ -102,11 +102,27 @@ export function createKnowledgeRepository(deps) {
102
102
  JOIN allowed ON allowed.id = n.id
103
103
  WHERE n.parent_id IS ? AND (? = 1 OR n.archived_at IS NULL)
104
104
  ORDER BY CASE n.kind WHEN 'folder' THEN 0 ELSE 1 END, lower(n.title), n.id${bounded ? " LIMIT ?" : ""}`;
105
+ /**
106
+ * Everything archived that this actor may see, wherever it sits (#113).
107
+ *
108
+ * ⚠️ No `parent_id` at all, and that is the point rather than an omission: somebody looking for
109
+ * what they archived does not know which folder it was in. The visibility predicate is the same
110
+ * `allowed` set every other read uses, so the archive shows no more than the tree would have.
111
+ *
112
+ * `has_children` is answered as 0 without asking: an archived row is not expandable — its
113
+ * children are reached by restoring it, not by opening it here.
114
+ */
115
+ const archivedEverywhere = `${visibleCte}
116
+ SELECT ${nodeColumns}, 0 AS has_children
117
+ FROM knowledge_nodes n
118
+ JOIN allowed ON allowed.id = n.id
119
+ WHERE n.archived_at IS NOT NULL
120
+ ORDER BY n.archived_at DESC, lower(n.title), n.id`;
105
121
  return {
106
122
  async listVisible(actor, input) {
107
123
  const result = await deps.db
108
- .prepare(visibleChildren(false))
109
- .bind(...readBindings(actor), input.parentId, input.includeArchived ? 1 : 0)
124
+ .prepare(input.archivedOnly ? archivedEverywhere : visibleChildren(false))
125
+ .bind(...readBindings(actor), ...(input.archivedOnly ? [] : [input.parentId, input.includeArchived ? 1 : 0]))
110
126
  .all();
111
127
  const rows = result.results ?? [];
112
128
  return {
@@ -136,7 +136,12 @@ export function createOpenId(deps) {
136
136
  client_name: input.clientName,
137
137
  redirect_uris: [input.redirectUri],
138
138
  response_types: ["code"],
139
- grant_types: ["authorization_code"],
139
+ // ⚠️ `refresh_token` belongs here, not only in the scope. RFC 7591 §2: `grant_types`
140
+ // declares which grants this client may USE, and a client registered without it may
141
+ // not refresh — whatever scope it was granted. That is why the portal connection did
142
+ // not survive its first access token (#97): `offline_access` was being requested and
143
+ // the right to act on it never was.
144
+ grant_types: ["authorization_code", "refresh_token"],
140
145
  ...(isCloudflareAccess(input.issuer)
141
146
  ? {}
142
147
  : { scope: "openid profile email offline_access" }),
package/dist/http/http.js CHANGED
@@ -101,8 +101,9 @@ export function createHttp(deps) {
101
101
  const url = new URL(context.req.url);
102
102
  const unknownQuery = [];
103
103
  url.searchParams.forEach((_value, key) => {
104
- if (key !== "parentId" && key !== "includeArchived")
104
+ if (key !== "parentId" && key !== "includeArchived" && key !== "archivedOnly") {
105
105
  unknownQuery.push(key);
106
+ }
106
107
  });
107
108
  if (unknownQuery.length) {
108
109
  throw new IntelError(400, "invalid_request", `Unknown query parameters: ${unknownQuery.join(", ")}`);
@@ -110,6 +111,7 @@ export function createHttp(deps) {
110
111
  const input = ListKnowledgeNodesInput.parse({
111
112
  parentId: url.searchParams.get("parentId"),
112
113
  includeArchived: url.searchParams.get("includeArchived") === "true",
114
+ ...(url.searchParams.get("archivedOnly") === "true" ? { archivedOnly: true } : {}),
113
115
  });
114
116
  return context.json(await deps.knowledge.list(asActor(auth), input));
115
117
  });
@@ -250,7 +252,7 @@ export function createHttp(deps) {
250
252
  app.get("/flows", async (context) => {
251
253
  const auth = requireCapability(context, "flows", "read");
252
254
  const url = new URL(context.req.url);
253
- requireKnownQuery(url, ["parentId", "includeArchived"]);
255
+ requireKnownQuery(url, ["parentId", "includeArchived", "archivedOnly"]);
254
256
  // Absent means "every flow I may see"; present but empty means the root of the shared tree.
255
257
  // Without that distinction a tree level and a full list would be the same request.
256
258
  const parentId = url.searchParams.get("parentId");
@@ -259,6 +261,7 @@ export function createHttp(deps) {
259
261
  const input = ListFlowsInput.parse({
260
262
  ...(url.searchParams.has("parentId") ? { parentId: parentId === "" ? null : parentId } : {}),
261
263
  ...(url.searchParams.get("includeArchived") === "true" ? { includeArchived: true } : {}),
264
+ ...(url.searchParams.get("archivedOnly") === "true" ? { archivedOnly: true } : {}),
262
265
  });
263
266
  return context.json(await deps.flows.list(asFlowActor(auth), input));
264
267
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anchrd/intel-api",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {