cleargate 0.1.0-alpha.1
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/README.md +3 -0
- package/dist/admin-api/index.cjs +359 -0
- package/dist/admin-api/index.cjs.map +1 -0
- package/dist/admin-api/index.d.cts +135 -0
- package/dist/admin-api/index.d.ts +135 -0
- package/dist/admin-api/index.js +313 -0
- package/dist/admin-api/index.js.map +1 -0
- package/dist/auth/factory.cjs +198 -0
- package/dist/auth/factory.cjs.map +1 -0
- package/dist/auth/factory.d.cts +10 -0
- package/dist/auth/factory.d.ts +10 -0
- package/dist/auth/factory.js +8 -0
- package/dist/auth/factory.js.map +1 -0
- package/dist/auth/require-token.cjs +40 -0
- package/dist/auth/require-token.cjs.map +1 -0
- package/dist/auth/require-token.d.cts +11 -0
- package/dist/auth/require-token.d.ts +11 -0
- package/dist/auth/require-token.js +16 -0
- package/dist/auth/require-token.js.map +1 -0
- package/dist/auth/token-store.cjs +20 -0
- package/dist/auth/token-store.cjs.map +1 -0
- package/dist/auth/token-store.d.cts +26 -0
- package/dist/auth/token-store.d.ts +26 -0
- package/dist/auth/token-store.js +2 -0
- package/dist/auth/token-store.js.map +1 -0
- package/dist/chunk-4V4QABOJ.js +165 -0
- package/dist/chunk-4V4QABOJ.js.map +1 -0
- package/dist/cli.cjs +459 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +280 -0
- package/dist/cli.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/admin-api/errors.ts
|
|
4
|
+
var AdminApiError = class extends Error {
|
|
5
|
+
constructor(kind, status, details, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.kind = kind;
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.details = details;
|
|
10
|
+
this.name = "AdminApiError";
|
|
11
|
+
}
|
|
12
|
+
kind;
|
|
13
|
+
status;
|
|
14
|
+
details;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/admin-api/responses.ts
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
var ProjectSchema = z.object({
|
|
20
|
+
id: z.string(),
|
|
21
|
+
name: z.string(),
|
|
22
|
+
created_by: z.string(),
|
|
23
|
+
created_at: z.string(),
|
|
24
|
+
deleted_at: z.string().nullable()
|
|
25
|
+
}).strict();
|
|
26
|
+
var MemberSchema = z.object({
|
|
27
|
+
id: z.string(),
|
|
28
|
+
project_id: z.string(),
|
|
29
|
+
email: z.string(),
|
|
30
|
+
role: z.string(),
|
|
31
|
+
display_name: z.string().nullable().optional(),
|
|
32
|
+
created_at: z.string(),
|
|
33
|
+
status: z.enum(["pending", "active"])
|
|
34
|
+
}).strict();
|
|
35
|
+
var InviteCreatedSchema = z.object({
|
|
36
|
+
member: MemberSchema,
|
|
37
|
+
invite_url: z.string(),
|
|
38
|
+
invite_token: z.string(),
|
|
39
|
+
invite_expires_in: z.number().int()
|
|
40
|
+
}).strict();
|
|
41
|
+
var TokenMetaSchema = z.object({
|
|
42
|
+
id: z.string(),
|
|
43
|
+
member_id: z.string(),
|
|
44
|
+
name: z.string(),
|
|
45
|
+
created_at: z.string(),
|
|
46
|
+
expires_at: z.string().nullable().optional(),
|
|
47
|
+
last_used_at: z.string().nullable().optional(),
|
|
48
|
+
revoked_at: z.string().nullable().optional()
|
|
49
|
+
}).strict();
|
|
50
|
+
var TokenIssuedSchema = z.object({
|
|
51
|
+
id: z.string(),
|
|
52
|
+
member_id: z.string(),
|
|
53
|
+
name: z.string(),
|
|
54
|
+
created_at: z.string(),
|
|
55
|
+
expires_at: z.string().nullable().optional(),
|
|
56
|
+
last_used_at: z.string().nullable().optional(),
|
|
57
|
+
revoked_at: z.string().nullable().optional(),
|
|
58
|
+
token: z.string()
|
|
59
|
+
}).strict();
|
|
60
|
+
var ErrorBodySchema = z.object({
|
|
61
|
+
error: z.string(),
|
|
62
|
+
details: z.record(z.string(), z.unknown()).optional()
|
|
63
|
+
}).strict();
|
|
64
|
+
|
|
65
|
+
// src/admin-api/redact.ts
|
|
66
|
+
var SENSITIVE_KEYS = /* @__PURE__ */ new Set(["token", "refresh_token", "invite_token"]);
|
|
67
|
+
function redactSensitive(obj) {
|
|
68
|
+
if (Array.isArray(obj)) {
|
|
69
|
+
return obj.map((item) => redactSensitive(item));
|
|
70
|
+
}
|
|
71
|
+
if (obj !== null && typeof obj === "object") {
|
|
72
|
+
const result = {};
|
|
73
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
74
|
+
if (SENSITIVE_KEYS.has(key)) {
|
|
75
|
+
result[key] = "<redacted>";
|
|
76
|
+
} else {
|
|
77
|
+
result[key] = redactSensitive(value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
return obj;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/admin-api/client.ts
|
|
86
|
+
function defaultWarn(msg) {
|
|
87
|
+
process.stderr.write(msg + "\n");
|
|
88
|
+
}
|
|
89
|
+
var AdminApiClientImpl = class {
|
|
90
|
+
baseUrl;
|
|
91
|
+
token;
|
|
92
|
+
fetchFn;
|
|
93
|
+
warn;
|
|
94
|
+
userAgent;
|
|
95
|
+
constructor(opts) {
|
|
96
|
+
this.baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
97
|
+
this.token = opts.token;
|
|
98
|
+
this.fetchFn = opts.fetch ?? globalThis.fetch;
|
|
99
|
+
this.warn = opts.warn ?? defaultWarn;
|
|
100
|
+
this.userAgent = opts.userAgent ?? `cleargate`;
|
|
101
|
+
}
|
|
102
|
+
debugLog(method, path2, status, body) {
|
|
103
|
+
if (process.env["CLEARGATE_LOG_LEVEL"] === "debug") {
|
|
104
|
+
const redacted = redactSensitive(body);
|
|
105
|
+
this.warn(`[admin-api] ${method} ${path2} \u2192 ${status} ${JSON.stringify(redacted)}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async request(method, urlPath, body) {
|
|
109
|
+
const url = `${this.baseUrl}/admin-api/v1${urlPath}`;
|
|
110
|
+
const headers = {
|
|
111
|
+
Authorization: `Bearer ${this.token}`,
|
|
112
|
+
"User-Agent": this.userAgent,
|
|
113
|
+
Accept: "application/json"
|
|
114
|
+
};
|
|
115
|
+
if (body !== void 0) {
|
|
116
|
+
headers["Content-Type"] = "application/json";
|
|
117
|
+
}
|
|
118
|
+
let response;
|
|
119
|
+
try {
|
|
120
|
+
response = await this.fetchFn(url, {
|
|
121
|
+
method,
|
|
122
|
+
headers,
|
|
123
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
124
|
+
});
|
|
125
|
+
} catch (err) {
|
|
126
|
+
throw new AdminApiError(
|
|
127
|
+
"network",
|
|
128
|
+
null,
|
|
129
|
+
err,
|
|
130
|
+
`cannot reach ${this.baseUrl} (${err instanceof Error ? err.message : String(err)})`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
let responseBody = null;
|
|
134
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
135
|
+
if (contentType.includes("application/json")) {
|
|
136
|
+
try {
|
|
137
|
+
responseBody = await response.json();
|
|
138
|
+
} catch {
|
|
139
|
+
responseBody = null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
this.debugLog(method, urlPath, response.status, responseBody);
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
const errBody = responseBody;
|
|
145
|
+
if (response.status === 401) {
|
|
146
|
+
throw new AdminApiError("auth", 401, responseBody, "Admin token rejected.");
|
|
147
|
+
}
|
|
148
|
+
if (response.status === 403) {
|
|
149
|
+
throw new AdminApiError("forbidden", 403, responseBody, "Token is not admin-role.");
|
|
150
|
+
}
|
|
151
|
+
if (response.status === 404) {
|
|
152
|
+
throw new AdminApiError("not_found", 404, responseBody, "Not found.");
|
|
153
|
+
}
|
|
154
|
+
if (response.status === 400 || response.status === 409) {
|
|
155
|
+
throw new AdminApiError(
|
|
156
|
+
"invalid_request",
|
|
157
|
+
response.status,
|
|
158
|
+
errBody?.details ?? responseBody,
|
|
159
|
+
`Invalid request: ${errBody?.error ?? "unknown"}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (response.status >= 500) {
|
|
163
|
+
throw new AdminApiError(
|
|
164
|
+
"server",
|
|
165
|
+
response.status,
|
|
166
|
+
responseBody,
|
|
167
|
+
`Server error ${response.status}.`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
throw new AdminApiError(
|
|
171
|
+
"server",
|
|
172
|
+
response.status,
|
|
173
|
+
responseBody,
|
|
174
|
+
`Unexpected status ${response.status}.`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return responseBody;
|
|
178
|
+
}
|
|
179
|
+
async createProject(input) {
|
|
180
|
+
const raw = await this.request("POST", "/projects", { name: input.name });
|
|
181
|
+
const parsed = ProjectSchema.safeParse(raw);
|
|
182
|
+
if (!parsed.success) {
|
|
183
|
+
throw new AdminApiError(
|
|
184
|
+
"response_shape",
|
|
185
|
+
null,
|
|
186
|
+
parsed.error,
|
|
187
|
+
"Server returned unexpected response shape (CLI may be out of date)."
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return parsed.data;
|
|
191
|
+
}
|
|
192
|
+
async inviteMember(input) {
|
|
193
|
+
const body = {
|
|
194
|
+
email: input.email,
|
|
195
|
+
role: input.role
|
|
196
|
+
};
|
|
197
|
+
if (input.displayName !== void 0) {
|
|
198
|
+
body["display_name"] = input.displayName;
|
|
199
|
+
}
|
|
200
|
+
const raw = await this.request(
|
|
201
|
+
"POST",
|
|
202
|
+
`/projects/${input.projectId}/members`,
|
|
203
|
+
body
|
|
204
|
+
);
|
|
205
|
+
const parsed = InviteCreatedSchema.safeParse(raw);
|
|
206
|
+
if (!parsed.success) {
|
|
207
|
+
throw new AdminApiError(
|
|
208
|
+
"response_shape",
|
|
209
|
+
null,
|
|
210
|
+
parsed.error,
|
|
211
|
+
"Server returned unexpected response shape (CLI may be out of date)."
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return parsed.data;
|
|
215
|
+
}
|
|
216
|
+
async issueToken(input) {
|
|
217
|
+
const body = {
|
|
218
|
+
member_id: input.memberId,
|
|
219
|
+
name: input.name
|
|
220
|
+
};
|
|
221
|
+
if (input.expiresAt !== void 0) {
|
|
222
|
+
body["expires_at"] = input.expiresAt;
|
|
223
|
+
}
|
|
224
|
+
const raw = await this.request(
|
|
225
|
+
"POST",
|
|
226
|
+
`/projects/${input.projectId}/tokens`,
|
|
227
|
+
body
|
|
228
|
+
);
|
|
229
|
+
const parsed = TokenIssuedSchema.safeParse(raw);
|
|
230
|
+
if (!parsed.success) {
|
|
231
|
+
throw new AdminApiError(
|
|
232
|
+
"response_shape",
|
|
233
|
+
null,
|
|
234
|
+
parsed.error,
|
|
235
|
+
"Server returned unexpected response shape (CLI may be out of date)."
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
return parsed.data;
|
|
239
|
+
}
|
|
240
|
+
async revokeToken(input) {
|
|
241
|
+
await this.request("DELETE", `/tokens/${input.tokenId}`, void 0);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
function createAdminApiClient(opts) {
|
|
245
|
+
return new AdminApiClientImpl(opts);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/admin-api/admin-auth.ts
|
|
249
|
+
import * as fs from "fs";
|
|
250
|
+
import * as os from "os";
|
|
251
|
+
import * as path from "path";
|
|
252
|
+
import { z as z2 } from "zod";
|
|
253
|
+
var AdminAuthFileSchema = z2.object({
|
|
254
|
+
version: z2.literal(1),
|
|
255
|
+
token: z2.string().min(1)
|
|
256
|
+
}).strict();
|
|
257
|
+
var MISSING_TOKEN_ERROR = "No admin token. Set CLEARGATE_ADMIN_TOKEN or write ~/.cleargate/admin-auth.json (chmod 600). See README \xA7admin-jwt.";
|
|
258
|
+
function loadAdminAuth(opts) {
|
|
259
|
+
const env = opts?.env ?? process.env;
|
|
260
|
+
const warn = opts?.warn ?? ((msg) => process.stderr.write(msg + "\n"));
|
|
261
|
+
const envToken = env["CLEARGATE_ADMIN_TOKEN"];
|
|
262
|
+
if (envToken) {
|
|
263
|
+
return { token: envToken, source: "env" };
|
|
264
|
+
}
|
|
265
|
+
const homedirFn = opts?.homedir ?? os.homedir;
|
|
266
|
+
const filePath = opts?.filePath ?? path.join(homedirFn(), ".cleargate", "admin-auth.json");
|
|
267
|
+
let raw;
|
|
268
|
+
try {
|
|
269
|
+
raw = fs.readFileSync(filePath, "utf8");
|
|
270
|
+
} catch (err) {
|
|
271
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
272
|
+
throw new Error(MISSING_TOKEN_ERROR);
|
|
273
|
+
}
|
|
274
|
+
throw new Error(`Failed to read admin-auth file at ${filePath}: ${String(err)}`);
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const stat = fs.statSync(filePath);
|
|
278
|
+
const mode = stat.mode & 511;
|
|
279
|
+
if (mode & 63) {
|
|
280
|
+
warn(
|
|
281
|
+
`cleargate-admin: warning: ${filePath} is group/world readable (mode ${mode.toString(8).padStart(3, "0")}). Run: chmod 600 ${filePath}`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
} catch {
|
|
285
|
+
}
|
|
286
|
+
let parsed;
|
|
287
|
+
try {
|
|
288
|
+
parsed = JSON.parse(raw);
|
|
289
|
+
} catch {
|
|
290
|
+
throw new Error(`Failed to parse admin-auth file at ${filePath}: invalid JSON`);
|
|
291
|
+
}
|
|
292
|
+
const result = AdminAuthFileSchema.safeParse(parsed);
|
|
293
|
+
if (!result.success) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Invalid admin-auth file at ${filePath}: ${result.error.message}`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return { token: result.data.token, source: "file" };
|
|
299
|
+
}
|
|
300
|
+
export {
|
|
301
|
+
AdminApiError,
|
|
302
|
+
AdminAuthFileSchema,
|
|
303
|
+
ErrorBodySchema,
|
|
304
|
+
InviteCreatedSchema,
|
|
305
|
+
MemberSchema,
|
|
306
|
+
ProjectSchema,
|
|
307
|
+
TokenIssuedSchema,
|
|
308
|
+
TokenMetaSchema,
|
|
309
|
+
createAdminApiClient,
|
|
310
|
+
loadAdminAuth,
|
|
311
|
+
redactSensitive
|
|
312
|
+
};
|
|
313
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/admin-api/errors.ts","../../src/admin-api/responses.ts","../../src/admin-api/redact.ts","../../src/admin-api/client.ts","../../src/admin-api/admin-auth.ts"],"sourcesContent":["/**\n * Typed error class for all admin API failures.\n * kind → exit code mapping lives in mcp/scripts/commands/_render-error.ts\n */\nexport class AdminApiError extends Error {\n constructor(\n public readonly kind:\n | 'network'\n | 'auth'\n | 'forbidden'\n | 'not_found'\n | 'invalid_request'\n | 'server'\n | 'response_shape',\n public readonly status: number | null,\n public readonly details: unknown,\n message: string,\n ) {\n super(message);\n this.name = 'AdminApiError';\n }\n}\n","/**\n * Vendored Zod response schemas — hand-authored from\n * mcp/src/admin-api/__snapshots__/openapi.test.ts.snap\n *\n * Snapshot drift is detected by cleargate-cli/test/admin-api/snapshot-drift.test.ts,\n * which reads the snapshot file at runtime and asserts field-set equality.\n */\nimport { z } from 'zod';\n\nexport const ProjectSchema = z\n .object({\n id: z.string(),\n name: z.string(),\n created_by: z.string(),\n created_at: z.string(),\n deleted_at: z.string().nullable(),\n })\n .strict();\n\nexport type Project = z.infer<typeof ProjectSchema>;\n\nexport const MemberSchema = z\n .object({\n id: z.string(),\n project_id: z.string(),\n email: z.string(),\n role: z.string(),\n display_name: z.string().nullable().optional(),\n created_at: z.string(),\n status: z.enum(['pending', 'active']),\n })\n .strict();\n\nexport type Member = z.infer<typeof MemberSchema>;\n\nexport const InviteCreatedSchema = z\n .object({\n member: MemberSchema,\n invite_url: z.string(),\n invite_token: z.string(),\n invite_expires_in: z.number().int(),\n })\n .strict();\n\nexport type InviteCreated = z.infer<typeof InviteCreatedSchema>;\n\nexport const TokenMetaSchema = z\n .object({\n id: z.string(),\n member_id: z.string(),\n name: z.string(),\n created_at: z.string(),\n expires_at: z.string().nullable().optional(),\n last_used_at: z.string().nullable().optional(),\n revoked_at: z.string().nullable().optional(),\n })\n .strict();\n\nexport type TokenMeta = z.infer<typeof TokenMetaSchema>;\n\n// TokenIssued = TokenMeta + plaintext token field (returned exactly once)\nexport const TokenIssuedSchema = z\n .object({\n id: z.string(),\n member_id: z.string(),\n name: z.string(),\n created_at: z.string(),\n expires_at: z.string().nullable().optional(),\n last_used_at: z.string().nullable().optional(),\n revoked_at: z.string().nullable().optional(),\n token: z.string(),\n })\n .strict();\n\nexport type TokenIssued = z.infer<typeof TokenIssuedSchema>;\n\nexport const ErrorBodySchema = z\n .object({\n error: z.string(),\n details: z.record(z.string(), z.unknown()).optional(),\n })\n .strict();\n\nexport type ErrorBody = z.infer<typeof ErrorBodySchema>;\n","/**\n * Recursively replaces sensitive key values with '<redacted>'.\n * Used in debug log paths — never in success output.\n * Keys stripped: token, refresh_token, invite_token\n */\nconst SENSITIVE_KEYS = new Set(['token', 'refresh_token', 'invite_token']);\n\nexport function redactSensitive(obj: unknown): unknown {\n if (Array.isArray(obj)) {\n return obj.map((item) => redactSensitive(item));\n }\n if (obj !== null && typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (SENSITIVE_KEYS.has(key)) {\n result[key] = '<redacted>';\n } else {\n result[key] = redactSensitive(value);\n }\n }\n return result;\n }\n return obj;\n}\n","/**\n * AdminApiClient — typed HTTP client for the ClearGate admin API.\n *\n * Key implementation notes:\n * - CLI method args are camelCase; wire bodies are snake_case (converted internally)\n * - DELETE requests MUST omit Content-Type (Fastify 5 FST_ERR_CTP_EMPTY_JSON_BODY)\n * - All 2xx responses are validated through vendored Zod schemas\n * - Errors map to AdminApiError with kind → exit code table in D6\n */\nimport { AdminApiError } from './errors.js';\nimport {\n ProjectSchema,\n InviteCreatedSchema,\n TokenIssuedSchema,\n type Project,\n type InviteCreated,\n type TokenIssued,\n} from './responses.js';\nimport { redactSensitive } from './redact.js';\n\nexport interface AdminApiClientOptions {\n baseUrl: string;\n token: string;\n fetch?: typeof globalThis.fetch;\n warn?: (msg: string) => void;\n userAgent?: string;\n}\n\nexport interface AdminApiClient {\n createProject(input: { name: string }): Promise<Project>;\n inviteMember(input: {\n projectId: string;\n email: string;\n role: 'user' | 'service';\n displayName?: string;\n }): Promise<InviteCreated>;\n issueToken(input: {\n projectId: string;\n memberId: string;\n name: string;\n expiresAt?: string;\n }): Promise<TokenIssued>;\n revokeToken(input: { tokenId: string }): Promise<void>;\n}\n\nfunction defaultWarn(msg: string): void {\n process.stderr.write(msg + '\\n');\n}\n\nclass AdminApiClientImpl implements AdminApiClient {\n private readonly baseUrl: string;\n private readonly token: string;\n private readonly fetchFn: typeof globalThis.fetch;\n private readonly warn: (msg: string) => void;\n private readonly userAgent: string;\n\n constructor(opts: AdminApiClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, '');\n this.token = opts.token;\n this.fetchFn = opts.fetch ?? globalThis.fetch;\n this.warn = opts.warn ?? defaultWarn;\n this.userAgent = opts.userAgent ?? `cleargate`;\n }\n\n private debugLog(method: string, path: string, status: number, body: unknown): void {\n if (process.env['CLEARGATE_LOG_LEVEL'] === 'debug') {\n const redacted = redactSensitive(body);\n this.warn(`[admin-api] ${method} ${path} → ${status} ${JSON.stringify(redacted)}`);\n }\n }\n\n private async request<T>(\n method: string,\n urlPath: string,\n body?: Record<string, unknown>,\n ): Promise<T> {\n const url = `${this.baseUrl}/admin-api/v1${urlPath}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': this.userAgent,\n Accept: 'application/json',\n };\n\n // CRITICAL: omit Content-Type on requests without body (DELETE)\n // Fastify 5 throws FST_ERR_CTP_EMPTY_JSON_BODY if Content-Type is set with empty body\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n let response: Response;\n try {\n response = await this.fetchFn(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n } catch (err) {\n throw new AdminApiError(\n 'network',\n null,\n err,\n `cannot reach ${this.baseUrl} (${err instanceof Error ? err.message : String(err)})`,\n );\n }\n\n // Parse response body when present\n let responseBody: unknown = null;\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n try {\n responseBody = await response.json();\n } catch {\n responseBody = null;\n }\n }\n\n this.debugLog(method, urlPath, response.status, responseBody);\n\n // Map HTTP status to AdminApiError kinds\n if (!response.ok) {\n const errBody = responseBody as { error?: string; details?: unknown } | null;\n if (response.status === 401) {\n throw new AdminApiError('auth', 401, responseBody, 'Admin token rejected.');\n }\n if (response.status === 403) {\n throw new AdminApiError('forbidden', 403, responseBody, 'Token is not admin-role.');\n }\n if (response.status === 404) {\n throw new AdminApiError('not_found', 404, responseBody, 'Not found.');\n }\n if (response.status === 400 || response.status === 409) {\n throw new AdminApiError(\n 'invalid_request',\n response.status,\n errBody?.details ?? responseBody,\n `Invalid request: ${errBody?.error ?? 'unknown'}`,\n );\n }\n if (response.status >= 500) {\n throw new AdminApiError(\n 'server',\n response.status,\n responseBody,\n `Server error ${response.status}.`,\n );\n }\n throw new AdminApiError(\n 'server',\n response.status,\n responseBody,\n `Unexpected status ${response.status}.`,\n );\n }\n\n return responseBody as T;\n }\n\n async createProject(input: { name: string }): Promise<Project> {\n const raw = await this.request<unknown>('POST', '/projects', { name: input.name });\n const parsed = ProjectSchema.safeParse(raw);\n if (!parsed.success) {\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async inviteMember(input: {\n projectId: string;\n email: string;\n role: 'user' | 'service';\n displayName?: string;\n }): Promise<InviteCreated> {\n const body: Record<string, unknown> = {\n email: input.email,\n role: input.role,\n };\n if (input.displayName !== undefined) {\n body['display_name'] = input.displayName;\n }\n const raw = await this.request<unknown>(\n 'POST',\n `/projects/${input.projectId}/members`,\n body,\n );\n const parsed = InviteCreatedSchema.safeParse(raw);\n if (!parsed.success) {\n // Try MemberSchema in case the server returned a member-only response\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async issueToken(input: {\n projectId: string;\n memberId: string;\n name: string;\n expiresAt?: string;\n }): Promise<TokenIssued> {\n const body: Record<string, unknown> = {\n member_id: input.memberId,\n name: input.name,\n };\n if (input.expiresAt !== undefined) {\n body['expires_at'] = input.expiresAt;\n }\n const raw = await this.request<unknown>(\n 'POST',\n `/projects/${input.projectId}/tokens`,\n body,\n );\n const parsed = TokenIssuedSchema.safeParse(raw);\n if (!parsed.success) {\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async revokeToken(input: { tokenId: string }): Promise<void> {\n // No body — Content-Type must be omitted (Fastify 5 CTP quirk)\n await this.request<unknown>('DELETE', `/tokens/${input.tokenId}`, undefined);\n }\n}\n\nexport function createAdminApiClient(opts: AdminApiClientOptions): AdminApiClient {\n return new AdminApiClientImpl(opts);\n}\n","/**\n * Loads an admin JWT for use with cleargate-admin CLI commands.\n *\n * Load order:\n * 1. CLEARGATE_ADMIN_TOKEN env var (wins immediately — file is not read)\n * 2. ~/.cleargate/admin-auth.json (shape: { version: 1, token: string })\n *\n * DISTINCT from FileTokenStore: that file holds user profile → refresh-token maps.\n * This file holds a single admin JWT acquired out-of-band via dev-issue-token.\n */\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\nexport const AdminAuthFileSchema = z\n .object({\n version: z.literal(1),\n token: z.string().min(1),\n })\n .strict();\n\nexport interface LoadAdminAuthOptions {\n env?: NodeJS.ProcessEnv;\n filePath?: string;\n homedir?: () => string;\n warn?: (msg: string) => void;\n}\n\nexport interface AdminAuth {\n token: string;\n source: 'env' | 'file';\n}\n\nconst MISSING_TOKEN_ERROR =\n 'No admin token. Set CLEARGATE_ADMIN_TOKEN or write ~/.cleargate/admin-auth.json (chmod 600). See README §admin-jwt.';\n\nexport function loadAdminAuth(opts?: LoadAdminAuthOptions): AdminAuth {\n const env = opts?.env ?? process.env;\n const warn = opts?.warn ?? ((msg: string) => process.stderr.write(msg + '\\n'));\n\n // Env wins — file is not read at all when env is set\n const envToken = env['CLEARGATE_ADMIN_TOKEN'];\n if (envToken) {\n return { token: envToken, source: 'env' };\n }\n\n // Resolve file path\n const homedirFn = opts?.homedir ?? os.homedir;\n const filePath =\n opts?.filePath ?? path.join(homedirFn(), '.cleargate', 'admin-auth.json');\n\n // Try file\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf8');\n } catch (err) {\n if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(MISSING_TOKEN_ERROR);\n }\n throw new Error(`Failed to read admin-auth file at ${filePath}: ${String(err)}`);\n }\n\n // Check file permissions (warn if too permissive)\n try {\n const stat = fs.statSync(filePath);\n const mode = stat.mode & 0o777;\n if (mode & 0o077) {\n warn(\n `cleargate-admin: warning: ${filePath} is group/world readable (mode ${(mode).toString(8).padStart(3, '0')}). Run: chmod 600 ${filePath}`,\n );\n }\n } catch {\n // If we can't stat the file, ignore — the read already succeeded\n }\n\n // Parse JSON\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Failed to parse admin-auth file at ${filePath}: invalid JSON`);\n }\n\n // Validate with strict schema\n const result = AdminAuthFileSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\n `Invalid admin-auth file at ${filePath}: ${result.error.message}`,\n );\n }\n\n return { token: result.data.token, source: 'file' };\n}\n"],"mappings":"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","names":["path","z"]}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/auth/factory.ts
|
|
32
|
+
var factory_exports = {};
|
|
33
|
+
__export(factory_exports, {
|
|
34
|
+
createTokenStore: () => createTokenStore
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(factory_exports);
|
|
37
|
+
var os = __toESM(require("os"), 1);
|
|
38
|
+
var path2 = __toESM(require("path"), 1);
|
|
39
|
+
|
|
40
|
+
// src/auth/keychain-store.ts
|
|
41
|
+
var import_keyring = require("@napi-rs/keyring");
|
|
42
|
+
var KeychainTokenStore = class {
|
|
43
|
+
constructor(service) {
|
|
44
|
+
this.service = service;
|
|
45
|
+
}
|
|
46
|
+
service;
|
|
47
|
+
backend = "keychain";
|
|
48
|
+
async save(profile, token) {
|
|
49
|
+
new import_keyring.Entry(this.service, profile).setPassword(token);
|
|
50
|
+
}
|
|
51
|
+
async load(profile) {
|
|
52
|
+
try {
|
|
53
|
+
const result = new import_keyring.Entry(this.service, profile).getPassword();
|
|
54
|
+
return result ?? null;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async remove(profile) {
|
|
60
|
+
try {
|
|
61
|
+
new import_keyring.Entry(this.service, profile).deletePassword();
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/auth/file-store.ts
|
|
68
|
+
var fs = __toESM(require("fs/promises"), 1);
|
|
69
|
+
var path = __toESM(require("path"), 1);
|
|
70
|
+
var import_zod = require("zod");
|
|
71
|
+
var ProfileEntrySchema = import_zod.z.object({ refreshToken: import_zod.z.string().min(1) }).strict();
|
|
72
|
+
var AuthFileSchema = import_zod.z.object({
|
|
73
|
+
version: import_zod.z.literal(1),
|
|
74
|
+
profiles: import_zod.z.record(import_zod.z.string().min(1), ProfileEntrySchema)
|
|
75
|
+
}).strict();
|
|
76
|
+
var EMPTY_AUTH_FILE = { version: 1, profiles: {} };
|
|
77
|
+
var FileTokenStore = class {
|
|
78
|
+
constructor(filePath) {
|
|
79
|
+
this.filePath = filePath;
|
|
80
|
+
}
|
|
81
|
+
filePath;
|
|
82
|
+
backend = "file";
|
|
83
|
+
async save(profile, token) {
|
|
84
|
+
const current = await this.readFile();
|
|
85
|
+
const updated = {
|
|
86
|
+
...current,
|
|
87
|
+
profiles: {
|
|
88
|
+
...current.profiles,
|
|
89
|
+
[profile]: { refreshToken: token }
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
await this.writeFile(updated);
|
|
93
|
+
}
|
|
94
|
+
async load(profile) {
|
|
95
|
+
const data = await this.readFile();
|
|
96
|
+
return data.profiles[profile]?.refreshToken ?? null;
|
|
97
|
+
}
|
|
98
|
+
async remove(profile) {
|
|
99
|
+
let current;
|
|
100
|
+
try {
|
|
101
|
+
current = await this.readFile();
|
|
102
|
+
} catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!(profile in current.profiles)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const { [profile]: _removed, ...rest } = current.profiles;
|
|
109
|
+
const updated = { ...current, profiles: rest };
|
|
110
|
+
await this.writeFile(updated);
|
|
111
|
+
}
|
|
112
|
+
async readFile() {
|
|
113
|
+
let raw;
|
|
114
|
+
try {
|
|
115
|
+
raw = await fs.readFile(this.filePath, "utf8");
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (err.code === "ENOENT") {
|
|
118
|
+
return EMPTY_AUTH_FILE;
|
|
119
|
+
}
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
let parsed;
|
|
123
|
+
try {
|
|
124
|
+
parsed = JSON.parse(raw);
|
|
125
|
+
} catch {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Failed to parse auth file at ${this.filePath}: invalid JSON`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const result = AuthFileSchema.safeParse(parsed);
|
|
131
|
+
if (!result.success) {
|
|
132
|
+
const versionCheck = parsed?.["version"];
|
|
133
|
+
if (versionCheck !== 1) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Invalid auth file at ${this.filePath}: unsupported version ${String(versionCheck)}. Please upgrade \`cleargate\` to read this file.`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Invalid auth file at ${this.filePath}: ${result.error.message}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return result.data;
|
|
143
|
+
}
|
|
144
|
+
async writeFile(data) {
|
|
145
|
+
const dir = path.dirname(this.filePath);
|
|
146
|
+
await fs.mkdir(dir, { recursive: true, mode: 448 });
|
|
147
|
+
await fs.chmod(dir, 448).catch(() => {
|
|
148
|
+
});
|
|
149
|
+
const json = JSON.stringify(data, null, 2);
|
|
150
|
+
const tmpPath = path.join(dir, ".auth.json.tmp");
|
|
151
|
+
await fs.writeFile(tmpPath, json, { mode: 384 });
|
|
152
|
+
await fs.chmod(tmpPath, 384);
|
|
153
|
+
await fs.rename(tmpPath, this.filePath);
|
|
154
|
+
await fs.chmod(this.filePath, 384);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// src/auth/factory.ts
|
|
159
|
+
var DEFAULT_KEYCHAIN_SERVICE = "cleargate";
|
|
160
|
+
function resolveFilePath(opts) {
|
|
161
|
+
if (opts.filePath) return opts.filePath;
|
|
162
|
+
const home = os.homedir();
|
|
163
|
+
if (!home) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
"Cannot determine home directory. Set opts.filePath explicitly or ensure os.homedir() returns a non-empty string."
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return path2.join(home, ".cleargate", "auth.json");
|
|
169
|
+
}
|
|
170
|
+
function defaultWarn(msg) {
|
|
171
|
+
process.stderr.write(msg + "\n");
|
|
172
|
+
}
|
|
173
|
+
async function createTokenStore(opts = {}) {
|
|
174
|
+
const filePath = resolveFilePath(opts);
|
|
175
|
+
const service = opts.keychainService ?? DEFAULT_KEYCHAIN_SERVICE;
|
|
176
|
+
const warn = opts.warn ?? defaultWarn;
|
|
177
|
+
if (opts.forceBackend === "file") {
|
|
178
|
+
return new FileTokenStore(filePath);
|
|
179
|
+
}
|
|
180
|
+
if (opts.forceBackend === "keychain") {
|
|
181
|
+
return new KeychainTokenStore(service);
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const { Entry: Entry2 } = await import("@napi-rs/keyring");
|
|
185
|
+
new Entry2(service, "__cleargate_probe__").getPassword();
|
|
186
|
+
return new KeychainTokenStore(service);
|
|
187
|
+
} catch {
|
|
188
|
+
warn(
|
|
189
|
+
`cleargate: OS keychain unavailable, falling back to file storage at ${filePath}. Run with --log-level=debug for details.`
|
|
190
|
+
);
|
|
191
|
+
return new FileTokenStore(filePath);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
195
|
+
0 && (module.exports = {
|
|
196
|
+
createTokenStore
|
|
197
|
+
});
|
|
198
|
+
//# sourceMappingURL=factory.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/auth/factory.ts","../../src/auth/keychain-store.ts","../../src/auth/file-store.ts"],"sourcesContent":["import * as os from 'node:os';\nimport * as path from 'node:path';\nimport { KeychainTokenStore } from './keychain-store.js';\nimport { FileTokenStore } from './file-store.js';\nimport type { TokenStore, TokenStoreFactoryOptions } from './token-store.js';\n\nconst DEFAULT_KEYCHAIN_SERVICE = 'cleargate';\n\nfunction resolveFilePath(opts: TokenStoreFactoryOptions): string {\n if (opts.filePath) return opts.filePath;\n const home = os.homedir();\n if (!home) {\n throw new Error(\n 'Cannot determine home directory. Set opts.filePath explicitly or ensure os.homedir() returns a non-empty string.',\n );\n }\n return path.join(home, '.cleargate', 'auth.json');\n}\n\nfunction defaultWarn(msg: string): void {\n process.stderr.write(msg + '\\n');\n}\n\n/**\n * Creates a TokenStore, selecting the keychain backend when available and\n * falling back to file storage with a stderr warning when the OS keychain\n * cannot be accessed.\n */\nexport async function createTokenStore(\n opts: TokenStoreFactoryOptions = {},\n): Promise<TokenStore> {\n const filePath = resolveFilePath(opts);\n const service = opts.keychainService ?? DEFAULT_KEYCHAIN_SERVICE;\n const warn = opts.warn ?? defaultWarn;\n\n // Short-circuit if backend is forced (test seam, skips probe)\n if (opts.forceBackend === 'file') {\n return new FileTokenStore(filePath);\n }\n if (opts.forceBackend === 'keychain') {\n return new KeychainTokenStore(service);\n }\n\n // Probe the keychain to determine availability\n try {\n const { Entry } = await import('@napi-rs/keyring');\n new Entry(service, '__cleargate_probe__').getPassword();\n // Probe succeeded (returned string | null cleanly) — use keychain\n return new KeychainTokenStore(service);\n } catch {\n // Constructor threw (native module load failed, libsecret missing on Linux)\n // OR getPassword() threw (dbus not running, prompt cancelled)\n // Either way, keychain is unavailable for this CLI invocation\n warn(\n `cleargate: OS keychain unavailable, falling back to file storage at ${filePath}. Run with --log-level=debug for details.`,\n );\n return new FileTokenStore(filePath);\n }\n}\n","import { Entry } from '@napi-rs/keyring';\nimport type { TokenStore } from './token-store.js';\n\nexport class KeychainTokenStore implements TokenStore {\n readonly backend = 'keychain' as const;\n\n constructor(private readonly service: string) {}\n\n async save(profile: string, token: string): Promise<void> {\n new Entry(this.service, profile).setPassword(token);\n }\n\n async load(profile: string): Promise<string | null> {\n try {\n const result = new Entry(this.service, profile).getPassword();\n // getPassword() returns string | null per @napi-rs/keyring@1.2.0 index.d.ts:124\n // Despite the docstring claiming it throws NoEntry, the return type wins.\n // Handle both: null return AND potential thrown NoEntry (platform-specific).\n return result ?? null;\n } catch {\n // NoEntry or other keychain error — treat as absent\n return null;\n }\n }\n\n async remove(profile: string): Promise<void> {\n try {\n new Entry(this.service, profile).deletePassword();\n } catch {\n // Entry didn't exist or other keychain error — idempotent, swallow\n }\n }\n}\n","import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { z } from 'zod';\nimport type { TokenStore } from './token-store.js';\n\nconst ProfileEntrySchema = z.object({ refreshToken: z.string().min(1) }).strict();\n\nexport const AuthFileSchema = z\n .object({\n version: z.literal(1),\n profiles: z.record(z.string().min(1), ProfileEntrySchema),\n })\n .strict();\n\ntype AuthFile = z.infer<typeof AuthFileSchema>;\n\nconst EMPTY_AUTH_FILE: AuthFile = { version: 1, profiles: {} };\n\nexport class FileTokenStore implements TokenStore {\n readonly backend = 'file' as const;\n\n constructor(private readonly filePath: string) {}\n\n async save(profile: string, token: string): Promise<void> {\n const current = await this.readFile();\n const updated: AuthFile = {\n ...current,\n profiles: {\n ...current.profiles,\n [profile]: { refreshToken: token },\n },\n };\n await this.writeFile(updated);\n }\n\n async load(profile: string): Promise<string | null> {\n const data = await this.readFile();\n return data.profiles[profile]?.refreshToken ?? null;\n }\n\n async remove(profile: string): Promise<void> {\n let current: AuthFile;\n try {\n current = await this.readFile();\n } catch {\n // File doesn't exist or unreadable — no-op since there's nothing to remove\n return;\n }\n if (!(profile in current.profiles)) {\n return; // Profile doesn't exist — idempotent\n }\n const { [profile]: _removed, ...rest } = current.profiles;\n const updated: AuthFile = { ...current, profiles: rest };\n await this.writeFile(updated);\n }\n\n private async readFile(): Promise<AuthFile> {\n let raw: string;\n try {\n raw = await fs.readFile(this.filePath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return EMPTY_AUTH_FILE;\n }\n throw err;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\n `Failed to parse auth file at ${this.filePath}: invalid JSON`,\n );\n }\n\n const result = AuthFileSchema.safeParse(parsed);\n if (!result.success) {\n // Check for version mismatch specifically\n const versionCheck = (parsed as Record<string, unknown>)?.['version'];\n if (versionCheck !== 1) {\n throw new Error(\n `Invalid auth file at ${this.filePath}: unsupported version ${String(versionCheck)}. Please upgrade \\`cleargate\\` to read this file.`,\n );\n }\n throw new Error(\n `Invalid auth file at ${this.filePath}: ${result.error.message}`,\n );\n }\n\n return result.data;\n }\n\n private async writeFile(data: AuthFile): Promise<void> {\n const dir = path.dirname(this.filePath);\n await fs.mkdir(dir, { recursive: true, mode: 0o700 });\n // Explicit chmod after mkdir — mkdir only sets mode on newly created dirs\n await fs.chmod(dir, 0o700).catch(() => {\n // If chmod fails on existing dir, that's acceptable — we don't want to\n // surprise users who have set custom modes on ~/.cleargate/\n });\n\n const json = JSON.stringify(data, null, 2);\n const tmpPath = path.join(dir, '.auth.json.tmp');\n\n // Atomic write: write to tmp then rename to avoid partial-write corruption\n await fs.writeFile(tmpPath, json, { mode: 0o600 });\n // Explicit chmod after writeFile — writeFile only sets mode on file creation\n await fs.chmod(tmpPath, 0o600);\n await fs.rename(tmpPath, this.filePath);\n // After rename, chmod the final path to ensure it stays 0600\n await fs.chmod(this.filePath, 0o600);\n }\n}\n"],"mappings":"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","names":["path","Entry"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TokenStoreFactoryOptions, TokenStore } from './token-store.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a TokenStore, selecting the keychain backend when available and
|
|
5
|
+
* falling back to file storage with a stderr warning when the OS keychain
|
|
6
|
+
* cannot be accessed.
|
|
7
|
+
*/
|
|
8
|
+
declare function createTokenStore(opts?: TokenStoreFactoryOptions): Promise<TokenStore>;
|
|
9
|
+
|
|
10
|
+
export { createTokenStore };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TokenStoreFactoryOptions, TokenStore } from './token-store.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a TokenStore, selecting the keychain backend when available and
|
|
5
|
+
* falling back to file storage with a stderr warning when the OS keychain
|
|
6
|
+
* cannot be accessed.
|
|
7
|
+
*/
|
|
8
|
+
declare function createTokenStore(opts?: TokenStoreFactoryOptions): Promise<TokenStore>;
|
|
9
|
+
|
|
10
|
+
export { createTokenStore };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/auth/require-token.ts
|
|
22
|
+
var require_token_exports = {};
|
|
23
|
+
__export(require_token_exports, {
|
|
24
|
+
requireToken: () => requireToken
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(require_token_exports);
|
|
27
|
+
async function requireToken(profile, store) {
|
|
28
|
+
const token = await store.load(profile);
|
|
29
|
+
if (token === null) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`No refresh token for profile "${profile}". Run \`cleargate join <invite-url>\` first.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return token;
|
|
35
|
+
}
|
|
36
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
37
|
+
0 && (module.exports = {
|
|
38
|
+
requireToken
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=require-token.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/auth/require-token.ts"],"sourcesContent":["import type { TokenStore } from './token-store.js';\n\n/**\n * Asserts a refresh token is present for the given profile.\n * Throws a user-friendly error if the token is missing.\n *\n * Mirrors requireMcpUrl from config.ts — single throw site for \"missing token\".\n */\nexport async function requireToken(\n profile: string,\n store: TokenStore,\n): Promise<string> {\n const token = await store.load(profile);\n if (token === null) {\n throw new Error(\n `No refresh token for profile \"${profile}\". Run \\`cleargate join <invite-url>\\` first.`,\n );\n }\n return token;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,eAAsB,aACpB,SACA,OACiB;AACjB,QAAM,QAAQ,MAAM,MAAM,KAAK,OAAO;AACtC,MAAI,UAAU,MAAM;AAClB,UAAM,IAAI;AAAA,MACR,iCAAiC,OAAO;AAAA,IAC1C;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TokenStore } from './token-store.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Asserts a refresh token is present for the given profile.
|
|
5
|
+
* Throws a user-friendly error if the token is missing.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors requireMcpUrl from config.ts — single throw site for "missing token".
|
|
8
|
+
*/
|
|
9
|
+
declare function requireToken(profile: string, store: TokenStore): Promise<string>;
|
|
10
|
+
|
|
11
|
+
export { requireToken };
|