toolcraft 0.0.50 → 0.0.52
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/node_modules/@poe-code/agent-defs/README.md +35 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.js +9 -2
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +3 -1
- package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.js +44 -2
- package/node_modules/@poe-code/agent-human-in-loop/package.json +0 -1
- package/node_modules/@poe-code/agent-mcp-config/README.md +54 -0
- package/node_modules/@poe-code/agent-mcp-config/package.json +0 -2
- package/node_modules/@poe-code/config-mutations/README.md +55 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.d.ts +1 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +65 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.js +4 -11
- package/node_modules/@poe-code/config-mutations/package.json +0 -1
- package/node_modules/@poe-code/frontmatter/dist/fences.d.ts +17 -0
- package/node_modules/@poe-code/frontmatter/dist/fences.js +33 -5
- package/node_modules/@poe-code/frontmatter/dist/parse.js +86 -14
- package/node_modules/@poe-code/frontmatter/dist/stringify.js +13 -0
- package/node_modules/@poe-code/process-runner/dist/docker/args.js +14 -1
- package/node_modules/@poe-code/process-runner/dist/docker/build-context.d.ts +5 -0
- package/node_modules/@poe-code/process-runner/dist/docker/build-context.js +37 -0
- package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +29 -29
- package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
- package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
- package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +49 -3
- package/node_modules/@poe-code/process-runner/package.json +8 -1
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +7 -0
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +34 -7
- package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +75 -19
- package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/backends/utils.js +23 -2
- package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +16 -12
- package/node_modules/@poe-code/task-list/dist/state-machine.js +9 -0
- package/node_modules/@poe-code/task-list/package.json +0 -1
- package/node_modules/auth-store/dist/create-secret-store.js +4 -3
- package/node_modules/auth-store/dist/encrypted-file-store.js +49 -2
- package/node_modules/auth-store/dist/keychain-store.js +11 -4
- package/node_modules/auth-store/package.json +0 -1
- package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +69 -12
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +100 -68
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +19 -18
- package/node_modules/mcp-oauth/dist/client/token-endpoint.js +37 -31
- package/node_modules/mcp-oauth/package.json +0 -1
- package/node_modules/tiny-mcp-client/dist/internal.js +96 -10
- package/node_modules/tiny-mcp-client/package.json +0 -3
- package/node_modules/tiny-mcp-client/src/internal.ts +120 -18
- package/node_modules/tiny-mcp-client/src/transports.test.ts +231 -2
- package/node_modules/toolcraft-design/package.json +0 -1
- package/package.json +3 -2
|
@@ -36,8 +36,20 @@ export function sortStrings(values) {
|
|
|
36
36
|
export function sortTasks(tasks) {
|
|
37
37
|
return [...tasks].sort((left, right) => left.qualifiedId.localeCompare(right.qualifiedId));
|
|
38
38
|
}
|
|
39
|
+
export function isTrimmedPrintableIdentifier(value) {
|
|
40
|
+
if (value.length === 0 || value !== value.trim()) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
44
|
+
const code = value.charCodeAt(index);
|
|
45
|
+
if (code < 32 || code === 127) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
39
51
|
export function validateTaskId(id) {
|
|
40
|
-
if (id
|
|
52
|
+
if (!isTrimmedPrintableIdentifier(id) ||
|
|
41
53
|
id.startsWith(".") ||
|
|
42
54
|
id.includes("/") ||
|
|
43
55
|
id.includes("\\") ||
|
|
@@ -46,6 +58,12 @@ export function validateTaskId(id) {
|
|
|
46
58
|
}
|
|
47
59
|
return id;
|
|
48
60
|
}
|
|
61
|
+
export function validateTaskName(name) {
|
|
62
|
+
if (name.trim().length === 0) {
|
|
63
|
+
throw new Error("Task name must not be empty.");
|
|
64
|
+
}
|
|
65
|
+
return name;
|
|
66
|
+
}
|
|
49
67
|
export async function statIfExists(fs, filePath) {
|
|
50
68
|
try {
|
|
51
69
|
return await fs.stat(filePath);
|
|
@@ -66,6 +84,9 @@ export async function rejectSymbolicLinkComponents(fs, filePath) {
|
|
|
66
84
|
currentPath = path.join(currentPath, component);
|
|
67
85
|
try {
|
|
68
86
|
if ((await fs.lstat(currentPath)).isSymbolicLink()) {
|
|
87
|
+
if (currentPath === "/tmp") {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
69
90
|
throw new Error(`Path "${filePath}" contains a symbolic link.`);
|
|
70
91
|
}
|
|
71
92
|
}
|
|
@@ -138,7 +159,7 @@ async function removeAbandonedLock(fs, lockPath) {
|
|
|
138
159
|
throw error;
|
|
139
160
|
}
|
|
140
161
|
const owner = Number(content);
|
|
141
|
-
if (
|
|
162
|
+
if (Number.isInteger(owner) && owner > 0 && isProcessRunning(owner)) {
|
|
142
163
|
return false;
|
|
143
164
|
}
|
|
144
165
|
try {
|
|
@@ -5,7 +5,7 @@ import taskSchema from "../schema/task.schema.json" with { type: "json" };
|
|
|
5
5
|
import { eventsFromState, findEvent } from "../state-machine.js";
|
|
6
6
|
import { resolveStateMachine } from "../state.js";
|
|
7
7
|
import { AnchorNotFoundError, InvalidTransitionError, MalformedTaskError, OrderMismatchError, TaskAlreadyExistsError, TaskNotFoundError } from "../types.js";
|
|
8
|
-
import { applyOrder, isRecord, rejectSymbolicLinkComponents, sortStrings, statIfExists, validateTaskId, withFileLock, writeAtomically } from "./utils.js";
|
|
8
|
+
import { applyOrder, isTrimmedPrintableIdentifier, isRecord, rejectSymbolicLinkComponents, sortStrings, statIfExists, validateTaskId, validateTaskName, withFileLock, writeAtomically } from "./utils.js";
|
|
9
9
|
const STORE_KIND = "task-store";
|
|
10
10
|
const STORE_SCHEMA_ID = storeSchema.$id;
|
|
11
11
|
const STORE_VERSION = 1;
|
|
@@ -28,7 +28,7 @@ function malformedTask(list, id, field) {
|
|
|
28
28
|
return new MalformedTaskError(`Malformed task "${list}/${id}": invalid "${field}".`);
|
|
29
29
|
}
|
|
30
30
|
function validateListName(name) {
|
|
31
|
-
if (name
|
|
31
|
+
if (!isTrimmedPrintableIdentifier(name) ||
|
|
32
32
|
name.startsWith(".") ||
|
|
33
33
|
name.includes("/") ||
|
|
34
34
|
name.includes("\\") ||
|
|
@@ -171,9 +171,7 @@ function assertValidStoreRecord(store, filePath) {
|
|
|
171
171
|
throw malformedStore(filePath, "kind");
|
|
172
172
|
}
|
|
173
173
|
const version = getOwnEntry(store, "version");
|
|
174
|
-
if (typeof version !== "number" ||
|
|
175
|
-
!Number.isInteger(version) ||
|
|
176
|
-
version !== STORE_VERSION) {
|
|
174
|
+
if (typeof version !== "number" || !Number.isInteger(version) || version !== STORE_VERSION) {
|
|
177
175
|
throw malformedStore(filePath, "version");
|
|
178
176
|
}
|
|
179
177
|
if (!isRecord(getOwnEntry(store, "lists"))) {
|
|
@@ -184,7 +182,8 @@ function assertValidTaskRecord(taskRecord, list, id, validStates) {
|
|
|
184
182
|
if (!isRecord(taskRecord)) {
|
|
185
183
|
throw malformedTask(list, id, "task");
|
|
186
184
|
}
|
|
187
|
-
if (hasOwnTaskField(taskRecord, "$schema") &&
|
|
185
|
+
if (hasOwnTaskField(taskRecord, "$schema") &&
|
|
186
|
+
getOwnEntry(taskRecord, "$schema") !== TASK_SCHEMA_ID) {
|
|
188
187
|
throw malformedTask(list, id, "$schema");
|
|
189
188
|
}
|
|
190
189
|
if (hasOwnTaskField(taskRecord, "kind") && getOwnEntry(taskRecord, "kind") !== TASK_KIND) {
|
|
@@ -192,16 +191,12 @@ function assertValidTaskRecord(taskRecord, list, id, validStates) {
|
|
|
192
191
|
}
|
|
193
192
|
if (hasOwnTaskField(taskRecord, "version")) {
|
|
194
193
|
const version = getOwnEntry(taskRecord, "version");
|
|
195
|
-
if (typeof version !== "number" ||
|
|
196
|
-
!Number.isInteger(version) ||
|
|
197
|
-
version !== TASK_VERSION) {
|
|
194
|
+
if (typeof version !== "number" || !Number.isInteger(version) || version !== TASK_VERSION) {
|
|
198
195
|
throw malformedTask(list, id, "version");
|
|
199
196
|
}
|
|
200
197
|
}
|
|
201
198
|
const name = getOwnEntry(taskRecord, "name");
|
|
202
|
-
if (!hasOwnTaskField(taskRecord, "name") ||
|
|
203
|
-
typeof name !== "string" ||
|
|
204
|
-
name.length === 0) {
|
|
199
|
+
if (!hasOwnTaskField(taskRecord, "name") || typeof name !== "string" || name.length === 0) {
|
|
205
200
|
throw malformedTask(list, id, "name");
|
|
206
201
|
}
|
|
207
202
|
const state = getOwnEntry(taskRecord, "state");
|
|
@@ -396,6 +391,7 @@ function createTasksView(deps, list) {
|
|
|
396
391
|
assertCreateDoesNotSetState(input);
|
|
397
392
|
assertCreateHasId(input);
|
|
398
393
|
validateTaskId(input.id);
|
|
394
|
+
validateTaskName(input.name);
|
|
399
395
|
const { document, store } = await readStore(deps.fs, deps.path, validStates);
|
|
400
396
|
if (getTaskRecord(store, list, input.id)) {
|
|
401
397
|
throw new TaskAlreadyExistsError(`Task "${list}/${input.id}" already exists.`);
|
|
@@ -408,6 +404,9 @@ function createTasksView(deps, list) {
|
|
|
408
404
|
async update(id, patch) {
|
|
409
405
|
assertUpdateDoesNotSetState(patch);
|
|
410
406
|
validateTaskId(id);
|
|
407
|
+
if (patch.name !== undefined) {
|
|
408
|
+
validateTaskName(patch.name);
|
|
409
|
+
}
|
|
411
410
|
const { document, store } = await readStore(deps.fs, deps.path, validStates);
|
|
412
411
|
const existing = getTaskOrThrow(store, list, id);
|
|
413
412
|
const nextTaskRecord = buildUpdatedTaskRecord(existing, patch);
|
|
@@ -500,6 +499,11 @@ function createTasksView(deps, list) {
|
|
|
500
499
|
}
|
|
501
500
|
else {
|
|
502
501
|
const anchorId = "before" in anchor ? anchor.before : anchor.after;
|
|
502
|
+
const activeIds = new Set(activeItemIds(listNode, validStates));
|
|
503
|
+
if (!activeIds.has(anchorId)) {
|
|
504
|
+
listNode.items.splice(fromIndex, 0, movedPair);
|
|
505
|
+
throw new AnchorNotFoundError(anchorId);
|
|
506
|
+
}
|
|
503
507
|
const anchorIndex = findItemIndex(listNode, anchorId);
|
|
504
508
|
if (anchorIndex < 0) {
|
|
505
509
|
listNode.items.splice(fromIndex, 0, movedPair);
|
|
@@ -4,6 +4,9 @@ function isRecord(value) {
|
|
|
4
4
|
function isStateList(value) {
|
|
5
5
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
6
6
|
}
|
|
7
|
+
function hasVisibleName(value) {
|
|
8
|
+
return value.trim().length > 0;
|
|
9
|
+
}
|
|
7
10
|
function canFireFromState(event, fromState) {
|
|
8
11
|
if (event.from === "*") {
|
|
9
12
|
return event.to !== fromState;
|
|
@@ -18,6 +21,9 @@ export function validateMachine(machine) {
|
|
|
18
21
|
throw new TypeError("State machine states must be a string array.");
|
|
19
22
|
}
|
|
20
23
|
const states = new Set(machine.states);
|
|
24
|
+
if (machine.states.some((state) => !hasVisibleName(state))) {
|
|
25
|
+
throw new Error("State names must not be empty.");
|
|
26
|
+
}
|
|
21
27
|
if (!hasOwnRecordField(machine, "initial") || typeof machine.initial !== "string") {
|
|
22
28
|
throw new TypeError("State machine initial must be a string.");
|
|
23
29
|
}
|
|
@@ -28,6 +34,9 @@ export function validateMachine(machine) {
|
|
|
28
34
|
throw new TypeError("State machine events must be an object.");
|
|
29
35
|
}
|
|
30
36
|
for (const [eventName, event] of Object.entries(machine.events)) {
|
|
37
|
+
if (!hasVisibleName(eventName)) {
|
|
38
|
+
throw new Error("Event names must not be empty.");
|
|
39
|
+
}
|
|
31
40
|
if (!isRecord(event)) {
|
|
32
41
|
throw new TypeError(`Event "${eventName}" must be an object.`);
|
|
33
42
|
}
|
|
@@ -28,13 +28,14 @@ export function createSecretStore(input) {
|
|
|
28
28
|
function resolveBackend(input) {
|
|
29
29
|
const envVar = input.backendEnvVar ?? DEFAULT_BACKEND_ENV_VAR;
|
|
30
30
|
const configuredBackend = input.backend ?? getOwnEnvValue(input.env, envVar) ?? getOwnEnvValue(process.env, envVar);
|
|
31
|
-
|
|
31
|
+
const backend = configuredBackend?.trim();
|
|
32
|
+
if (backend === "keychain") {
|
|
32
33
|
return "keychain";
|
|
33
34
|
}
|
|
34
|
-
if (
|
|
35
|
+
if (backend === undefined || backend === "file") {
|
|
35
36
|
return "file";
|
|
36
37
|
}
|
|
37
|
-
throw new Error(`Unsupported auth store backend: ${
|
|
38
|
+
throw new Error(`Unsupported auth store backend: ${backend}`);
|
|
38
39
|
}
|
|
39
40
|
function getOwnEnvValue(env, key) {
|
|
40
41
|
return env !== undefined && Object.prototype.hasOwnProperty.call(env, key)
|
|
@@ -24,7 +24,10 @@ export class EncryptedFileStore {
|
|
|
24
24
|
if (input.filePath === undefined) {
|
|
25
25
|
const homeDirectory = (input.getHomeDirectory ?? homedir)();
|
|
26
26
|
const defaultDirectory = input.defaultDirectory ?? ".auth-store";
|
|
27
|
-
|
|
27
|
+
const defaultFileName = input.defaultFileName ?? "credentials.enc";
|
|
28
|
+
assertSafeDefaultDirectory(defaultDirectory);
|
|
29
|
+
assertSafeDefaultFileName(defaultFileName);
|
|
30
|
+
this.filePath = path.join(homeDirectory, defaultDirectory, defaultFileName);
|
|
28
31
|
this.symbolicLinkCheckStartPath = resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory);
|
|
29
32
|
}
|
|
30
33
|
else {
|
|
@@ -154,7 +157,7 @@ function resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory) {
|
|
|
154
157
|
}
|
|
155
158
|
function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
|
|
156
159
|
if (symbolicLinkCheckStartPath === null) {
|
|
157
|
-
return
|
|
160
|
+
return getExplicitProtectedCredentialPaths(resolvedPath);
|
|
158
161
|
}
|
|
159
162
|
const resolvedStartPath = path.resolve(symbolicLinkCheckStartPath);
|
|
160
163
|
if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
|
|
@@ -168,6 +171,50 @@ function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
|
|
|
168
171
|
}
|
|
169
172
|
return protectedPaths;
|
|
170
173
|
}
|
|
174
|
+
function assertSafeDefaultDirectory(defaultDirectory) {
|
|
175
|
+
if (path.isAbsolute(defaultDirectory) || path.win32.isAbsolute(defaultDirectory)) {
|
|
176
|
+
throw new Error("defaultDirectory must be a relative path inside the home directory");
|
|
177
|
+
}
|
|
178
|
+
for (const segment of splitPathSegments(defaultDirectory)) {
|
|
179
|
+
if (segment === "..") {
|
|
180
|
+
throw new Error("defaultDirectory must be a relative path inside the home directory");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function assertSafeDefaultFileName(defaultFileName) {
|
|
185
|
+
if (defaultFileName.trim().length === 0 ||
|
|
186
|
+
defaultFileName === "." ||
|
|
187
|
+
defaultFileName === ".." ||
|
|
188
|
+
splitPathSegments(defaultFileName).length !== 1) {
|
|
189
|
+
throw new Error("defaultFileName must be a file name without path separators");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function splitPathSegments(value) {
|
|
193
|
+
return value
|
|
194
|
+
.split("/")
|
|
195
|
+
.flatMap((segment) => segment.split("\\"))
|
|
196
|
+
.filter((segment) => segment.length > 0);
|
|
197
|
+
}
|
|
198
|
+
function getExplicitProtectedCredentialPaths(resolvedPath) {
|
|
199
|
+
const parsed = path.parse(resolvedPath);
|
|
200
|
+
const segments = resolvedPath
|
|
201
|
+
.slice(parsed.root.length)
|
|
202
|
+
.split(path.sep)
|
|
203
|
+
.filter((segment) => segment.length > 0);
|
|
204
|
+
if (segments.length <= 1) {
|
|
205
|
+
return [resolvedPath];
|
|
206
|
+
}
|
|
207
|
+
const protectedPaths = [];
|
|
208
|
+
let currentPath = parsed.root;
|
|
209
|
+
for (const [index, segment] of segments.entries()) {
|
|
210
|
+
currentPath = path.join(currentPath, segment);
|
|
211
|
+
if (index === 0) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
protectedPaths.push(currentPath);
|
|
215
|
+
}
|
|
216
|
+
return protectedPaths;
|
|
217
|
+
}
|
|
171
218
|
function isPathInsideOrEqual(childPath, parentPath) {
|
|
172
219
|
const relativePath = path.relative(parentPath, childPath);
|
|
173
220
|
return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
|
|
@@ -7,8 +7,14 @@ export class KeychainStore {
|
|
|
7
7
|
account;
|
|
8
8
|
constructor(input) {
|
|
9
9
|
this.runCommand = input.runCommand ?? runSecurityCommand;
|
|
10
|
-
this.service = input.service;
|
|
11
|
-
this.account = input.account;
|
|
10
|
+
this.service = input.service.trim();
|
|
11
|
+
this.account = input.account.trim();
|
|
12
|
+
if (this.service.length === 0) {
|
|
13
|
+
throw new Error("Keychain service must not be empty");
|
|
14
|
+
}
|
|
15
|
+
if (this.account.length === 0) {
|
|
16
|
+
throw new Error("Keychain account must not be empty");
|
|
17
|
+
}
|
|
12
18
|
}
|
|
13
19
|
async get() {
|
|
14
20
|
const result = await this.executeSecurityCommand(["find-generic-password", "-s", this.service, "-a", this.account, "-w"], "read secret from macOS Keychain");
|
|
@@ -31,8 +37,9 @@ export class KeychainStore {
|
|
|
31
37
|
"-a",
|
|
32
38
|
this.account,
|
|
33
39
|
"-U",
|
|
34
|
-
"-w"
|
|
35
|
-
|
|
40
|
+
"-w",
|
|
41
|
+
value
|
|
42
|
+
], "store secret in macOS Keychain");
|
|
36
43
|
if (getCommandExitCode(result) !== 0) {
|
|
37
44
|
throw createSecurityCliFailure("store secret in macOS Keychain", result);
|
|
38
45
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { createSecretStore
|
|
3
|
+
import { createSecretStore } from "auth-store";
|
|
4
4
|
import { canonicalizeResourceIndicator } from "../resource-indicator.js";
|
|
5
5
|
const DEFAULT_FILE_SALT = "poe-code:mcp-oauth:v1";
|
|
6
6
|
const DEFAULT_FILE_DIRECTORY = ".poe-code/mcp-oauth";
|
|
@@ -8,6 +8,7 @@ const DEFAULT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth";
|
|
|
8
8
|
const DEFAULT_CLIENT_FILE_SALT = "poe-code:mcp-oauth:clients:v1";
|
|
9
9
|
const DEFAULT_CLIENT_FILE_DIRECTORY = ".poe-code/mcp-oauth/clients";
|
|
10
10
|
const DEFAULT_CLIENT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth-clients";
|
|
11
|
+
const MAX_JS_DATE_MS = 8_640_000_000_000_000;
|
|
11
12
|
export function createAuthStoreSessionStore(options = {}) {
|
|
12
13
|
return {
|
|
13
14
|
async load(resource) {
|
|
@@ -17,10 +18,10 @@ export function createAuthStoreSessionStore(options = {}) {
|
|
|
17
18
|
return null;
|
|
18
19
|
}
|
|
19
20
|
const parsed = JSON.parse(value);
|
|
20
|
-
if (
|
|
21
|
+
if (isStoredOAuthSession(parsed)) {
|
|
21
22
|
return parsed;
|
|
22
23
|
}
|
|
23
|
-
throw new Error("Stored OAuth session must
|
|
24
|
+
throw new Error("Stored OAuth session must match the expected shape");
|
|
24
25
|
},
|
|
25
26
|
async save(resource, session) {
|
|
26
27
|
const store = createResourceSecretStore(resource, options);
|
|
@@ -29,7 +30,7 @@ export function createAuthStoreSessionStore(options = {}) {
|
|
|
29
30
|
async clear(resource) {
|
|
30
31
|
const store = createResourceSecretStore(resource, options);
|
|
31
32
|
await store.delete();
|
|
32
|
-
}
|
|
33
|
+
}
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
export function createAuthStoreClientStore(options) {
|
|
@@ -44,7 +45,8 @@ export function createAuthStoreClientStore(options) {
|
|
|
44
45
|
const clientId = isObjectRecord(parsed) ? getOwnString(parsed, "clientId") : undefined;
|
|
45
46
|
if (clientId !== undefined) {
|
|
46
47
|
const client = { clientId };
|
|
47
|
-
if (isObjectRecord(parsed) &&
|
|
48
|
+
if (isObjectRecord(parsed) &&
|
|
49
|
+
Object.prototype.hasOwnProperty.call(parsed, "clientSecret")) {
|
|
48
50
|
client.clientSecret = getOwnEntry(parsed, "clientSecret");
|
|
49
51
|
}
|
|
50
52
|
return client;
|
|
@@ -58,7 +60,7 @@ export function createAuthStoreClientStore(options) {
|
|
|
58
60
|
async clear(issuer) {
|
|
59
61
|
const store = createIssuerSecretStore(issuer, options);
|
|
60
62
|
await store.delete();
|
|
61
|
-
}
|
|
63
|
+
}
|
|
62
64
|
};
|
|
63
65
|
}
|
|
64
66
|
function createNamedSecretStore(key, options, defaults) {
|
|
@@ -71,16 +73,15 @@ function createNamedSecretStore(key, options, defaults) {
|
|
|
71
73
|
? undefined
|
|
72
74
|
: path.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
|
|
73
75
|
salt: options.fileStore?.salt ?? defaults.salt,
|
|
74
|
-
defaultDirectory: options.fileStore?.defaultDirectory ||
|
|
75
|
-
defaults.directory,
|
|
76
|
+
defaultDirectory: options.fileStore?.defaultDirectory || defaults.directory,
|
|
76
77
|
defaultFileName: parsedFilePath === null
|
|
77
78
|
? `${hash}.enc`
|
|
78
|
-
: `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}
|
|
79
|
+
: `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`
|
|
79
80
|
};
|
|
80
81
|
const keychainStore = {
|
|
81
82
|
...options.keychainStore,
|
|
82
83
|
service: options.keychainStore?.service ?? defaults.service,
|
|
83
|
-
account: `${options.keychainStore?.account ?? defaults.accountPrefix}:${hash}
|
|
84
|
+
account: `${options.keychainStore?.account ?? defaults.accountPrefix}:${hash}`
|
|
84
85
|
};
|
|
85
86
|
return createSecretStore({ ...options, fileStore, keychainStore }).store;
|
|
86
87
|
}
|
|
@@ -89,7 +90,7 @@ function createResourceSecretStore(resource, options) {
|
|
|
89
90
|
salt: DEFAULT_FILE_SALT,
|
|
90
91
|
directory: DEFAULT_FILE_DIRECTORY,
|
|
91
92
|
service: DEFAULT_KEYCHAIN_SERVICE,
|
|
92
|
-
accountPrefix: "provider"
|
|
93
|
+
accountPrefix: "provider"
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
96
|
function createIssuerSecretStore(issuer, options) {
|
|
@@ -97,7 +98,7 @@ function createIssuerSecretStore(issuer, options) {
|
|
|
97
98
|
salt: DEFAULT_CLIENT_FILE_SALT,
|
|
98
99
|
directory: DEFAULT_CLIENT_FILE_DIRECTORY,
|
|
99
100
|
service: DEFAULT_CLIENT_KEYCHAIN_SERVICE,
|
|
100
|
-
accountPrefix: "issuer"
|
|
101
|
+
accountPrefix: "issuer"
|
|
101
102
|
});
|
|
102
103
|
}
|
|
103
104
|
function isObjectRecord(value) {
|
|
@@ -110,3 +111,59 @@ function getOwnString(record, key) {
|
|
|
110
111
|
const value = getOwnEntry(record, key);
|
|
111
112
|
return typeof value === "string" ? value : undefined;
|
|
112
113
|
}
|
|
114
|
+
function isStoredOAuthSession(value) {
|
|
115
|
+
if (!isObjectRecord(value)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return (isNonBlankOwnString(value, "resource") &&
|
|
119
|
+
isNonBlankOwnString(value, "authorizationServer") &&
|
|
120
|
+
isStoredOAuthClient(getOwnEntry(value, "client")) &&
|
|
121
|
+
isStoredOAuthDiscovery(getOwnEntry(value, "discovery")) &&
|
|
122
|
+
isStoredOAuthTokensOrMissing(getOwnEntry(value, "tokens")));
|
|
123
|
+
}
|
|
124
|
+
function isStoredOAuthClient(value) {
|
|
125
|
+
if (!isObjectRecord(value) || !isNonBlankOwnString(value, "clientId")) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
const clientSecret = getOwnEntry(value, "clientSecret");
|
|
129
|
+
return (clientSecret === undefined ||
|
|
130
|
+
(typeof clientSecret === "string" && clientSecret.trim().length > 0));
|
|
131
|
+
}
|
|
132
|
+
function isStoredOAuthDiscovery(value) {
|
|
133
|
+
if (!isObjectRecord(value)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return (isNonBlankOwnString(value, "resourceMetadataUrl") &&
|
|
137
|
+
isObjectRecord(getOwnEntry(value, "resourceMetadata")) &&
|
|
138
|
+
isObjectRecord(getOwnEntry(value, "authorizationServerMetadata")));
|
|
139
|
+
}
|
|
140
|
+
function isStoredOAuthTokensOrMissing(value) {
|
|
141
|
+
if (value === undefined) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
if (!isObjectRecord(value)) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
if (!isNonBlankOwnString(value, "accessToken") || getOwnString(value, "tokenType") !== "Bearer") {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
const expiresAt = getOwnEntry(value, "expiresAt");
|
|
151
|
+
if (expiresAt !== null &&
|
|
152
|
+
(typeof expiresAt !== "number" ||
|
|
153
|
+
!Number.isSafeInteger(expiresAt) ||
|
|
154
|
+
expiresAt > MAX_JS_DATE_MS ||
|
|
155
|
+
!Number.isFinite(new Date(expiresAt).getTime()))) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const refreshToken = getOwnEntry(value, "refreshToken");
|
|
159
|
+
if (refreshToken !== undefined &&
|
|
160
|
+
(typeof refreshToken !== "string" || refreshToken.trim().length === 0)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const scope = getOwnEntry(value, "scope");
|
|
164
|
+
return scope === undefined || (typeof scope === "string" && scope.trim().length > 0);
|
|
165
|
+
}
|
|
166
|
+
function isNonBlankOwnString(record, key) {
|
|
167
|
+
const value = getOwnString(record, key);
|
|
168
|
+
return value !== undefined && value.trim().length > 0;
|
|
169
|
+
}
|