@prisma/cli 3.0.0-dev.50.1 → 3.0.0-dev.52.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/dist/lib/auth/login.js +88 -7
- package/package.json +1 -1
package/dist/lib/auth/login.js
CHANGED
|
@@ -4,6 +4,7 @@ import open from "open";
|
|
|
4
4
|
import { AuthError, createManagementApiSdk } from "@prisma/management-api-sdk";
|
|
5
5
|
import events from "node:events";
|
|
6
6
|
import http from "node:http";
|
|
7
|
+
import readline from "node:readline/promises";
|
|
7
8
|
//#region src/lib/auth/login.ts
|
|
8
9
|
var AuthError$1 = class extends Error {
|
|
9
10
|
constructor(message) {
|
|
@@ -14,11 +15,15 @@ var AuthError$1 = class extends Error {
|
|
|
14
15
|
async function login(options = {}) {
|
|
15
16
|
const hostname = options.hostname ?? "localhost";
|
|
16
17
|
const port = options.port ?? 0;
|
|
18
|
+
const input = options.input ?? process.stdin;
|
|
19
|
+
const output = options.output ?? process.stderr;
|
|
20
|
+
const interactive = input.isTTY === true;
|
|
17
21
|
const server = http.createServer();
|
|
18
22
|
server.listen({
|
|
19
23
|
host: hostname,
|
|
20
24
|
port
|
|
21
25
|
});
|
|
26
|
+
const pasteAbort = new AbortController();
|
|
22
27
|
try {
|
|
23
28
|
const state = new LoginState({
|
|
24
29
|
hostname,
|
|
@@ -28,9 +33,21 @@ async function login(options = {}) {
|
|
|
28
33
|
apiBaseUrl: options.apiBaseUrl,
|
|
29
34
|
authBaseUrl: options.authBaseUrl,
|
|
30
35
|
openUrl: options.openUrl,
|
|
31
|
-
env: options.env
|
|
36
|
+
env: options.env,
|
|
37
|
+
output
|
|
32
38
|
});
|
|
33
|
-
|
|
39
|
+
let completed = false;
|
|
40
|
+
let completion;
|
|
41
|
+
const completeOnce = (url) => {
|
|
42
|
+
if (!completion) completion = state.handleCallback(url).then(() => {
|
|
43
|
+
completed = true;
|
|
44
|
+
}, (error) => {
|
|
45
|
+
completion = void 0;
|
|
46
|
+
throw error;
|
|
47
|
+
});
|
|
48
|
+
return completion;
|
|
49
|
+
};
|
|
50
|
+
const httpResult = new Promise((resolve, reject) => {
|
|
34
51
|
server.on("request", async (req, res) => {
|
|
35
52
|
const url = new URL(`http://${state.host}${req.url}`);
|
|
36
53
|
if (url.pathname !== "/auth/callback") {
|
|
@@ -38,8 +55,14 @@ async function login(options = {}) {
|
|
|
38
55
|
res.end("Not found");
|
|
39
56
|
return;
|
|
40
57
|
}
|
|
58
|
+
if (completed) {
|
|
59
|
+
const workspaceName = await state.resolveWorkspaceName();
|
|
60
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
61
|
+
res.end(renderSuccessPage(workspaceName));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
41
64
|
try {
|
|
42
|
-
await
|
|
65
|
+
await completeOnce(url);
|
|
43
66
|
} catch (error) {
|
|
44
67
|
res.statusCode = 400;
|
|
45
68
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -53,18 +76,65 @@ async function login(options = {}) {
|
|
|
53
76
|
resolve();
|
|
54
77
|
});
|
|
55
78
|
});
|
|
56
|
-
await state.openLoginPage();
|
|
57
|
-
|
|
79
|
+
await state.openLoginPage(interactive);
|
|
80
|
+
if (interactive) {
|
|
81
|
+
const pasteResult = consumePastedCallback({
|
|
82
|
+
input,
|
|
83
|
+
output,
|
|
84
|
+
signal: pasteAbort.signal,
|
|
85
|
+
complete: completeOnce
|
|
86
|
+
});
|
|
87
|
+
await Promise.race([httpResult, pasteResult]);
|
|
88
|
+
} else await httpResult;
|
|
58
89
|
} finally {
|
|
90
|
+
pasteAbort.abort();
|
|
59
91
|
if (server.listening) await new Promise((resolve) => server.close(() => resolve()));
|
|
60
92
|
}
|
|
61
93
|
}
|
|
94
|
+
async function consumePastedCallback(options) {
|
|
95
|
+
if (!options.input.isTTY) return;
|
|
96
|
+
const rl = readline.createInterface({
|
|
97
|
+
input: options.input,
|
|
98
|
+
output: options.output
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
for (;;) {
|
|
102
|
+
let answer;
|
|
103
|
+
try {
|
|
104
|
+
answer = await rl.question("Paste the callback URL here: ", { signal: options.signal });
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error?.name === "AbortError") return;
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
const trimmed = answer.trim().replace(/^["']|["']$/g, "");
|
|
110
|
+
let url;
|
|
111
|
+
try {
|
|
112
|
+
if (!trimmed) throw new Error("empty input");
|
|
113
|
+
url = new URL(trimmed);
|
|
114
|
+
} catch {
|
|
115
|
+
options.output.write("That didn't look like a URL. Paste the full localhost callback URL and try again.\n");
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
await options.complete(url);
|
|
120
|
+
return;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
123
|
+
options.output.write(`Sign-in didn't complete (${message}). Paste the callback URL to try again.\n`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} finally {
|
|
128
|
+
rl.close();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
62
131
|
var LoginState = class {
|
|
63
132
|
latestVerifier;
|
|
64
133
|
latestState;
|
|
65
134
|
sdk;
|
|
66
135
|
openUrl;
|
|
67
136
|
tokenStorage;
|
|
137
|
+
output;
|
|
68
138
|
constructor(options) {
|
|
69
139
|
this.options = options;
|
|
70
140
|
this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env);
|
|
@@ -76,8 +146,9 @@ var LoginState = class {
|
|
|
76
146
|
authBaseUrl: options.authBaseUrl
|
|
77
147
|
});
|
|
78
148
|
this.openUrl = options.openUrl ?? open;
|
|
149
|
+
this.output = options.output;
|
|
79
150
|
}
|
|
80
|
-
async openLoginPage() {
|
|
151
|
+
async openLoginPage(interactive) {
|
|
81
152
|
const { url, state, verifier } = await this.sdk.getLoginUrl({
|
|
82
153
|
scope: "workspace:admin offline_access",
|
|
83
154
|
additionalParams: {
|
|
@@ -88,7 +159,17 @@ var LoginState = class {
|
|
|
88
159
|
});
|
|
89
160
|
this.latestState = state;
|
|
90
161
|
this.latestVerifier = verifier;
|
|
91
|
-
|
|
162
|
+
if (interactive) this.printLoginInstructions(url);
|
|
163
|
+
try {
|
|
164
|
+
await this.openUrl(url);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (!interactive) throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
printLoginInstructions(url) {
|
|
170
|
+
const output = this.output;
|
|
171
|
+
if (!output) return;
|
|
172
|
+
output.write(`\nOpen this URL to sign in: ${url}\n\nIf the browser opens on another machine, finish sign-in there. When it\nredirects to localhost, copy the full localhost URL from the address bar\nand paste it here.\n\n`);
|
|
92
173
|
}
|
|
93
174
|
async handleCallback(url) {
|
|
94
175
|
if (url.pathname !== "/auth/callback") throw new AuthError$1("Not a callback URL");
|