@cortexkit/common-auth 0.9.1 → 0.9.3
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/claustrum/enrollment.js +6 -2
- package/dist/dump/index.d.ts +2 -0
- package/dist/dump/index.js +11 -9
- package/dist/fs/atomic-write.d.ts +2 -0
- package/dist/fs/atomic-write.js +17 -11
- package/dist/fs/refresh-file-lock.js +13 -3
- package/dist/rpc/client.d.ts +3 -0
- package/dist/rpc/client.js +2 -0
- package/dist/rpc/port-file.d.ts +2 -0
- package/dist/rpc/port-file.js +18 -12
- package/package.json +5 -1
|
@@ -271,18 +271,22 @@ async function writeStateAtomic(path, state) {
|
|
|
271
271
|
}
|
|
272
272
|
const temporary = join(parent, `.${basename(path)}.${process.pid}.${randomUUID()}.tmp`);
|
|
273
273
|
let descriptor;
|
|
274
|
+
let created = false;
|
|
274
275
|
try {
|
|
275
276
|
descriptor = await open(temporary, 'wx', 0o600);
|
|
277
|
+
created = true;
|
|
276
278
|
await descriptor.writeFile(bytes, 'utf8');
|
|
279
|
+
await descriptor.chmod(0o600);
|
|
277
280
|
await descriptor.sync();
|
|
278
281
|
await descriptor.close();
|
|
279
282
|
descriptor = undefined;
|
|
280
|
-
await chmod(temporary, 0o600);
|
|
281
283
|
await rename(temporary, path);
|
|
284
|
+
created = false;
|
|
282
285
|
}
|
|
283
286
|
finally {
|
|
284
287
|
await descriptor?.close().catch(() => { });
|
|
285
|
-
|
|
288
|
+
if (created)
|
|
289
|
+
await unlink(temporary).catch(() => { });
|
|
286
290
|
}
|
|
287
291
|
}
|
|
288
292
|
export async function readClaustrumEnrollmentStatus(paths, proposedName) {
|
package/dist/dump/index.d.ts
CHANGED
|
@@ -59,6 +59,8 @@ export interface DumpOptions {
|
|
|
59
59
|
* files that were written stay.
|
|
60
60
|
*/
|
|
61
61
|
cleanupFailedDumps?: boolean;
|
|
62
|
+
/** Test seam for forcing response staging-name collisions. */
|
|
63
|
+
stageName?: () => string;
|
|
62
64
|
}
|
|
63
65
|
export interface DumpInput {
|
|
64
66
|
/** The session the request belongs to; missing means `session-unknown`. */
|
package/dist/dump/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
-
import { chmod, lstat, mkdir, readdir, readFile, rename, unlink, writeFile, } from 'node:fs/promises';
|
|
2
|
+
import { chmod, lstat, mkdir, open, readdir, readFile, rename, unlink, writeFile, } from 'node:fs/promises';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { createRedactor } from '../logger/redact.js';
|
|
5
5
|
const PREVIOUS_BODY_LIMIT = 100;
|
|
@@ -132,21 +132,23 @@ export async function sweepDumpDirectory(options) {
|
|
|
132
132
|
* reader never sees half a file and a symlink planted at a predictable
|
|
133
133
|
* staging name is never followed.
|
|
134
134
|
*/
|
|
135
|
-
async function replaceFile(path, text) {
|
|
135
|
+
async function replaceFile(path, text, stageName) {
|
|
136
136
|
for (let attempt = 0;; attempt += 1) {
|
|
137
|
-
const staging = `${path}.${randomBytes(12).toString('hex')}.partial`;
|
|
137
|
+
const staging = `${path}.${stageName ? stageName() : randomBytes(12).toString('hex')}.partial`;
|
|
138
138
|
let created = false;
|
|
139
|
+
let handle;
|
|
139
140
|
try {
|
|
140
|
-
await
|
|
141
|
-
encoding: 'utf8',
|
|
142
|
-
mode: 0o600,
|
|
143
|
-
flag: 'wx',
|
|
144
|
-
});
|
|
141
|
+
handle = await open(staging, 'wx', 0o600);
|
|
145
142
|
created = true;
|
|
143
|
+
await handle.writeFile(text, 'utf8');
|
|
144
|
+
await handle.chmod(0o600);
|
|
145
|
+
await handle.close();
|
|
146
|
+
handle = undefined;
|
|
146
147
|
await rename(staging, path);
|
|
147
148
|
return;
|
|
148
149
|
}
|
|
149
150
|
catch (error) {
|
|
151
|
+
await handle?.close().catch(() => { });
|
|
150
152
|
if (created)
|
|
151
153
|
await unlink(staging).catch(() => { });
|
|
152
154
|
const code = error.code;
|
|
@@ -500,7 +502,7 @@ export function createDumper(options) {
|
|
|
500
502
|
complete: input.complete ?? true,
|
|
501
503
|
});
|
|
502
504
|
try {
|
|
503
|
-
await replaceFile(result.responseFile, `${JSON.stringify(artifact, null, 2)}\n
|
|
505
|
+
await replaceFile(result.responseFile, `${JSON.stringify(artifact, null, 2)}\n`, options.stageName);
|
|
504
506
|
return result.responseFile;
|
|
505
507
|
}
|
|
506
508
|
catch (error) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export interface AtomicWriteOptions {
|
|
2
2
|
serialize?: (value: unknown) => string;
|
|
3
3
|
beforeRename?: () => Promise<void>;
|
|
4
|
+
/** Test seam for forcing staging-name collisions; defaults to randomUUID. */
|
|
5
|
+
stageName?: () => string;
|
|
4
6
|
}
|
|
5
7
|
export declare function writeJsonAtomic(path: string, value: unknown, options?: AtomicWriteOptions): Promise<void>;
|
package/dist/fs/atomic-write.js
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { mkdir, rename, rm
|
|
2
|
+
import { mkdir, open, rename, rm } from 'node:fs/promises';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
4
|
export async function writeJsonAtomic(path, value, options = {}) {
|
|
5
5
|
await mkdir(dirname(path), { recursive: true });
|
|
6
|
-
const tempPath = `${path}.${randomUUID()}.tmp`;
|
|
6
|
+
const tempPath = `${path}.${(options.stageName ?? randomUUID)()}.tmp`;
|
|
7
|
+
let handle;
|
|
8
|
+
let created = false;
|
|
7
9
|
try {
|
|
8
|
-
await
|
|
10
|
+
handle = await open(tempPath, 'wx', 0o600);
|
|
11
|
+
created = true;
|
|
12
|
+
await handle.writeFile(options.serialize
|
|
9
13
|
? options.serialize(value)
|
|
10
|
-
: `${JSON.stringify(value, null, 2)}\n`,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
: `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
15
|
+
await handle.chmod(0o600);
|
|
16
|
+
await handle.close();
|
|
17
|
+
handle = undefined;
|
|
14
18
|
await options.beforeRename?.();
|
|
15
19
|
await rename(tempPath, path);
|
|
20
|
+
created = false;
|
|
16
21
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
finally {
|
|
23
|
+
await handle?.close().catch(() => { });
|
|
24
|
+
// A failed write can leave partial bytes, but a collision is not ours to remove.
|
|
25
|
+
if (created)
|
|
26
|
+
await rm(tempPath, { force: true }).catch(() => { });
|
|
21
27
|
}
|
|
22
28
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { mkdir, open, readFile, rename, rm, stat, writeFile, } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { LockOwnershipError, lockPathFor } from './with-lock.js';
|
|
5
5
|
const setRefreshLockRenewalTimeout = globalThis.setTimeout.bind(globalThis);
|
|
@@ -67,12 +67,22 @@ export async function acquireRefreshFileLock(options) {
|
|
|
67
67
|
async function writeOwner() {
|
|
68
68
|
// Readers must see a complete lease, even while renewal is writing.
|
|
69
69
|
const tempPath = `${lockPath}.${randomUUID()}.tmp`;
|
|
70
|
+
let handle;
|
|
71
|
+
let created = false;
|
|
70
72
|
try {
|
|
71
|
-
await
|
|
73
|
+
handle = await open(tempPath, 'wx', 0o600);
|
|
74
|
+
created = true;
|
|
75
|
+
await handle.writeFile(`${JSON.stringify({ ownerId, expiresAt: now() + options.ttlMs })}\n`, 'utf8');
|
|
76
|
+
await handle.chmod(0o600);
|
|
77
|
+
await handle.close();
|
|
78
|
+
handle = undefined;
|
|
72
79
|
await rename(tempPath, lockPath);
|
|
80
|
+
created = false;
|
|
73
81
|
}
|
|
74
82
|
finally {
|
|
75
|
-
await
|
|
83
|
+
await handle?.close().catch(() => { });
|
|
84
|
+
if (created)
|
|
85
|
+
await rm(tempPath, { force: true }).catch(() => { });
|
|
76
86
|
}
|
|
77
87
|
}
|
|
78
88
|
async function tryAcquire() {
|
package/dist/rpc/port-file.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export declare function writePortFile(dir: string, entry: {
|
|
|
15
15
|
}, options?: {
|
|
16
16
|
secureDir?: boolean;
|
|
17
17
|
beforeWrite?: () => void | Promise<void>;
|
|
18
|
+
/** Test seam for forcing staging-name collisions; defaults to randomUUID. */
|
|
19
|
+
stageName?: () => string;
|
|
18
20
|
}): Promise<string>;
|
|
19
21
|
export declare function sweepRpcState(root: string, activeDir: string, isManagedDir: (name: string) => boolean, log?: RpcLogChannel): Promise<void>;
|
|
20
22
|
export interface DiscoverPortFileOptions {
|
package/dist/rpc/port-file.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { chmod, mkdir, readdir, readFile, rename, rmdir, unlink,
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { chmod, mkdir, open, readdir, readFile, rename, rmdir, unlink, } from 'node:fs/promises';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
/**
|
|
5
5
|
* A PID a liveness probe may address. `process.kill(0 | negative, 0)` signals
|
|
@@ -63,7 +63,7 @@ async function removeCorruptPortFile(portFile, log) {
|
|
|
63
63
|
}
|
|
64
64
|
export async function writePortFile(dir, entry, options = {}) {
|
|
65
65
|
// The directory can be removed by another project's sweep between our
|
|
66
|
-
// mkdir and the first
|
|
66
|
+
// mkdir and the first open/rename, so the whole create-then-rename
|
|
67
67
|
// unit is retried once on ENOENT. The retry recreates the directory; a
|
|
68
68
|
// persistent ENOENT (e.g. permission, read-only parent) will surface on
|
|
69
69
|
// the second attempt — failing fast beats an unbounded loop.
|
|
@@ -77,18 +77,24 @@ export async function writePortFile(dir, entry, options = {}) {
|
|
|
77
77
|
await options.beforeWrite?.();
|
|
78
78
|
const full = { ...entry, startedAt: Date.now() };
|
|
79
79
|
const target = join(dir, `port-${entry.pid}.json`);
|
|
80
|
-
const tmp = `${target}.${
|
|
80
|
+
const tmp = `${target}.${(options.stageName ?? randomUUID)()}.tmp`;
|
|
81
|
+
let handle;
|
|
82
|
+
let created = false;
|
|
81
83
|
try {
|
|
82
|
-
await
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
handle = await open(tmp, 'wx', 0o600);
|
|
85
|
+
created = true;
|
|
86
|
+
await handle.writeFile(JSON.stringify(full), 'utf8');
|
|
87
|
+
await handle.chmod(0o600);
|
|
88
|
+
await handle.close();
|
|
89
|
+
handle = undefined;
|
|
86
90
|
await rename(tmp, target);
|
|
91
|
+
created = false;
|
|
87
92
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
finally {
|
|
94
|
+
await handle?.close().catch(() => { });
|
|
95
|
+
// Remove token-bearing staging bytes only if this call created them.
|
|
96
|
+
if (created)
|
|
97
|
+
await unlink(tmp).catch(() => { });
|
|
92
98
|
}
|
|
93
99
|
return target;
|
|
94
100
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/common-auth",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "Shared code for the CortexKit auth plugins: account pool, quota and routing, commands and auth menu, OpenCode 2 hooks, Claustrum custody, and plumbing (loopback RPC, file locks, logger, sidebar state, TUI preferences and build).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"types": "./dist/rpc/index.d.ts",
|
|
17
17
|
"import": "./dist/rpc/index.js"
|
|
18
18
|
},
|
|
19
|
+
"./rpc/client": {
|
|
20
|
+
"types": "./dist/rpc/client.d.ts",
|
|
21
|
+
"import": "./dist/rpc/client.js"
|
|
22
|
+
},
|
|
19
23
|
"./fs": {
|
|
20
24
|
"types": "./dist/fs/index.d.ts",
|
|
21
25
|
"import": "./dist/fs/index.js"
|