brightspace-mcp-server 2.0.0 → 3.0.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/README.md +33 -52
- package/build/api/client.d.ts +32 -7
- package/build/api/client.d.ts.map +1 -1
- package/build/api/client.js +93 -27
- package/build/api/client.js.map +1 -1
- package/build/auth/auth-cooldown.d.ts +8 -0
- package/build/auth/auth-cooldown.d.ts.map +1 -1
- package/build/auth/auth-cooldown.js +15 -2
- package/build/auth/auth-cooldown.js.map +1 -1
- package/build/auth/auth-lock.d.ts.map +1 -1
- package/build/auth/auth-lock.js +51 -3
- package/build/auth/auth-lock.js.map +1 -1
- package/build/auth/auth-runner.d.ts +15 -4
- package/build/auth/auth-runner.d.ts.map +1 -1
- package/build/auth/auth-runner.js +128 -109
- package/build/auth/auth-runner.js.map +1 -1
- package/build/auth/browser-auth.d.ts +2 -1
- package/build/auth/browser-auth.d.ts.map +1 -1
- package/build/auth/browser-auth.js +15 -4
- package/build/auth/browser-auth.js.map +1 -1
- package/build/auth/purdue-sso.d.ts +7 -0
- package/build/auth/purdue-sso.d.ts.map +1 -1
- package/build/auth/purdue-sso.js +46 -0
- package/build/auth/purdue-sso.js.map +1 -1
- package/build/auth/sso-flow.d.ts +2 -1
- package/build/auth/sso-flow.d.ts.map +1 -1
- package/build/auth/sso-flow.js +10 -3
- package/build/auth/sso-flow.js.map +1 -1
- package/build/auth/suny-sso.d.ts +3 -0
- package/build/auth/suny-sso.d.ts.map +1 -1
- package/build/auth/suny-sso.js +2 -0
- package/build/auth/suny-sso.js.map +1 -1
- package/build/auth-cli.js +49 -4
- package/build/auth-cli.js.map +1 -1
- package/build/index.js +13 -61
- package/build/index.js.map +1 -1
- package/build/setup.js +32 -13
- package/build/setup.js.map +1 -1
- package/build/tools/tool-helpers.d.ts +7 -1
- package/build/tools/tool-helpers.d.ts.map +1 -1
- package/build/tools/tool-helpers.js +96 -11
- package/build/tools/tool-helpers.js.map +1 -1
- package/build/utils/browser-install.d.ts +23 -0
- package/build/utils/browser-install.d.ts.map +1 -0
- package/build/utils/browser-install.js +33 -0
- package/build/utils/browser-install.js.map +1 -0
- package/build/utils/commands.d.ts +26 -0
- package/build/utils/commands.d.ts.map +1 -0
- package/build/utils/commands.js +26 -0
- package/build/utils/commands.js.map +1 -0
- package/build/utils/config.d.ts.map +1 -1
- package/build/utils/config.js +4 -2
- package/build/utils/config.js.map +1 -1
- package/build/utils/download-errors.d.ts +45 -0
- package/build/utils/download-errors.d.ts.map +1 -0
- package/build/utils/download-errors.js +45 -0
- package/build/utils/download-errors.js.map +1 -0
- package/build/utils/download-helpers.d.ts.map +1 -1
- package/build/utils/download-helpers.js +2 -1
- package/build/utils/download-helpers.js.map +1 -1
- package/build/utils/file-validator.d.ts +1 -1
- package/build/utils/file-validator.d.ts.map +1 -1
- package/build/utils/file-validator.js +47 -7
- package/build/utils/file-validator.js.map +1 -1
- package/build/utils/install-sites.d.ts +61 -0
- package/build/utils/install-sites.d.ts.map +1 -0
- package/build/utils/install-sites.js +207 -0
- package/build/utils/install-sites.js.map +1 -0
- package/build/utils/self-update.d.ts +61 -0
- package/build/utils/self-update.d.ts.map +1 -0
- package/build/utils/self-update.js +92 -0
- package/build/utils/self-update.js.map +1 -0
- package/build/utils/update-checker.d.ts +85 -7
- package/build/utils/update-checker.d.ts.map +1 -1
- package/build/utils/update-checker.js +141 -36
- package/build/utils/update-checker.js.map +1 -1
- package/package.json +1 -1
package/build/auth/auth-lock.js
CHANGED
|
@@ -2,6 +2,42 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { hostname } from "node:os";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
6
|
+
/**
|
|
7
|
+
* Windows refuses to rename or remove a directory while any process still
|
|
8
|
+
* holds a handle inside it. Every contender for a stale lock is doing exactly
|
|
9
|
+
* that: reclaim.lock lives inside the directory the winner is trying to rename,
|
|
10
|
+
* so the loser's own scan blocks the winner. POSIX renames by inode and never
|
|
11
|
+
* sees this.
|
|
12
|
+
*
|
|
13
|
+
* The condition clears in milliseconds, so a short retry turns a spurious
|
|
14
|
+
* EPERM into an ordinary release instead of an unhandled crash.
|
|
15
|
+
*/
|
|
16
|
+
const CONTENTION_CODES = new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
17
|
+
const RENAME_ATTEMPTS = 5;
|
|
18
|
+
/**
|
|
19
|
+
* Rename, tolerating Windows contention. Resolves true when the directory
|
|
20
|
+
* moved, false when it is already gone or contention never cleared. Only a
|
|
21
|
+
* genuinely unexpected error is thrown, so callers decide what a failed
|
|
22
|
+
* rename means rather than being handed an ambiguous EPERM.
|
|
23
|
+
*/
|
|
24
|
+
async function renameThroughContention(from, to) {
|
|
25
|
+
for (let attempt = 0; attempt < RENAME_ATTEMPTS; attempt += 1) {
|
|
26
|
+
try {
|
|
27
|
+
await fs.rename(from, to);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const code = error.code ?? "";
|
|
32
|
+
if (code === "ENOENT")
|
|
33
|
+
return false;
|
|
34
|
+
if (!CONTENTION_CODES.has(code))
|
|
35
|
+
throw error;
|
|
36
|
+
await delay(10 * (attempt + 1));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
5
41
|
export class AuthenticationInProgressError extends Error {
|
|
6
42
|
code = "AUTH_IN_PROGRESS";
|
|
7
43
|
constructor() {
|
|
@@ -56,8 +92,15 @@ class Lease {
|
|
|
56
92
|
if (current?.nonce !== this.owner.nonce)
|
|
57
93
|
return;
|
|
58
94
|
const retired = `${this.directory}.released.${this.owner.nonce}`;
|
|
59
|
-
|
|
60
|
-
|
|
95
|
+
// Releasing must never throw. It runs in a finally, so an exception here
|
|
96
|
+
// would mask the real failure and strand the lock for everyone else.
|
|
97
|
+
if (await renameThroughContention(this.directory, retired)) {
|
|
98
|
+
await cleanRetired(retired);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Contention never cleared. Clear the directory where it stands so the
|
|
102
|
+
// next process sees no owner rather than a lock nobody holds.
|
|
103
|
+
await cleanRetired(this.directory);
|
|
61
104
|
}
|
|
62
105
|
}
|
|
63
106
|
/** Acquire a process-shared lock without waiting or relying on its age. */
|
|
@@ -100,7 +143,12 @@ async function acquireLease(lockPath, depth) {
|
|
|
100
143
|
if (current?.nonce !== stale.nonce || !isDead(current))
|
|
101
144
|
throw new AuthenticationInProgressError();
|
|
102
145
|
const retired = `${lockPath}.stale.${owner.nonce}`;
|
|
103
|
-
|
|
146
|
+
// A rename blocked by contention means another contender is inside this
|
|
147
|
+
// directory recovering the same stale lock. That is someone else holding
|
|
148
|
+
// authentication, not a failure of ours.
|
|
149
|
+
if (!await renameThroughContention(lockPath, retired)) {
|
|
150
|
+
throw new AuthenticationInProgressError();
|
|
151
|
+
}
|
|
104
152
|
claim.directory = path.join(retired, "reclaim.lock");
|
|
105
153
|
await claim.release();
|
|
106
154
|
await cleanRetired(retired);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-lock.js","sourceRoot":"","sources":["../../src/auth/auth-lock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-lock.js","sourceRoot":"","sources":["../../src/auth/auth-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,16 +21,27 @@ export interface AuthRunnerOptions {
|
|
|
21
21
|
* so both processes read the same account configuration and .env file.
|
|
22
22
|
*/
|
|
23
23
|
export declare class AuthRunner {
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The login this process already started, if one is still running.
|
|
26
|
+
*
|
|
27
|
+
* Every tool call funnels here the moment the saved session is gone, and a
|
|
28
|
+
* stdio MCP server serves tool calls concurrently. Without a latch, two cold
|
|
29
|
+
* calls both see no token and both spawn a login: two browsers racing the
|
|
30
|
+
* same cross-process lock, and at Purdue two Authenticator prompts on the
|
|
31
|
+
* user's phone for one request. Holding the in-flight Promise makes every
|
|
32
|
+
* caller await the same login and read the same outcome.
|
|
33
|
+
*/
|
|
34
|
+
private inFlight;
|
|
25
35
|
private readonly scriptPath;
|
|
26
36
|
private readonly timeoutMs;
|
|
27
37
|
private readonly onProgress?;
|
|
28
38
|
constructor(options?: AuthRunnerOptions);
|
|
29
39
|
/**
|
|
30
|
-
*
|
|
31
|
-
* Returns true on success and throws a useful error on failure.
|
|
32
|
-
* Prevents concurrent attempts within this MCP process.
|
|
40
|
+
* Authenticate, joining the login this process already started if there is
|
|
41
|
+
* one. Returns true on success and throws a useful error on failure.
|
|
33
42
|
*/
|
|
34
43
|
run(): Promise<boolean>;
|
|
44
|
+
/** One child process, start to finish. */
|
|
45
|
+
private spawnAuth;
|
|
35
46
|
}
|
|
36
47
|
//# sourceMappingURL=auth-runner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-runner.d.ts","sourceRoot":"","sources":["../../src/auth/auth-runner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-runner.d.ts","sourceRoot":"","sources":["../../src/auth/auth-runner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAY/C,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,GAAG,aAAa,GAAG,eAAe,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEzH,qBAAa,gBAAiB,SAAQ,SAAS;aACjB,IAAI,EAAE,eAAe;gBAArB,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM;CAInE;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAkDD;;;;;;GAMG;AACH,qBAAa,UAAU;IACrB;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAyB;gBAEzC,OAAO,GAAE,iBAAsB;IAQ3C;;;OAGG;IACG,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;IAkB7B,0CAA0C;YAC5B,SAAS;CAgGxB"}
|
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { log } from "../utils/logger.js";
|
|
10
10
|
import { AuthError } from "../utils/errors.js";
|
|
11
|
+
import { AUTH_COMMAND } from "../utils/commands.js";
|
|
11
12
|
/**
|
|
12
13
|
* Timeout for the auth process. It has to outlast the child's own MFA wait,
|
|
13
14
|
* which is five minutes: a person has to find their phone, unlock it, and read
|
|
@@ -78,7 +79,17 @@ function forwardLines(stream, emit) {
|
|
|
78
79
|
* so both processes read the same account configuration and .env file.
|
|
79
80
|
*/
|
|
80
81
|
export class AuthRunner {
|
|
81
|
-
|
|
82
|
+
/**
|
|
83
|
+
* The login this process already started, if one is still running.
|
|
84
|
+
*
|
|
85
|
+
* Every tool call funnels here the moment the saved session is gone, and a
|
|
86
|
+
* stdio MCP server serves tool calls concurrently. Without a latch, two cold
|
|
87
|
+
* calls both see no token and both spawn a login: two browsers racing the
|
|
88
|
+
* same cross-process lock, and at Purdue two Authenticator prompts on the
|
|
89
|
+
* user's phone for one request. Holding the in-flight Promise makes every
|
|
90
|
+
* caller await the same login and read the same outcome.
|
|
91
|
+
*/
|
|
92
|
+
inFlight = null;
|
|
82
93
|
scriptPath;
|
|
83
94
|
timeoutMs;
|
|
84
95
|
onProgress;
|
|
@@ -90,125 +101,133 @@ export class AuthRunner {
|
|
|
90
101
|
this.onProgress = options.onProgress;
|
|
91
102
|
}
|
|
92
103
|
/**
|
|
93
|
-
*
|
|
94
|
-
* Returns true on success and throws a useful error on failure.
|
|
95
|
-
* Prevents concurrent attempts within this MCP process.
|
|
104
|
+
* Authenticate, joining the login this process already started if there is
|
|
105
|
+
* one. Returns true on success and throws a useful error on failure.
|
|
96
106
|
*/
|
|
97
107
|
async run() {
|
|
98
|
-
if (this.
|
|
99
|
-
|
|
108
|
+
if (this.inFlight) {
|
|
109
|
+
log("DEBUG", "Joining the authentication already in flight");
|
|
110
|
+
return this.inFlight;
|
|
100
111
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
112
|
+
// The latch is released by the flow that owns it, as it settles, so a
|
|
113
|
+
// failed login is never replayed: the tool call after a declined MFA
|
|
114
|
+
// prompt starts a fresh attempt rather than inheriting the stale
|
|
115
|
+
// rejection. Ownership is checked because a caller could in principle
|
|
116
|
+
// clear the latch while this flow is still running.
|
|
117
|
+
const flow = this.spawnAuth().finally(() => {
|
|
118
|
+
if (this.inFlight === flow)
|
|
119
|
+
this.inFlight = null;
|
|
120
|
+
});
|
|
121
|
+
this.inFlight = flow;
|
|
122
|
+
return flow;
|
|
123
|
+
}
|
|
124
|
+
/** One child process, start to finish. */
|
|
125
|
+
async spawnAuth() {
|
|
126
|
+
log("INFO", "Auto-launching brightspace-auth...");
|
|
127
|
+
return await new Promise((resolve, reject) => {
|
|
128
|
+
const child = spawn(process.execPath, // use the same Node binary
|
|
129
|
+
[this.scriptPath, "--automatic"], {
|
|
130
|
+
cwd: process.cwd(),
|
|
131
|
+
env: { ...process.env },
|
|
132
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
133
|
+
detached: process.platform !== "win32",
|
|
134
|
+
});
|
|
135
|
+
let timedOut = false;
|
|
136
|
+
let settled = false;
|
|
137
|
+
let killTimer;
|
|
138
|
+
const kill = (signal) => {
|
|
139
|
+
try {
|
|
140
|
+
if (child.pid && process.platform === "win32") {
|
|
141
|
+
execFileSync("taskkill", ["/pid", String(child.pid), "/t", "/f"], {
|
|
142
|
+
stdio: "ignore", timeout: 5000,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
else if (child.pid) {
|
|
146
|
+
if (signal === "SIGKILL") {
|
|
147
|
+
try {
|
|
148
|
+
for (const pid of descendantPids(child.pid)) {
|
|
149
|
+
try {
|
|
150
|
+
process.kill(pid, "SIGKILL");
|
|
130
151
|
}
|
|
152
|
+
catch { /* Already exited. */ }
|
|
131
153
|
}
|
|
132
|
-
catch { /* Still terminate the owned process group if ps is unavailable. */ }
|
|
133
154
|
}
|
|
134
|
-
process.
|
|
155
|
+
catch { /* Still terminate the owned process group if ps is unavailable. */ }
|
|
135
156
|
}
|
|
136
|
-
|
|
137
|
-
child.kill(signal);
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
// The child may already have exited between close and cleanup.
|
|
157
|
+
process.kill(-child.pid, signal);
|
|
141
158
|
}
|
|
142
|
-
};
|
|
143
|
-
const onExit = () => kill("SIGTERM");
|
|
144
|
-
process.once("exit", onExit);
|
|
145
|
-
const finish = (error) => {
|
|
146
|
-
if (settled)
|
|
147
|
-
return;
|
|
148
|
-
settled = true;
|
|
149
|
-
clearTimeout(timer);
|
|
150
|
-
if (killTimer)
|
|
151
|
-
clearTimeout(killTimer);
|
|
152
|
-
process.off("exit", onExit);
|
|
153
|
-
if (error)
|
|
154
|
-
reject(error);
|
|
155
159
|
else
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
160
|
+
child.kill(signal);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// The child may already have exited between close and cleanup.
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const onExit = () => kill("SIGTERM");
|
|
167
|
+
process.once("exit", onExit);
|
|
168
|
+
const finish = (error) => {
|
|
169
|
+
if (settled)
|
|
170
|
+
return;
|
|
171
|
+
settled = true;
|
|
172
|
+
clearTimeout(timer);
|
|
173
|
+
if (killTimer)
|
|
174
|
+
clearTimeout(killTimer);
|
|
175
|
+
process.off("exit", onExit);
|
|
176
|
+
if (error)
|
|
177
|
+
reject(error);
|
|
178
|
+
else
|
|
179
|
+
resolve(true);
|
|
180
|
+
};
|
|
181
|
+
const timer = setTimeout(() => {
|
|
182
|
+
timedOut = true;
|
|
183
|
+
kill("SIGTERM");
|
|
184
|
+
killTimer = setTimeout(() => {
|
|
180
185
|
kill("SIGKILL");
|
|
181
|
-
finish(new AuthProcessError("
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
else if (code === 0) {
|
|
191
|
-
log("INFO", "Auto-auth completed successfully");
|
|
192
|
-
finish();
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
const failures = {
|
|
196
|
-
2: ["busy", "Authentication already in progress in another process. Complete that attempt, then retry."],
|
|
197
|
-
3: ["cooldown", "Automatic MFA is paused after an unsuccessful attempt. Run brightspace-auth to retry immediately."],
|
|
198
|
-
4: ["unsupported", "This identity provider cannot complete headless authentication. See the authentication logs."],
|
|
199
|
-
5: ["secureStorage", "The native credential store is unavailable or locked. Unlock it and retry."],
|
|
200
|
-
6: ["transport", "Brightspace authentication is temporarily unavailable because of a network or server failure. Your saved session was preserved. Try again later."],
|
|
201
|
-
};
|
|
202
|
-
const [kind, message] = failures[code ?? -1] ?? ["failed", "Authentication failed. Run brightspace-auth to try again."];
|
|
203
|
-
kill("SIGKILL");
|
|
204
|
-
finish(new AuthProcessError(kind, message));
|
|
205
|
-
}
|
|
206
|
-
});
|
|
186
|
+
finish(new AuthProcessError("timeout", `Authentication timed out. Run ${AUTH_COMMAND} to try again.`));
|
|
187
|
+
}, KILL_GRACE_MS);
|
|
188
|
+
}, this.timeoutMs);
|
|
189
|
+
forwardLines(child.stderr, (line) => {
|
|
190
|
+
log("INFO", line);
|
|
191
|
+
try {
|
|
192
|
+
this.onProgress?.(line);
|
|
193
|
+
}
|
|
194
|
+
catch { /* Logging must not interrupt authentication. */ }
|
|
207
195
|
});
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
196
|
+
// Piped and drained rather than ignored: a full stdout pipe would
|
|
197
|
+
// block the child mid-login.
|
|
198
|
+
forwardLines(child.stdout, (line) => log("DEBUG", line));
|
|
199
|
+
child.on("error", (error) => {
|
|
200
|
+
if (settled)
|
|
201
|
+
return;
|
|
202
|
+
log("ERROR", "Auto-auth process failed", error.message);
|
|
203
|
+
kill("SIGKILL");
|
|
204
|
+
finish(new AuthProcessError("failed", `Could not start authentication. Run ${AUTH_COMMAND} for details.`));
|
|
205
|
+
});
|
|
206
|
+
child.on("close", (code) => {
|
|
207
|
+
if (settled)
|
|
208
|
+
return;
|
|
209
|
+
if (timedOut) {
|
|
210
|
+
kill("SIGKILL");
|
|
211
|
+
finish(new AuthProcessError("timeout", `Authentication timed out. Run ${AUTH_COMMAND} to try again.`));
|
|
212
|
+
}
|
|
213
|
+
else if (code === 0) {
|
|
214
|
+
log("INFO", "Auto-auth completed successfully");
|
|
215
|
+
finish();
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
const failures = {
|
|
219
|
+
2: ["busy", "Authentication already in progress in another process. Complete that attempt, then retry."],
|
|
220
|
+
3: ["cooldown", `Automatic MFA is paused after an unsuccessful attempt. Run ${AUTH_COMMAND} to retry immediately.`],
|
|
221
|
+
4: ["unsupported", "This identity provider cannot complete headless authentication. See the authentication logs."],
|
|
222
|
+
5: ["secureStorage", "The native credential store is unavailable or locked. Unlock it and retry."],
|
|
223
|
+
6: ["transport", "Brightspace authentication is temporarily unavailable because of a network or server failure. Your saved session was preserved. Try again later."],
|
|
224
|
+
};
|
|
225
|
+
const [kind, message] = failures[code ?? -1] ?? ["failed", `Authentication failed. Run ${AUTH_COMMAND} to try again.`];
|
|
226
|
+
kill("SIGKILL");
|
|
227
|
+
finish(new AuthProcessError(kind, message));
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
});
|
|
212
231
|
}
|
|
213
232
|
}
|
|
214
233
|
//# sourceMappingURL=auth-runner.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-runner.js","sourceRoot":"","sources":["../../src/auth/auth-runner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-runner.js","sourceRoot":"","sources":["../../src/auth/auth-runner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AACtC,MAAM,aAAa,GAAG,IAAI,CAAC;AAI3B,MAAM,OAAO,gBAAiB,SAAQ,SAAS;IACjB;IAA5B,YAA4B,IAAqB,EAAE,OAAe;QAChE,KAAK,CAAC,OAAO,CAAC,CAAC;QADW,SAAI,GAAJ,IAAI,CAAiB;QAE/C,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAOD,kFAAkF;AAClF,SAAS,cAAc,CAAC,SAAiB;IACvC,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,EAAE;QACrD,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI;KAChC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IACxE,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,CAAC,MAAc,EAAE,EAAE;QAC/B,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;YAC/B,IAAI,IAAI,KAAK,MAAM,IAAI,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7E,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACjB,KAAK,CAAC,GAAG,CAAC,CAAC;gBACX,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,KAAK,CAAC,SAAS,CAAC,CAAC;IACjB,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CACnB,MAAuB,EACvB,IAA4B;IAE5B,IAAI,CAAC,MAAM;QAAE,OAAO;IACpB,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;QAClC,QAAQ,IAAI,KAAK,CAAC;QAClB,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,QAAQ,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,IAAI,EAAE;gBAAE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QACxC,CAAC;IACH,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;QACpB,IAAI,QAAQ,CAAC,IAAI,EAAE;YAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9C,QAAQ,GAAG,EAAE,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,UAAU;IACrB;;;;;;;;;OASG;IACK,QAAQ,GAA4B,IAAI,CAAC;IAChC,UAAU,CAAS;IACnB,SAAS,CAAS;IAClB,UAAU,CAA0B;IAErD,YAAY,UAA6B,EAAE;QACzC,sFAAsF;QACtF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QAC7D,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,eAAe,CAAC;QACtD,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IACvC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG;QACP,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,GAAG,CAAC,OAAO,EAAE,8CAA8C,CAAC,CAAC;YAC7D,OAAO,IAAI,CAAC,QAAQ,CAAC;QACvB,CAAC;QAED,sEAAsE;QACtE,qEAAqE;QACrE,iEAAiE;QACjE,sEAAsE;QACtE,oDAAoD;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YACzC,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI;gBAAE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACnD,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0CAA0C;IAClC,KAAK,CAAC,SAAS;QACrB,GAAG,CAAC,MAAM,EAAE,oCAAoC,CAAC,CAAC;QAElD,OAAO,MAAM,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACpD,MAAM,KAAK,GAAG,KAAK,CACjB,OAAO,CAAC,QAAQ,EAAE,2BAA2B;YAC7C,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,EAChC;gBACE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;gBAClB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE;gBACvB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;gBACjC,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;aACvC,CACF,CAAC;YAEF,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,IAAI,SAAoD,CAAC;YACzD,MAAM,IAAI,GAAG,CAAC,MAAsB,EAAE,EAAE;gBACtC,IAAI,CAAC;oBACH,IAAI,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;wBAC9C,YAAY,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE;4BAChE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI;yBAC/B,CAAC,CAAC;oBACL,CAAC;yBAAM,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;wBACrB,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;4BACzB,IAAI,CAAC;gCACH,KAAK,MAAM,GAAG,IAAI,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;oCAC5C,IAAI,CAAC;wCAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;oCAAC,CAAC;oCAAC,MAAM,CAAC,CAAC,qBAAqB,CAAC,CAAC;gCACvE,CAAC;4BACH,CAAC;4BAAC,MAAM,CAAC,CAAC,mEAAmE,CAAC,CAAC;wBACjF,CAAC;wBACD,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;oBACnC,CAAC;;wBAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC5B,CAAC;gBAAC,MAAM,CAAC;oBACP,+DAA+D;gBACjE,CAAC;YACH,CAAC,CAAC;YACF,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACrC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAC7B,MAAM,MAAM,GAAG,CAAC,KAAwB,EAAE,EAAE;gBAC1C,IAAI,OAAO;oBAAE,OAAO;gBACpB,OAAO,GAAG,IAAI,CAAC;gBACf,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,IAAI,SAAS;oBAAE,YAAY,CAAC,SAAS,CAAC,CAAC;gBACvC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAC5B,IAAI,KAAK;oBAAE,MAAM,CAAC,KAAK,CAAC,CAAC;;oBACpB,OAAO,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC,CAAC;YACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,QAAQ,GAAG,IAAI,CAAC;gBAChB,IAAI,CAAC,SAAS,CAAC,CAAC;gBAChB,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC1B,IAAI,CAAC,SAAS,CAAC,CAAC;oBAChB,MAAM,CAAC,IAAI,gBAAgB,CAAC,SAAS,EAAE,iCAAiC,YAAY,gBAAgB,CAAC,CAAC,CAAC;gBACzG,CAAC,EAAE,aAAa,CAAC,CAAC;YACpB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAEnB,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBAClC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBAClB,IAAI,CAAC;oBAAC,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,gDAAgD,CAAC,CAAC;YAC7F,CAAC,CAAC,CAAC;YACH,kEAAkE;YAClE,6BAA6B;YAC7B,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAEzD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;gBAC1B,IAAI,OAAO;oBAAE,OAAO;gBACpB,GAAG,CAAC,OAAO,EAAE,0BAA0B,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBACxD,IAAI,CAAC,SAAS,CAAC,CAAC;gBAChB,MAAM,CAAC,IAAI,gBAAgB,CAAC,QAAQ,EAAE,uCAAuC,YAAY,eAAe,CAAC,CAAC,CAAC;YAC7G,CAAC,CAAC,CAAC;YAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gBACzB,IAAI,OAAO;oBAAE,OAAO;gBACpB,IAAI,QAAQ,EAAE,CAAC;oBACb,IAAI,CAAC,SAAS,CAAC,CAAC;oBAChB,MAAM,CAAC,IAAI,gBAAgB,CAAC,SAAS,EAAE,iCAAiC,YAAY,gBAAgB,CAAC,CAAC,CAAC;gBACzG,CAAC;qBAAM,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;oBACtB,GAAG,CAAC,MAAM,EAAE,kCAAkC,CAAC,CAAC;oBAChD,MAAM,EAAE,CAAC;gBACX,CAAC;qBAAM,CAAC;oBACN,MAAM,QAAQ,GAA8C;wBAC1D,CAAC,EAAE,CAAC,MAAM,EAAE,2FAA2F,CAAC;wBACxG,CAAC,EAAE,CAAC,UAAU,EAAE,8DAA8D,YAAY,wBAAwB,CAAC;wBACnH,CAAC,EAAE,CAAC,aAAa,EAAE,8FAA8F,CAAC;wBAClH,CAAC,EAAE,CAAC,eAAe,EAAE,4EAA4E,CAAC;wBAClG,CAAC,EAAE,CAAC,WAAW,EAAE,kJAAkJ,CAAC;qBACrK,CAAC;oBACF,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,8BAA8B,YAAY,gBAAgB,CAAC,CAAC;oBACvH,IAAI,CAAC,SAAS,CAAC,CAAC;oBAChB,MAAM,CAAC,IAAI,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Licensed under MIT: see LICENSE file for details.
|
|
5
5
|
*/
|
|
6
6
|
import type { AppConfig, TokenData } from "../types/index.js";
|
|
7
|
+
import type { RequestMfaCode } from "./sso-flow.js";
|
|
7
8
|
export declare class BrowserAuthTransportError extends Error {
|
|
8
9
|
readonly code = "AUTH_TRANSPORT";
|
|
9
10
|
constructor(message: string, options?: ErrorOptions);
|
|
@@ -18,7 +19,7 @@ export declare class BrowserAuth {
|
|
|
18
19
|
private ssoFlow;
|
|
19
20
|
private readonly stateStore;
|
|
20
21
|
private readonly cooldown;
|
|
21
|
-
constructor(config: AppConfig);
|
|
22
|
+
constructor(config: AppConfig, requestMfaCode?: RequestMfaCode);
|
|
22
23
|
private static isWSLOrDocker;
|
|
23
24
|
authenticate(options?: AuthenticateOptions): Promise<TokenData>;
|
|
24
25
|
private attemptAuthentication;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-auth.d.ts","sourceRoot":"","sources":["../../src/auth/browser-auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"browser-auth.d.ts","sourceRoot":"","sources":["../../src/auth/browser-auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAK9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAoBpD,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,IAAI,oBAAoB;gBACrB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAIpD;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,yEAAyE;IACzE,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;gBAE5B,MAAM,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,cAAc;IAO9D,OAAO,CAAC,MAAM,CAAC,aAAa;IAetB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,SAAS,CAAC;YAgB3D,qBAAqB;IAwEnC;;;;OAIG;YACW,eAAe;IA0D7B;;;OAGG;YACW,aAAa;IA0B3B,kFAAkF;YACpE,gBAAgB;IAmD9B;;;OAGG;YACW,cAAc;YAmDd,mBAAmB;IAKjC;;;;;OAKG;YACW,cAAc;IAsB5B;;;OAGG;YACW,mBAAmB;IA0CjC,6EAA6E;YAC/D,UAAU;IAQxB,6FAA6F;YAC/E,aAAa;IAO3B;;;OAGG;YACW,wBAAwB;IAyCtC;;;;;OAKG;YACW,sBAAsB;IAoCpC;;;;OAIG;YACW,eAAe;IAoD7B;;;OAGG;YACW,gBAAgB;IAgE9B;;;OAGG;YACW,kBAAkB;CAiCjC"}
|
|
@@ -12,6 +12,7 @@ import { BrowserStateStore } from "./browser-state-store.js";
|
|
|
12
12
|
import { acquireProcessLock } from "./auth-lock.js";
|
|
13
13
|
import { AuthCooldown } from "./auth-cooldown.js";
|
|
14
14
|
import { mintAccessToken } from "./token-mint.js";
|
|
15
|
+
import { isMissingBrowserError, PLAYWRIGHT_INSTALL_HINT } from "../utils/browser-install.js";
|
|
15
16
|
const SILENT_SSO_TIMEOUT_MS = 30000;
|
|
16
17
|
const SILENT_SSO_POLL_MS = 1000;
|
|
17
18
|
const INITIAL_NAVIGATION_TIMEOUT_MS = 60000;
|
|
@@ -36,9 +37,9 @@ export class BrowserAuth {
|
|
|
36
37
|
ssoFlow;
|
|
37
38
|
stateStore;
|
|
38
39
|
cooldown;
|
|
39
|
-
constructor(config) {
|
|
40
|
+
constructor(config, requestMfaCode) {
|
|
40
41
|
this.config = config;
|
|
41
|
-
this.ssoFlow = createSSOFlow(config);
|
|
42
|
+
this.ssoFlow = createSSOFlow(config, requestMfaCode);
|
|
42
43
|
this.stateStore = new BrowserStateStore(config.sessionDir);
|
|
43
44
|
this.cooldown = new AuthCooldown(config.sessionDir);
|
|
44
45
|
}
|
|
@@ -94,7 +95,17 @@ export class BrowserAuth {
|
|
|
94
95
|
if (BrowserAuth.isWSLOrDocker())
|
|
95
96
|
args.push("--no-sandbox", "--disable-setuid-sandbox");
|
|
96
97
|
// Use Playwright's own timeout, which cleans up an unsuccessful launch.
|
|
97
|
-
|
|
98
|
+
try {
|
|
99
|
+
browser = await chromium.launch({ headless: this.config.headless, timeout: 60000, args });
|
|
100
|
+
}
|
|
101
|
+
catch (launchError) {
|
|
102
|
+
// Keep the remedy attached to the failure. Without this the hint is
|
|
103
|
+
// lost when the auth runner flattens errors into "Authentication failed".
|
|
104
|
+
if (isMissingBrowserError(launchError)) {
|
|
105
|
+
throw new BrowserAuthError(PLAYWRIGHT_INSTALL_HINT, "browser_missing", launchError);
|
|
106
|
+
}
|
|
107
|
+
throw launchError;
|
|
108
|
+
}
|
|
98
109
|
process.once("SIGINT", closeOnSignal);
|
|
99
110
|
process.once("SIGTERM", closeOnSignal);
|
|
100
111
|
context = await browser.newContext({ viewport: { width: 1280, height: 720 }, storageState: state });
|
|
@@ -136,7 +147,7 @@ export class BrowserAuth {
|
|
|
136
147
|
throw new BrowserAuthError("Brightspace did not provide a usable API token. Saved SSO cookies have been preserved.", "token_extraction");
|
|
137
148
|
if (interrupted)
|
|
138
149
|
throw new BrowserAuthError("Authentication interrupted", "interrupted");
|
|
139
|
-
log("INFO", "
|
|
150
|
+
log("INFO", "Browser authentication complete");
|
|
140
151
|
return { ...token, ...material, tenantOrigin: new URL(this.config.baseUrl).origin };
|
|
141
152
|
}
|
|
142
153
|
finally {
|