|
1 | | -import { readFile, rm, writeFile } from "node:fs/promises"; |
| 1 | +import { readFile, rm } from "node:fs/promises"; |
2 | 2 | import { createServer } from "node:http"; |
3 | 3 | import { homedir } from "node:os"; |
4 | 4 | import { pathToFileURL } from "node:url"; |
5 | 5 | import * as z from "zod/mini"; |
6 | 6 |
|
7 | 7 | import { refreshToken as baseRefreshToken } from "./clients/auth"; |
| 8 | +import { CREDENTIALS_PATH } from "./config"; |
8 | 9 | import { DEFAULT_PRISMIC_HOST, env } from "./env"; |
9 | | -import { exists } from "./lib/file"; |
| 10 | +import { exists, writeFileRecursive } from "./lib/file"; |
10 | 11 | import { stringify } from "./lib/json"; |
11 | 12 | import { appendTrailingSlash } from "./lib/url"; |
12 | | -import { checkIsSliceMachineProject } from "./project"; |
13 | 13 |
|
14 | | -export const AUTH_FILE_PATH = new URL(".prismic", appendTrailingSlash(pathToFileURL(homedir()))); |
15 | 14 | const LOGIN_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes |
16 | 15 | const PREFERRED_PORT = 5555; |
17 | 16 | const LOGIN_SOURCE = "prismic-cli"; |
18 | 17 |
|
19 | | -const AuthFileSchema = z.looseObject({ |
| 18 | +const CredentialsSchema = z.looseObject({ |
20 | 19 | token: z.optional(z.string().check(z.minLength(1))), |
21 | 20 | host: z.optional(z.string().check(z.minLength(1))), |
22 | | - |
23 | | - // Update notifier state |
24 | | - latestKnownVersion: z.optional(z.string()), |
25 | | - lastUpdateCheckAt: z.optional(z.number()), |
26 | | - |
27 | | - // Backward compatibility with Slice Machine's .prismic |
28 | | - base: z.optional(z.string()), |
29 | | - cookies: z.optional(z.string()), |
30 | 21 | }); |
31 | | -type AuthFile = z.infer<typeof AuthFileSchema>; |
| 22 | +type Credentials = z.infer<typeof CredentialsSchema>; |
32 | 23 |
|
33 | 24 | export async function getToken(): Promise<string | undefined> { |
34 | | - const auth = await readAuthFile(); |
35 | | - const isSliceMachineProject = await checkIsSliceMachineProject(); |
36 | | - if (isSliceMachineProject && auth?.cookies) { |
37 | | - for (const cookie of auth.cookies.split("; ")) { |
38 | | - if (cookie.startsWith("prismic-auth=")) return cookie.replace(/^prismic-auth=/, ""); |
39 | | - } |
40 | | - } |
41 | | - return auth?.token; |
| 25 | + const credentials = await readCredentials(); |
| 26 | + return credentials?.token; |
42 | 27 | } |
43 | 28 |
|
44 | 29 | export async function getHost(): Promise<string> { |
45 | 30 | if (env.PRISMIC_HOST) return env.PRISMIC_HOST; |
46 | | - const auth = await readAuthFile(); |
47 | | - const isSliceMachineProject = await checkIsSliceMachineProject(); |
48 | | - if (isSliceMachineProject && auth?.base) { |
49 | | - try { |
50 | | - return new URL(auth.base).host || DEFAULT_PRISMIC_HOST; |
51 | | - } catch {} |
52 | | - } |
53 | | - return auth?.host || DEFAULT_PRISMIC_HOST; |
| 31 | + const credentials = await readCredentials(); |
| 32 | + return credentials?.host || DEFAULT_PRISMIC_HOST; |
54 | 33 | } |
55 | 34 |
|
56 | 35 | export async function refreshToken(): Promise<string | undefined> { |
57 | 36 | const token = await getToken(); |
58 | 37 | if (!token) return; |
59 | 38 | const host = await getHost(); |
60 | 39 | const newToken = await baseRefreshToken(token, { host }); |
61 | | - await saveAuthFile({ token: newToken, host }); |
| 40 | + await saveCredentials({ token: newToken, host }); |
62 | 41 | return newToken; |
63 | 42 | } |
64 | 43 |
|
65 | 44 | export async function logout(): Promise<boolean> { |
66 | | - const authFileExists = await exists(AUTH_FILE_PATH); |
67 | | - if (!authFileExists) return true; |
| 45 | + const credentialsExist = await exists(CREDENTIALS_PATH); |
| 46 | + if (!credentialsExist) return true; |
68 | 47 |
|
69 | 48 | try { |
70 | | - await rm(AUTH_FILE_PATH, { force: true }); |
| 49 | + await rm(CREDENTIALS_PATH, { force: true }); |
71 | 50 | return true; |
72 | 51 | } catch { |
73 | 52 | return false; |
74 | 53 | } |
75 | 54 | } |
76 | 55 |
|
77 | | -export async function readAuthFile(): Promise<AuthFile | undefined> { |
| 56 | +async function readCredentials(): Promise<Credentials | undefined> { |
78 | 57 | try { |
79 | | - const contents = await readFile(AUTH_FILE_PATH, "utf-8"); |
| 58 | + const contents = await readFile(CREDENTIALS_PATH, "utf-8"); |
80 | 59 | const json = JSON.parse(contents); |
81 | | - return z.parse(AuthFileSchema, json); |
| 60 | + return z.parse(CredentialsSchema, json); |
82 | 61 | } catch { |
83 | 62 | return undefined; |
84 | 63 | } |
85 | 64 | } |
86 | 65 |
|
87 | | -export async function saveAuthFile(auth: AuthFile): Promise<void> { |
88 | | - const existingAuthFile = await readAuthFile(); |
89 | | - const newAuthFile: AuthFile = { ...existingAuthFile, ...auth }; |
90 | | - const isSliceMachineProject = await checkIsSliceMachineProject(); |
91 | | - if (isSliceMachineProject) { |
92 | | - if (newAuthFile.host) newAuthFile.base = `https://${newAuthFile.host}/`; |
93 | | - if (newAuthFile.token) |
94 | | - newAuthFile.cookies = `prismic-auth=${newAuthFile.token}; Path=/; SameSite=none; SESSION=fake_session; Path=/; SameSite=none`; |
95 | | - } |
96 | | - await writeFile(AUTH_FILE_PATH, stringify(newAuthFile)); |
| 66 | +async function saveCredentials(credentials: Credentials): Promise<void> { |
| 67 | + await writeFileRecursive(CREDENTIALS_PATH, stringify(credentials)); |
97 | 68 | } |
98 | 69 |
|
99 | 70 | export async function createLoginSession(options?: { |
@@ -139,7 +110,7 @@ export async function createLoginSession(options?: { |
139 | 110 | return; |
140 | 111 | } |
141 | 112 |
|
142 | | - await saveAuthFile({ token, host }); |
| 113 | + await saveCredentials({ token, host }); |
143 | 114 |
|
144 | 115 | res.writeHead(200, { |
145 | 116 | "Access-Control-Allow-Origin": corsOrigin, |
@@ -197,6 +168,33 @@ export async function createLoginSession(options?: { |
197 | 168 | }); |
198 | 169 | } |
199 | 170 |
|
| 171 | +const LEGACY_AUTH_FILE_PATH = new URL( |
| 172 | + ".prismic", |
| 173 | + appendTrailingSlash(pathToFileURL(homedir())), |
| 174 | +); |
| 175 | + |
| 176 | +export async function cleanupLegacyAuthFile(): Promise<void> { |
| 177 | + let contents: string; |
| 178 | + try { |
| 179 | + contents = await readFile(LEGACY_AUTH_FILE_PATH, "utf-8"); |
| 180 | + } catch { |
| 181 | + return; |
| 182 | + } |
| 183 | + |
| 184 | + try { |
| 185 | + const json = JSON.parse(contents); |
| 186 | + if (!json || (json.latestKnownVersion === undefined && json.lastUpdateCheckAt === undefined)) { |
| 187 | + return; |
| 188 | + } |
| 189 | + } catch { |
| 190 | + return; |
| 191 | + } |
| 192 | + |
| 193 | + try { |
| 194 | + await rm(LEGACY_AUTH_FILE_PATH, { force: true }); |
| 195 | + } catch {} |
| 196 | +} |
| 197 | + |
200 | 198 | async function buildLoginUrl(host: string, port: number): Promise<URL> { |
201 | 199 | const url = new URL("dashboard/cli/login", `https://${host}/`); |
202 | 200 | url.searchParams.set("source", LOGIN_SOURCE); |
|
0 commit comments