@danypops/tickets 0.9.0 → 0.10.0
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/package.json +1 -1
- package/src/auth/token-store.ts +13 -10
- package/src/cli/index.ts +17 -15
- package/src/config/config.ts +1 -1
package/package.json
CHANGED
package/src/auth/token-store.ts
CHANGED
|
@@ -5,9 +5,14 @@
|
|
|
5
5
|
* *to* GitHub/GitLab/Jira on the user's behalf. Same security posture as
|
|
6
6
|
* vehicle-server's ensureAuthToken: 0700 directory, 0600 files, atomic write.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
9
|
+
import { mkdir } from "node:fs/promises";
|
|
9
10
|
import { homedir } from "node:os";
|
|
10
11
|
import { dirname, join } from "node:path";
|
|
12
|
+
import { createAtomicJsonWriter } from "@danypops/vehicle-core";
|
|
13
|
+
import { createNodeAtomicJsonFsAdapter } from "@danypops/vehicle-server/atomic-json";
|
|
14
|
+
|
|
15
|
+
const atomicJson = createAtomicJsonWriter({ fs: createNodeAtomicJsonFsAdapter() });
|
|
11
16
|
|
|
12
17
|
export interface StoredToken {
|
|
13
18
|
accessToken: string;
|
|
@@ -35,21 +40,19 @@ function tokenPath(backend: string, opts: TokenStoreEnv = {}): string {
|
|
|
35
40
|
return join(tokenStoreDir(opts), `${backend}.json`);
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
export function saveToken(backend: string, token: StoredToken, opts: TokenStoreEnv = {}): void {
|
|
43
|
+
export async function saveToken(backend: string, token: StoredToken, opts: TokenStoreEnv = {}): Promise<void> {
|
|
39
44
|
const path = tokenPath(backend, opts);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
writeFileSync(temp, `${JSON.stringify(token, null, 2)}\n`, { mode: 0o600 });
|
|
43
|
-
renameSync(temp, path);
|
|
45
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
46
|
+
await atomicJson.write(path, token, { mode: 0o600, pretty: true, trailingNewline: true });
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
export function loadToken(backend: string, opts: TokenStoreEnv = {}): StoredToken | undefined {
|
|
49
|
+
export async function loadToken(backend: string, opts: TokenStoreEnv = {}): Promise<StoredToken | undefined> {
|
|
47
50
|
const path = tokenPath(backend, opts);
|
|
48
|
-
if (!existsSync(path)) return undefined;
|
|
49
|
-
chmodSync(path, 0o600);
|
|
50
51
|
try {
|
|
51
|
-
return
|
|
52
|
+
return (await atomicJson.read(path)) as StoredToken | undefined;
|
|
52
53
|
} catch {
|
|
54
|
+
// A corrupt/malformed token file is treated the same as no token --
|
|
55
|
+
// the caller re-authenticates rather than crashing on a parse error.
|
|
53
56
|
return undefined;
|
|
54
57
|
}
|
|
55
58
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -550,7 +550,7 @@ auth
|
|
|
550
550
|
if (type === "github" && opts.ghCli !== undefined) {
|
|
551
551
|
const result = await readGhCliToken(opts.ghCli === true ? undefined : opts.ghCli);
|
|
552
552
|
if (!result.ok) throw new Error(result.reason);
|
|
553
|
-
saveToken(opts.backend, { accessToken: result.token });
|
|
553
|
+
await saveToken(opts.backend, { accessToken: result.token });
|
|
554
554
|
printJson({
|
|
555
555
|
backend: opts.backend,
|
|
556
556
|
status: "authorized",
|
|
@@ -577,7 +577,7 @@ auth
|
|
|
577
577
|
}
|
|
578
578
|
},
|
|
579
579
|
});
|
|
580
|
-
saveToken(opts.backend, {
|
|
580
|
+
await saveToken(opts.backend, {
|
|
581
581
|
accessToken: token.accessToken,
|
|
582
582
|
refreshToken: token.refreshToken,
|
|
583
583
|
expiresAt: token.expiresAt,
|
|
@@ -600,7 +600,7 @@ auth
|
|
|
600
600
|
}
|
|
601
601
|
},
|
|
602
602
|
});
|
|
603
|
-
saveToken(opts.backend, {
|
|
603
|
+
await saveToken(opts.backend, {
|
|
604
604
|
accessToken: token.accessToken,
|
|
605
605
|
refreshToken: token.refreshToken,
|
|
606
606
|
expiresAt: token.expiresAt,
|
|
@@ -627,7 +627,7 @@ auth
|
|
|
627
627
|
}
|
|
628
628
|
},
|
|
629
629
|
});
|
|
630
|
-
saveToken(opts.backend, {
|
|
630
|
+
await saveToken(opts.backend, {
|
|
631
631
|
accessToken: token.accessToken,
|
|
632
632
|
refreshToken: token.refreshToken,
|
|
633
633
|
expiresAt: token.expiresAt,
|
|
@@ -661,7 +661,7 @@ auth
|
|
|
661
661
|
process.exitCode = 1;
|
|
662
662
|
return;
|
|
663
663
|
}
|
|
664
|
-
saveToken(backend, { accessToken: value });
|
|
664
|
+
await saveToken(backend, { accessToken: value });
|
|
665
665
|
printJson({
|
|
666
666
|
backend,
|
|
667
667
|
status: "stored",
|
|
@@ -672,18 +672,20 @@ auth
|
|
|
672
672
|
auth
|
|
673
673
|
.command("status")
|
|
674
674
|
.description("list backends with a locally stored delegated token")
|
|
675
|
-
.action(() => {
|
|
675
|
+
.action(async () => {
|
|
676
676
|
const backends = listStoredBackends();
|
|
677
677
|
printJson(
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
678
|
+
await Promise.all(
|
|
679
|
+
backends.map(async (backend) => {
|
|
680
|
+
const token = await loadToken(backend);
|
|
681
|
+
return {
|
|
682
|
+
backend,
|
|
683
|
+
fresh: token ? isTokenFresh(token) : false,
|
|
684
|
+
expiresAt: token?.expiresAt ?? null,
|
|
685
|
+
scope: token?.scope ?? null,
|
|
686
|
+
};
|
|
687
|
+
}),
|
|
688
|
+
),
|
|
687
689
|
);
|
|
688
690
|
});
|
|
689
691
|
|
package/src/config/config.ts
CHANGED
|
@@ -103,7 +103,7 @@ export async function preferredAuth(
|
|
|
103
103
|
const fromEnigma = await tryEnigma(name, { env, token: env.ENIGMA_CLIENT_TOKEN });
|
|
104
104
|
if (fromEnigma) return { token: fromEnigma.accessToken, oauth: true, extra: fromEnigma.extra };
|
|
105
105
|
|
|
106
|
-
const stored = loadToken(name, { env });
|
|
106
|
+
const stored = await loadToken(name, { env });
|
|
107
107
|
if (stored && isTokenFresh(stored)) {
|
|
108
108
|
return { token: stored.accessToken, oauth: true, extra: stored.extra };
|
|
109
109
|
}
|