@amerged/ohmyhost-mcp 0.1.10
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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +11 -0
- package/THIRD_PARTY_NOTICES.md +474 -0
- package/dist/chunk-7MV2EVXV.js +30779 -0
- package/dist/index.js +9 -0
- package/dist/stdio-main.js +5607 -0
- package/package.json +32 -0
- package/src/apps/mcp/index.ts +30 -0
- package/src/apps/mcp/local-client.ts +891 -0
- package/src/apps/mcp/local-server.ts +1512 -0
- package/src/apps/mcp/stdio-main.ts +26 -0
- package/src/apps/product-cli/bin.ts +24 -0
- package/src/apps/product-cli/cli.ts +2271 -0
- package/src/apps/product-cli/command.ts +1811 -0
- package/src/apps/product-cli/composition.ts +823 -0
- package/src/apps/product-cli/credential-store.ts +149 -0
- package/src/apps/product-cli/device-token-expiry.ts +89 -0
- package/src/apps/product-cli/file-credential-store.ts +166 -0
- package/src/apps/product-cli/index.ts +23 -0
- package/src/apps/product-cli/product-api.ts +1082 -0
- package/src/apps/product-cli/project-link-store.ts +30 -0
- package/src/apps/product-cli/psql-session.ts +29 -0
- package/src/apps/product-cli/repository-init.ts +1622 -0
- package/src/apps/product-cli/runtime-contract.ts +1747 -0
- package/src/apps/product-cli/user-token-file.ts +214 -0
- package/src/apps/product-cli/workspace.ts +92 -0
- package/src/packages/agent-skills/generated-skill-resources.ts +147 -0
- package/src/packages/agent-skills/index.ts +37 -0
- package/src/packages/contracts/application-root.ts +80 -0
- package/src/packages/contracts/billing.ts +172 -0
- package/src/packages/contracts/credit-pricing.ts +214 -0
- package/src/packages/contracts/database-access.ts +257 -0
- package/src/packages/contracts/database-compute.ts +72 -0
- package/src/packages/contracts/database-migration-admission.ts +235 -0
- package/src/packages/contracts/feedback.ts +96 -0
- package/src/packages/contracts/framework-admission.ts +280 -0
- package/src/packages/contracts/framework-family.ts +18 -0
- package/src/packages/contracts/functions.ts +76 -0
- package/src/packages/contracts/index.ts +245 -0
- package/src/packages/contracts/operation-deployment-result.ts +165 -0
- package/src/packages/contracts/operation-failure.ts +150 -0
- package/src/packages/contracts/platform-origins.ts +5 -0
- package/src/packages/contracts/project-context.ts +59 -0
- package/src/packages/contracts/project-exports.ts +102 -0
- package/src/packages/contracts/user-api-keys.ts +98 -0
- package/src/packages/sdk-ts/generated/client/client.gen.ts +277 -0
- package/src/packages/sdk-ts/generated/client/index.ts +27 -0
- package/src/packages/sdk-ts/generated/client/types.gen.ts +217 -0
- package/src/packages/sdk-ts/generated/client/utils.gen.ts +316 -0
- package/src/packages/sdk-ts/generated/client.gen.ts +24 -0
- package/src/packages/sdk-ts/generated/core/auth.gen.ts +48 -0
- package/src/packages/sdk-ts/generated/core/bodySerializer.gen.ts +82 -0
- package/src/packages/sdk-ts/generated/core/params.gen.ts +178 -0
- package/src/packages/sdk-ts/generated/core/pathSerializer.gen.ts +171 -0
- package/src/packages/sdk-ts/generated/core/queryKeySerializer.gen.ts +117 -0
- package/src/packages/sdk-ts/generated/core/serverSentEvents.gen.ts +242 -0
- package/src/packages/sdk-ts/generated/core/types.gen.ts +114 -0
- package/src/packages/sdk-ts/generated/core/utils.gen.ts +140 -0
- package/src/packages/sdk-ts/generated/index.ts +578 -0
- package/src/packages/sdk-ts/generated/sdk.gen.ts +3440 -0
- package/src/packages/sdk-ts/generated/types.gen.ts +5790 -0
- package/src/packages/sdk-ts/index.ts +7 -0
- package/src/packages/workos-auth-contracts/device-flow.ts +271 -0
- package/src/packages/workos-auth-contracts/index.ts +8 -0
- package/src/packages/workos-auth-contracts/management-rpc.ts +379 -0
- package/src/packages/workos-auth-contracts/organizations.ts +113 -0
- package/src/packages/workos-auth-contracts/parsing.ts +75 -0
- package/src/packages/workos-auth-contracts/session-status.ts +53 -0
- package/src/packages/workos-auth-contracts/tokens.ts +216 -0
- package/src/packages/workos-auth-contracts/user-api-keys.ts +85 -0
- package/src/packages/workos-auth-contracts/webhooks.ts +127 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export interface StoredCredential {
|
|
2
|
+
readonly accessToken: string;
|
|
3
|
+
readonly refreshToken: string;
|
|
4
|
+
readonly expiresAt?: string;
|
|
5
|
+
/** The workspace this credential is scoped to, as the provider names it. */
|
|
6
|
+
readonly workosOrganizationId?: string;
|
|
7
|
+
/** The same workspace in platform terms, kept for display. */
|
|
8
|
+
readonly organizationId?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CurrentCredential extends StoredCredential {
|
|
12
|
+
readonly expiresAt: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CredentialStore {
|
|
16
|
+
read(): Promise<StoredCredential | undefined>;
|
|
17
|
+
replace(credential: CurrentCredential): Promise<void>;
|
|
18
|
+
clear(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface KeyringEntry {
|
|
22
|
+
setPassword(password: string): Promise<void>;
|
|
23
|
+
getPassword(): Promise<string | undefined>;
|
|
24
|
+
deleteCredential(): Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type KeyringEntryFactory = (account: string) => Promise<KeyringEntry>;
|
|
28
|
+
|
|
29
|
+
export class CredentialStoreUnavailableError extends Error {
|
|
30
|
+
public constructor() {
|
|
31
|
+
super("The credential store is unavailable.");
|
|
32
|
+
this.name = "CredentialStoreUnavailableError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class CredentialStoreIntegrityError extends Error {
|
|
37
|
+
public constructor() {
|
|
38
|
+
super("The credential store integrity check failed.");
|
|
39
|
+
this.name = "CredentialStoreIntegrityError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const CREDENTIAL_ACCOUNT = "credential";
|
|
44
|
+
|
|
45
|
+
export class NativeNapiKeyringCredentialStore implements CredentialStore {
|
|
46
|
+
private readonly createEntry: KeyringEntryFactory;
|
|
47
|
+
|
|
48
|
+
public constructor(
|
|
49
|
+
createEntry: KeyringEntryFactory | undefined = undefined,
|
|
50
|
+
serviceName = "ohmyhost",
|
|
51
|
+
) {
|
|
52
|
+
this.createEntry = createEntry ?? createNativeEntryFactory(serviceName);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public async read(): Promise<StoredCredential | undefined> {
|
|
56
|
+
const serialized = await this.entry().then((entry) => entry.getPassword());
|
|
57
|
+
if (serialized === undefined) return undefined;
|
|
58
|
+
return parseVerifiedCredential(serialized);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public async replace(credential: CurrentCredential): Promise<void> {
|
|
62
|
+
const entry = await this.entry();
|
|
63
|
+
const serialized = serializeCredential(credential);
|
|
64
|
+
await entry.setPassword(serialized);
|
|
65
|
+
parseVerifiedCredential(await entry.getPassword());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public async clear(): Promise<void> {
|
|
69
|
+
const entry = await this.entry();
|
|
70
|
+
await entry.deleteCredential();
|
|
71
|
+
if ((await entry.getPassword()) !== undefined) throw new CredentialStoreIntegrityError();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private entry(): Promise<KeyringEntry> {
|
|
75
|
+
return this.createEntry(CREDENTIAL_ACCOUNT);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const parseVerifiedCredential = (serialized: string | undefined): StoredCredential => {
|
|
80
|
+
try {
|
|
81
|
+
return parseCredential(serialized);
|
|
82
|
+
} catch {
|
|
83
|
+
throw new CredentialStoreIntegrityError();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const createNativeEntryFactory = (serviceName: string): KeyringEntryFactory => {
|
|
88
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(serviceName)) {
|
|
89
|
+
throw new CredentialStoreUnavailableError();
|
|
90
|
+
}
|
|
91
|
+
return async (account) => {
|
|
92
|
+
const { AsyncEntry } = await import("@napi-rs/keyring");
|
|
93
|
+
let entry: InstanceType<typeof AsyncEntry>;
|
|
94
|
+
try {
|
|
95
|
+
entry = new AsyncEntry(serviceName, account);
|
|
96
|
+
} catch {
|
|
97
|
+
throw new CredentialStoreUnavailableError();
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
setPassword: (password) => runNativeOperation(() => entry.setPassword(password)),
|
|
101
|
+
getPassword: async () => (await runNativeOperation(() => entry.getPassword())) ?? undefined,
|
|
102
|
+
deleteCredential: () => runNativeOperation(() => entry.deleteCredential()),
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const runNativeOperation = async <Result>(operation: () => Promise<Result>): Promise<Result> => {
|
|
108
|
+
try {
|
|
109
|
+
return await operation();
|
|
110
|
+
} catch {
|
|
111
|
+
throw new CredentialStoreUnavailableError();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const serializeCredential = (credential: CurrentCredential): string =>
|
|
116
|
+
JSON.stringify(credential);
|
|
117
|
+
|
|
118
|
+
export const parseCredential = (serialized: string | undefined): StoredCredential => {
|
|
119
|
+
if (serialized === undefined) throw new Error("Credential entry is missing");
|
|
120
|
+
const value: unknown = JSON.parse(serialized);
|
|
121
|
+
if (
|
|
122
|
+
typeof value !== "object" ||
|
|
123
|
+
value === null ||
|
|
124
|
+
!("accessToken" in value) ||
|
|
125
|
+
!("refreshToken" in value) ||
|
|
126
|
+
typeof value.accessToken !== "string" ||
|
|
127
|
+
typeof value.refreshToken !== "string" ||
|
|
128
|
+
value.accessToken.length === 0 ||
|
|
129
|
+
value.refreshToken.length === 0 ||
|
|
130
|
+
("expiresAt" in value && typeof value.expiresAt !== "string") ||
|
|
131
|
+
("workosOrganizationId" in value &&
|
|
132
|
+
(typeof value.workosOrganizationId !== "string" ||
|
|
133
|
+
!/^org_[A-Za-z0-9_]{1,120}$/u.test(value.workosOrganizationId))) ||
|
|
134
|
+
("organizationId" in value &&
|
|
135
|
+
(typeof value.organizationId !== "string" ||
|
|
136
|
+
!/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(value.organizationId)))
|
|
137
|
+
) {
|
|
138
|
+
throw new Error("Credential entry is invalid");
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
accessToken: value.accessToken,
|
|
142
|
+
refreshToken: value.refreshToken,
|
|
143
|
+
...("expiresAt" in value ? { expiresAt: value.expiresAt as string } : {}),
|
|
144
|
+
...("workosOrganizationId" in value
|
|
145
|
+
? { workosOrganizationId: value.workosOrganizationId as string }
|
|
146
|
+
: {}),
|
|
147
|
+
...("organizationId" in value ? { organizationId: value.organizationId as string } : {}),
|
|
148
|
+
};
|
|
149
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export class DeviceTokenExpiryError extends Error {
|
|
2
|
+
public constructor() {
|
|
3
|
+
super("The Device Flow token expiry is invalid.");
|
|
4
|
+
this.name = "DeviceTokenExpiryError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const invalidExpiry = (): DeviceTokenExpiryError => new DeviceTokenExpiryError();
|
|
9
|
+
|
|
10
|
+
const decodeBase64Url = (value: string): Uint8Array => {
|
|
11
|
+
if (value.length === 0 || !/^[A-Za-z0-9_-]+$/u.test(value) || value.length % 4 === 1) {
|
|
12
|
+
throw invalidExpiry();
|
|
13
|
+
}
|
|
14
|
+
const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
15
|
+
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "=");
|
|
16
|
+
let binary: string;
|
|
17
|
+
try {
|
|
18
|
+
binary = atob(padded);
|
|
19
|
+
} catch {
|
|
20
|
+
throw invalidExpiry();
|
|
21
|
+
}
|
|
22
|
+
const canonical = btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
23
|
+
if (canonical !== value) throw invalidExpiry();
|
|
24
|
+
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const parseJsonSegment = (value: string): Readonly<Record<string, unknown>> => {
|
|
28
|
+
let parsed: unknown;
|
|
29
|
+
try {
|
|
30
|
+
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(decodeBase64Url(value)));
|
|
31
|
+
} catch {
|
|
32
|
+
throw invalidExpiry();
|
|
33
|
+
}
|
|
34
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
35
|
+
throw invalidExpiry();
|
|
36
|
+
}
|
|
37
|
+
return parsed as Readonly<Record<string, unknown>>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const readJwtExpirySeconds = (accessToken: string): number => {
|
|
41
|
+
const segments = accessToken.split(".");
|
|
42
|
+
if (segments.length !== 3) throw invalidExpiry();
|
|
43
|
+
const [header, payload, signature] = segments;
|
|
44
|
+
if (header === undefined || payload === undefined || signature === undefined) {
|
|
45
|
+
throw invalidExpiry();
|
|
46
|
+
}
|
|
47
|
+
parseJsonSegment(header);
|
|
48
|
+
decodeBase64Url(signature);
|
|
49
|
+
const claims = parseJsonSegment(payload);
|
|
50
|
+
const expiry = claims["exp"];
|
|
51
|
+
if (
|
|
52
|
+
!Object.hasOwn(claims, "exp") ||
|
|
53
|
+
typeof expiry !== "number" ||
|
|
54
|
+
!Number.isSafeInteger(expiry) ||
|
|
55
|
+
expiry <= 0 ||
|
|
56
|
+
expiry > 8_640_000_000_000
|
|
57
|
+
) {
|
|
58
|
+
throw invalidExpiry();
|
|
59
|
+
}
|
|
60
|
+
return expiry;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const resolveDeviceTokenExpiresAt = (
|
|
64
|
+
accessToken: string,
|
|
65
|
+
expiresInSeconds: number | undefined,
|
|
66
|
+
now: Date,
|
|
67
|
+
): string => {
|
|
68
|
+
const nowMilliseconds = now.getTime();
|
|
69
|
+
if (!Number.isFinite(nowMilliseconds)) throw invalidExpiry();
|
|
70
|
+
|
|
71
|
+
const jwtExpirySeconds = readJwtExpirySeconds(accessToken);
|
|
72
|
+
const jwtExpiryMilliseconds = jwtExpirySeconds * 1_000;
|
|
73
|
+
if (!Number.isSafeInteger(jwtExpiryMilliseconds) || jwtExpiryMilliseconds <= nowMilliseconds) {
|
|
74
|
+
throw invalidExpiry();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (expiresInSeconds !== undefined) {
|
|
78
|
+
if (!Number.isSafeInteger(expiresInSeconds) || expiresInSeconds <= 0) throw invalidExpiry();
|
|
79
|
+
const relativeExpirySeconds = Math.floor(nowMilliseconds / 1_000) + expiresInSeconds;
|
|
80
|
+
if (
|
|
81
|
+
!Number.isSafeInteger(relativeExpirySeconds) ||
|
|
82
|
+
relativeExpirySeconds !== jwtExpirySeconds
|
|
83
|
+
) {
|
|
84
|
+
throw invalidExpiry();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return new Date(jwtExpiryMilliseconds).toISOString();
|
|
89
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { constants, lstat, mkdir, open, rename, unlink } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CredentialStoreUnavailableError,
|
|
6
|
+
parseCredential,
|
|
7
|
+
serializeCredential,
|
|
8
|
+
type CredentialStore,
|
|
9
|
+
type CurrentCredential,
|
|
10
|
+
type StoredCredential,
|
|
11
|
+
} from "./credential-store.js";
|
|
12
|
+
|
|
13
|
+
const DIRECTORY_MODE = 0o700;
|
|
14
|
+
const FILE_MODE = 0o600;
|
|
15
|
+
|
|
16
|
+
export class OwnerOnlyFileCredentialStore implements CredentialStore {
|
|
17
|
+
readonly #credentialPath: string;
|
|
18
|
+
readonly #temporaryPath: string;
|
|
19
|
+
|
|
20
|
+
public constructor(
|
|
21
|
+
private readonly directory: string,
|
|
22
|
+
private readonly ownerId: number,
|
|
23
|
+
) {
|
|
24
|
+
this.#credentialPath = join(directory, "credentials.json");
|
|
25
|
+
this.#temporaryPath = join(directory, ".credentials.next");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public async read(): Promise<StoredCredential | undefined> {
|
|
29
|
+
try {
|
|
30
|
+
await this.ensureDirectory();
|
|
31
|
+
await this.recoverTemporary();
|
|
32
|
+
if (!(await this.validateFile())) return undefined;
|
|
33
|
+
const handle = await open(this.#credentialPath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
34
|
+
try {
|
|
35
|
+
const status = await handle.stat();
|
|
36
|
+
this.assertOwnedStatus(status, FILE_MODE, false);
|
|
37
|
+
return parseCredential(await handle.readFile("utf8"));
|
|
38
|
+
} finally {
|
|
39
|
+
await handle.close();
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
throw new CredentialStoreUnavailableError();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public async replace(credential: CurrentCredential): Promise<void> {
|
|
47
|
+
try {
|
|
48
|
+
await this.ensureDirectory();
|
|
49
|
+
await this.recoverTemporary();
|
|
50
|
+
await this.validateFile();
|
|
51
|
+
const handle = await open(
|
|
52
|
+
this.#temporaryPath,
|
|
53
|
+
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
|
|
54
|
+
FILE_MODE,
|
|
55
|
+
);
|
|
56
|
+
try {
|
|
57
|
+
await handle.writeFile(serializeCredential(credential), "utf8");
|
|
58
|
+
await handle.sync();
|
|
59
|
+
} finally {
|
|
60
|
+
await handle.close();
|
|
61
|
+
}
|
|
62
|
+
await this.validateOwnedPath(this.#temporaryPath, FILE_MODE, false);
|
|
63
|
+
await rename(this.#temporaryPath, this.#credentialPath);
|
|
64
|
+
await this.syncDirectory();
|
|
65
|
+
await this.validateOwnedPath(this.#credentialPath, FILE_MODE, false);
|
|
66
|
+
} catch {
|
|
67
|
+
await unlink(this.#temporaryPath).catch(() => undefined);
|
|
68
|
+
throw new CredentialStoreUnavailableError();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public async clear(): Promise<void> {
|
|
73
|
+
try {
|
|
74
|
+
await this.ensureDirectory();
|
|
75
|
+
let failed = false;
|
|
76
|
+
for (const path of [this.#credentialPath, this.#temporaryPath]) {
|
|
77
|
+
try {
|
|
78
|
+
await unlink(path);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (!isMissing(error)) failed = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
await this.syncDirectory();
|
|
85
|
+
} catch {
|
|
86
|
+
failed = true;
|
|
87
|
+
}
|
|
88
|
+
for (const path of [this.#credentialPath, this.#temporaryPath]) {
|
|
89
|
+
try {
|
|
90
|
+
await lstat(path);
|
|
91
|
+
failed = true;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (!isMissing(error)) failed = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (failed) throw new Error("Credential artifact cleanup failed");
|
|
97
|
+
} catch {
|
|
98
|
+
throw new CredentialStoreUnavailableError();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private async ensureDirectory(): Promise<void> {
|
|
103
|
+
await mkdir(this.directory, { mode: DIRECTORY_MODE, recursive: true });
|
|
104
|
+
await this.validateOwnedPath(this.directory, DIRECTORY_MODE, true);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private async recoverTemporary(): Promise<void> {
|
|
108
|
+
try {
|
|
109
|
+
const status = await lstat(this.#temporaryPath);
|
|
110
|
+
const invalid =
|
|
111
|
+
status.isSymbolicLink() ||
|
|
112
|
+
!status.isFile() ||
|
|
113
|
+
status.uid !== this.ownerId ||
|
|
114
|
+
(status.mode & 0o777) !== FILE_MODE;
|
|
115
|
+
await unlink(this.#temporaryPath);
|
|
116
|
+
await this.syncDirectory();
|
|
117
|
+
if (invalid) throw new Error("Temporary credential artifact was invalid");
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (!isMissing(error)) throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private async validateFile(): Promise<boolean> {
|
|
124
|
+
try {
|
|
125
|
+
await this.validateOwnedPath(this.#credentialPath, FILE_MODE, false);
|
|
126
|
+
return true;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (isMissing(error)) return false;
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private async validateOwnedPath(path: string, mode: number, directory: boolean): Promise<void> {
|
|
134
|
+
const status = await lstat(path);
|
|
135
|
+
if (status.isSymbolicLink()) {
|
|
136
|
+
throw new Error("Credential path ownership or mode is invalid");
|
|
137
|
+
}
|
|
138
|
+
this.assertOwnedStatus(status, mode, directory);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private assertOwnedStatus(
|
|
142
|
+
status: Awaited<ReturnType<typeof lstat>>,
|
|
143
|
+
mode: number,
|
|
144
|
+
directory: boolean,
|
|
145
|
+
): void {
|
|
146
|
+
if (
|
|
147
|
+
(directory ? !status.isDirectory() : !status.isFile()) ||
|
|
148
|
+
status.uid !== this.ownerId ||
|
|
149
|
+
(Number(status.mode) & 0o777) !== mode
|
|
150
|
+
) {
|
|
151
|
+
throw new Error("Credential path ownership or mode is invalid");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private async syncDirectory(): Promise<void> {
|
|
156
|
+
const handle = await open(dirname(this.#credentialPath), constants.O_RDONLY);
|
|
157
|
+
try {
|
|
158
|
+
await handle.sync();
|
|
159
|
+
} finally {
|
|
160
|
+
await handle.close();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const isMissing = (error: unknown): boolean =>
|
|
166
|
+
typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export * from "./cli.js";
|
|
2
|
+
export * from "./command.js";
|
|
3
|
+
export * from "./composition.js";
|
|
4
|
+
export * from "./credential-store.js";
|
|
5
|
+
export * from "./file-credential-store.js";
|
|
6
|
+
export * from "./product-api.js";
|
|
7
|
+
export * from "./project-link-store.js";
|
|
8
|
+
export * from "./repository-init.js";
|
|
9
|
+
export * from "./workspace.js";
|
|
10
|
+
export {
|
|
11
|
+
parseAccountProfile,
|
|
12
|
+
parseCurrentIdentity,
|
|
13
|
+
parseDevAccessTicket,
|
|
14
|
+
parseCloudflareDnsAuthorization,
|
|
15
|
+
parseCloudflareDnsAuthorizationStatus,
|
|
16
|
+
parsePaidDomain,
|
|
17
|
+
parsePaidDomainPlan,
|
|
18
|
+
parseOperation,
|
|
19
|
+
parseOperationEvent,
|
|
20
|
+
parseSafeProblem,
|
|
21
|
+
} from "./runtime-contract.js";
|
|
22
|
+
|
|
23
|
+
export * from "./user-token-file.js";
|