brightspace-mcp-server 1.6.3 → 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.
Files changed (127) hide show
  1. package/README.md +43 -35
  2. package/build/api/client.d.ts +37 -10
  3. package/build/api/client.d.ts.map +1 -1
  4. package/build/api/client.js +158 -100
  5. package/build/api/client.js.map +1 -1
  6. package/build/api/errors.d.ts +4 -0
  7. package/build/api/errors.d.ts.map +1 -1
  8. package/build/api/errors.js +7 -0
  9. package/build/api/errors.js.map +1 -1
  10. package/build/auth/auth-cooldown.d.ts +24 -0
  11. package/build/auth/auth-cooldown.d.ts.map +1 -0
  12. package/build/auth/auth-cooldown.js +60 -0
  13. package/build/auth/auth-cooldown.js.map +1 -0
  14. package/build/auth/auth-lock.d.ts +7 -0
  15. package/build/auth/auth-lock.d.ts.map +1 -0
  16. package/build/auth/auth-lock.js +162 -0
  17. package/build/auth/auth-lock.js.map +1 -0
  18. package/build/auth/auth-runner.d.ts +30 -9
  19. package/build/auth/auth-runner.d.ts.map +1 -1
  20. package/build/auth/auth-runner.js +171 -56
  21. package/build/auth/auth-runner.js.map +1 -1
  22. package/build/auth/browser-auth.d.ts +19 -87
  23. package/build/auth/browser-auth.d.ts.map +1 -1
  24. package/build/auth/browser-auth.js +266 -614
  25. package/build/auth/browser-auth.js.map +1 -1
  26. package/build/auth/browser-state-store.d.ts +19 -0
  27. package/build/auth/browser-state-store.d.ts.map +1 -0
  28. package/build/auth/browser-state-store.js +101 -0
  29. package/build/auth/browser-state-store.js.map +1 -0
  30. package/build/auth/credential-store.d.ts +29 -0
  31. package/build/auth/credential-store.d.ts.map +1 -0
  32. package/build/auth/credential-store.js +233 -0
  33. package/build/auth/credential-store.js.map +1 -0
  34. package/build/auth/encrypted-store.d.ts +25 -0
  35. package/build/auth/encrypted-store.d.ts.map +1 -0
  36. package/build/auth/encrypted-store.js +61 -0
  37. package/build/auth/encrypted-store.js.map +1 -0
  38. package/build/auth/legacy-profile.d.ts +3 -0
  39. package/build/auth/legacy-profile.d.ts.map +1 -0
  40. package/build/auth/legacy-profile.js +41 -0
  41. package/build/auth/legacy-profile.js.map +1 -0
  42. package/build/auth/legacy-state.d.ts +14 -0
  43. package/build/auth/legacy-state.d.ts.map +1 -0
  44. package/build/auth/legacy-state.js +107 -0
  45. package/build/auth/legacy-state.js.map +1 -0
  46. package/build/auth/purdue-sso.d.ts +25 -36
  47. package/build/auth/purdue-sso.d.ts.map +1 -1
  48. package/build/auth/purdue-sso.js +210 -191
  49. package/build/auth/purdue-sso.js.map +1 -1
  50. package/build/auth/session-store.d.ts +16 -38
  51. package/build/auth/session-store.d.ts.map +1 -1
  52. package/build/auth/session-store.js +157 -152
  53. package/build/auth/session-store.js.map +1 -1
  54. package/build/auth/sso-flow.d.ts +16 -4
  55. package/build/auth/sso-flow.d.ts.map +1 -1
  56. package/build/auth/sso-flow.js +24 -2
  57. package/build/auth/sso-flow.js.map +1 -1
  58. package/build/auth/suny-sso.d.ts +6 -2
  59. package/build/auth/suny-sso.d.ts.map +1 -1
  60. package/build/auth/suny-sso.js +17 -24
  61. package/build/auth/suny-sso.js.map +1 -1
  62. package/build/auth/token-manager.d.ts +10 -3
  63. package/build/auth/token-manager.d.ts.map +1 -1
  64. package/build/auth/token-manager.js +62 -26
  65. package/build/auth/token-manager.js.map +1 -1
  66. package/build/auth/token-mint.d.ts +2 -2
  67. package/build/auth/token-mint.d.ts.map +1 -1
  68. package/build/auth/token-mint.js +31 -9
  69. package/build/auth/token-mint.js.map +1 -1
  70. package/build/auth-cli.d.ts +1 -7
  71. package/build/auth-cli.d.ts.map +1 -1
  72. package/build/auth-cli.js +71 -70
  73. package/build/auth-cli.js.map +1 -1
  74. package/build/index.js +20 -64
  75. package/build/index.js.map +1 -1
  76. package/build/setup.js +44 -19
  77. package/build/setup.js.map +1 -1
  78. package/build/tools/tool-helpers.d.ts +7 -1
  79. package/build/tools/tool-helpers.d.ts.map +1 -1
  80. package/build/tools/tool-helpers.js +96 -11
  81. package/build/tools/tool-helpers.js.map +1 -1
  82. package/build/types/index.d.ts +6 -0
  83. package/build/types/index.d.ts.map +1 -1
  84. package/build/utils/browser-install.d.ts +23 -0
  85. package/build/utils/browser-install.d.ts.map +1 -0
  86. package/build/utils/browser-install.js +33 -0
  87. package/build/utils/browser-install.js.map +1 -0
  88. package/build/utils/commands.d.ts +26 -0
  89. package/build/utils/commands.d.ts.map +1 -0
  90. package/build/utils/commands.js +26 -0
  91. package/build/utils/commands.js.map +1 -0
  92. package/build/utils/config-store.d.ts +1 -0
  93. package/build/utils/config-store.d.ts.map +1 -1
  94. package/build/utils/config-store.js +3 -0
  95. package/build/utils/config-store.js.map +1 -1
  96. package/build/utils/config.d.ts +2 -1
  97. package/build/utils/config.d.ts.map +1 -1
  98. package/build/utils/config.js +32 -10
  99. package/build/utils/config.js.map +1 -1
  100. package/build/utils/download-errors.d.ts +45 -0
  101. package/build/utils/download-errors.d.ts.map +1 -0
  102. package/build/utils/download-errors.js +45 -0
  103. package/build/utils/download-errors.js.map +1 -0
  104. package/build/utils/download-helpers.d.ts.map +1 -1
  105. package/build/utils/download-helpers.js +2 -1
  106. package/build/utils/download-helpers.js.map +1 -1
  107. package/build/utils/file-validator.d.ts +1 -1
  108. package/build/utils/file-validator.d.ts.map +1 -1
  109. package/build/utils/file-validator.js +47 -7
  110. package/build/utils/file-validator.js.map +1 -1
  111. package/build/utils/install-sites.d.ts +61 -0
  112. package/build/utils/install-sites.d.ts.map +1 -0
  113. package/build/utils/install-sites.js +207 -0
  114. package/build/utils/install-sites.js.map +1 -0
  115. package/build/utils/secure-config.d.ts +6 -0
  116. package/build/utils/secure-config.d.ts.map +1 -0
  117. package/build/utils/secure-config.js +57 -0
  118. package/build/utils/secure-config.js.map +1 -0
  119. package/build/utils/self-update.d.ts +61 -0
  120. package/build/utils/self-update.d.ts.map +1 -0
  121. package/build/utils/self-update.js +92 -0
  122. package/build/utils/self-update.js.map +1 -0
  123. package/build/utils/update-checker.d.ts +85 -7
  124. package/build/utils/update-checker.d.ts.map +1 -1
  125. package/build/utils/update-checker.js +141 -36
  126. package/build/utils/update-checker.js.map +1 -1
  127. package/package.json +4 -2
@@ -1,192 +1,197 @@
1
1
  /**
2
- * Purdue Brightspace MCP Server
2
+ * Brightspace MCP Server
3
3
  * Copyright (c) 2026 Rohan Muppa. All rights reserved.
4
- * Licensed under MIT — see LICENSE file for details.
4
+ * Licensed under MIT. See LICENSE file for details.
5
5
  */
6
6
  import * as crypto from "node:crypto";
7
7
  import * as fs from "node:fs/promises";
8
- import * as fsSync from "node:fs";
9
8
  import * as path from "node:path";
10
9
  import * as os from "node:os";
11
10
  import { SessionStoreError } from "../utils/errors.js";
12
- import { log } from "../utils/logger.js";
13
- import { writeFileAtomic } from "../utils/atomic-write.js";
11
+ import { acquireProcessLock, AuthenticationInProgressError } from "./auth-lock.js";
12
+ import { NativeCredentialStoreError } from "./credential-store.js";
13
+ import { decrypt, readEncryptedRecord, saveEncryptedRecord, trashFile } from "./encrypted-store.js";
14
14
  const DEFAULT_SESSION_DIR = path.join(os.homedir(), ".d2l-session");
15
- const SESSION_FILE_NAME = "session.json";
16
- const SESSION_VERSION = 1;
17
- // Encryption constants
18
- const ALGORITHM = "aes-256-gcm";
19
- const IV_LENGTH = 12; // GCM recommended IV length
20
- const AUTH_TAG_LENGTH = 16; // GCM auth tag length
21
- const SALT_LENGTH = 16;
22
- const SALT_FILE_NAME = "salt";
23
- /**
24
- * SessionStore manages encrypted token persistence to disk.
25
- * Uses AES-256-GCM for encryption with a key derived from username + hostname.
26
- */
15
+ function validTenantOrigin(origin) {
16
+ if (origin === undefined)
17
+ return true;
18
+ if (typeof origin !== "string")
19
+ return false;
20
+ try {
21
+ const parsed = new URL(origin);
22
+ return parsed.protocol === "https:" && parsed.origin === origin;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ function validToken(value) {
29
+ const token = value;
30
+ return !!token && typeof token.accessToken === "string" && Number.isFinite(token.capturedAt) && Number.isFinite(token.expiresAt)
31
+ && (token.source === "browser" || token.source === "cache")
32
+ && (token.cookieHeader === undefined || typeof token.cookieHeader === "string")
33
+ && (token.csrfToken === undefined || typeof token.csrfToken === "string")
34
+ && validTenantOrigin(token.tenantOrigin);
35
+ }
36
+ function equalToken(a, b) {
37
+ return a === b || (!!a && !!b && a.accessToken === b.accessToken && a.capturedAt === b.capturedAt
38
+ && a.expiresAt === b.expiresAt && a.source === b.source && a.cookieHeader === b.cookieHeader && a.csrfToken === b.csrfToken && a.tenantOrigin === b.tenantOrigin);
39
+ }
40
+ /** AES-GCM session persistence with a random key held by the native secret store. */
27
41
  export class SessionStore {
42
+ options;
28
43
  sessionDir;
29
44
  sessionFilePath;
30
- constructor(sessionDir) {
31
- this.sessionDir = sessionDir ?? DEFAULT_SESSION_DIR;
32
- this.sessionFilePath = path.join(this.sessionDir, SESSION_FILE_NAME);
45
+ constructor(sessionDir = DEFAULT_SESSION_DIR, options = {}) {
46
+ this.options = options;
47
+ this.sessionDir = sessionDir;
48
+ this.sessionFilePath = path.join(sessionDir, "session.json");
33
49
  }
34
- /**
35
- * Get or create a random salt unique to this installation.
36
- * Stored at ~/.d2l-session/salt with restricted permissions.
37
- */
38
- getOrCreateSalt() {
39
- const saltPath = path.join(this.sessionDir, SALT_FILE_NAME);
50
+ async withWriteLock(operation) {
51
+ await fs.mkdir(this.sessionDir, { recursive: true, mode: 0o700 });
52
+ let release;
53
+ for (let attempt = 0; attempt < 100; attempt++) {
54
+ try {
55
+ release = await acquireProcessLock(path.join(this.sessionDir, ".session-write.lock"));
56
+ break;
57
+ }
58
+ catch (error) {
59
+ if (!(error instanceof AuthenticationInProgressError) || attempt === 99)
60
+ throw error;
61
+ await new Promise((resolve) => setTimeout(resolve, 25));
62
+ }
63
+ }
40
64
  try {
41
- return fsSync.readFileSync(saltPath);
42
- }
43
- catch {
44
- // Salt doesn't exist yet — create session dir and generate one
45
- const isWindows = process.platform === "win32";
46
- fsSync.mkdirSync(this.sessionDir, {
47
- recursive: true,
48
- ...(isWindows ? {} : { mode: 0o700 }),
49
- });
50
- const salt = crypto.randomBytes(SALT_LENGTH);
51
- fsSync.writeFileSync(saltPath, salt, {
52
- ...(isWindows ? {} : { mode: 0o600 }),
53
- });
54
- return salt;
65
+ return await operation();
66
+ }
67
+ finally {
68
+ await release?.();
55
69
  }
56
70
  }
57
- /**
58
- * Derive AES-256 key from username and hostname using scrypt.
59
- * Uses a per-installation random salt to prevent precomputation attacks.
60
- */
61
- deriveKey() {
62
- // Deliberately NOT the hostname. On a campus network os.hostname() is a
63
- // DHCP reverse-DNS name (pal-nat186-166-147.itap.purdue.edu), so it
64
- // changes with the lease. Keying on it meant that moving between networks
65
- // silently made the saved session undecryptable, and the user paid for it
66
- // with a full MFA login. The per-install random salt already provides the
67
- // uniqueness the hostname was there for, and it is stable.
68
- const username = os.userInfo().username;
69
- const salt = this.getOrCreateSalt();
70
- // Use scrypt to derive a 32-byte key (256 bits for AES-256)
71
- return crypto.scryptSync(username, salt, 32);
71
+ async readFile() {
72
+ try {
73
+ return JSON.parse(await fs.readFile(this.sessionFilePath, "utf8"));
74
+ }
75
+ catch (error) {
76
+ if (error.code === "ENOENT")
77
+ return null;
78
+ throw error;
79
+ }
72
80
  }
73
- /**
74
- * Encrypt plaintext using AES-256-GCM.
75
- * Returns IV, auth tag, and ciphertext as hex strings.
76
- */
77
- encrypt(plaintext) {
78
- const key = this.deriveKey();
79
- const iv = crypto.randomBytes(IV_LENGTH);
80
- const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
81
- let encrypted = cipher.update(plaintext, "utf8", "hex");
82
- encrypted += cipher.final("hex");
83
- const authTag = cipher.getAuthTag();
84
- return {
85
- iv: iv.toString("hex"),
86
- authTag: authTag.toString("hex"),
87
- data: encrypted,
88
- };
81
+ async decode(record) {
82
+ let token;
83
+ if (record.version === 2) {
84
+ token = await readEncryptedRecord(this.sessionDir, record, "session", this.options);
85
+ }
86
+ else if (record.version === 1) {
87
+ // Never generate a salt when loading old data. That would make recovery harder.
88
+ const salt = await fs.readFile(path.join(this.sessionDir, "salt"));
89
+ const username = os.userInfo().username;
90
+ let lastError;
91
+ for (const material of [username, username + os.hostname()]) {
92
+ try {
93
+ token = JSON.parse(decrypt(record.encrypted, crypto.scryptSync(material, salt, 32)));
94
+ break;
95
+ }
96
+ catch (error) {
97
+ lastError = error;
98
+ }
99
+ }
100
+ if (!token)
101
+ throw lastError;
102
+ }
103
+ else {
104
+ throw new Error("Unsupported saved session version.");
105
+ }
106
+ if (!validToken(token))
107
+ throw new Error("Invalid saved session contents.");
108
+ return token;
89
109
  }
90
- /**
91
- * Decrypt ciphertext using AES-256-GCM.
92
- * Returns plaintext string, or throws if auth tag verification fails.
93
- */
94
- decrypt(encrypted) {
95
- const key = this.deriveKey();
96
- const iv = Buffer.from(encrypted.iv, "hex");
97
- const authTag = Buffer.from(encrypted.authTag, "hex");
98
- const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
99
- decipher.setAuthTag(authTag);
100
- let decrypted = decipher.update(encrypted.data, "hex", "utf8");
101
- decrypted += decipher.final("utf8");
102
- return decrypted;
110
+ async loadUnlocked() {
111
+ const record = await this.readFile();
112
+ if (!record)
113
+ return null;
114
+ const token = await this.decode(record);
115
+ if (record.version === 1)
116
+ await this.saveUnlocked(token);
117
+ return token;
118
+ }
119
+ async saveUnlocked(token) {
120
+ if (!validToken(token))
121
+ throw new Error("Invalid session token.");
122
+ await saveEncryptedRecord(this.sessionDir, this.sessionFilePath, "session", token, this.options);
123
+ }
124
+ storeError(action, error) {
125
+ if (error instanceof NativeCredentialStoreError || error instanceof AuthenticationInProgressError)
126
+ throw error;
127
+ throw new SessionStoreError(`Failed to ${action} session. Existing session data was preserved.`, error instanceof Error ? error : new Error(String(error)));
103
128
  }
104
- /**
105
- * Save token to disk with encryption.
106
- * Creates session directory if it doesn't exist.
107
- */
108
129
  async save(token) {
109
130
  try {
110
- // Ensure session directory exists with restricted permissions (owner-only on Unix)
111
- const isWindows = process.platform === "win32";
112
- await fs.mkdir(this.sessionDir, {
113
- recursive: true,
114
- ...(isWindows ? {} : { mode: 0o700 }),
131
+ await this.withWriteLock(async () => {
132
+ const current = await this.readFile();
133
+ if (current)
134
+ await this.decode(current);
135
+ await this.saveUnlocked(token);
115
136
  });
116
- const plaintext = JSON.stringify(token);
117
- const encrypted = this.encrypt(plaintext);
118
- const sessionFile = {
119
- version: SESSION_VERSION,
120
- encrypted,
121
- createdAt: Date.now(),
122
- expiresAt: token.expiresAt,
123
- };
124
- // Staged and renamed, so the auth CLI and a running server can never
125
- // leave each other a half-written file.
126
- await writeFileAtomic(this.sessionFilePath, JSON.stringify(sessionFile, null, 2), isWindows ? {} : { mode: 0o600 });
127
- log("DEBUG", `Session saved to ${this.sessionFilePath}`);
128
137
  }
129
138
  catch (error) {
130
- const err = error instanceof Error ? error : new Error(String(error));
131
- log("ERROR", `Failed to save session: ${err.message}`);
132
- throw new SessionStoreError("Failed to save session", err);
139
+ this.storeError("save", error);
133
140
  }
134
141
  }
135
- /**
136
- * Load token from disk with decryption.
137
- * Returns null if file doesn't exist or is corrupted (graceful degradation).
138
- */
142
+ /** Missing files return null. Damaged or inaccessible secrets fail without MFA. */
139
143
  async load() {
140
144
  try {
141
- // Check if file exists
142
- try {
143
- await fs.access(this.sessionFilePath);
144
- }
145
- catch {
146
- log("DEBUG", "No session file found");
145
+ const record = await this.readFile();
146
+ if (!record)
147
147
  return null;
148
- }
149
- // Read and parse session file
150
- const fileContent = await fs.readFile(this.sessionFilePath, "utf-8");
151
- const sessionFile = JSON.parse(fileContent);
152
- // Decrypt token data
153
- const plaintext = this.decrypt(sessionFile.encrypted);
154
- const token = JSON.parse(plaintext);
155
- log("DEBUG", `Session loaded from ${this.sessionFilePath}`);
156
- return token;
148
+ if (record.version === 1)
149
+ return await this.withWriteLock(() => this.loadUnlocked());
150
+ return await this.decode(record);
157
151
  }
158
152
  catch (error) {
159
- const err = error instanceof Error ? error : new Error(String(error));
160
- log("WARN", `Failed to load session: ${err.message}`);
161
- // A session this install cannot read is never going to become readable:
162
- // it was written by an older build whose key derivation differed, or the
163
- // file is damaged. Removing it means the next run does one clean login
164
- // instead of logging the same warning on every start forever.
165
- try {
166
- await fs.rm(this.sessionFilePath, { force: true });
167
- log("DEBUG", "Removed the unreadable session file, a fresh login is needed");
168
- }
169
- catch {
170
- // Nothing more to do: the caller re-authenticates either way.
171
- }
172
- // Return null instead of throwing - graceful degradation
173
- return null;
153
+ this.storeError("load", error);
174
154
  }
175
155
  }
176
- /**
177
- * Clear session by deleting the session file.
178
- */
179
156
  async clear() {
180
157
  try {
181
- await fs.unlink(this.sessionFilePath);
182
- log("DEBUG", `Session cleared: ${this.sessionFilePath}`);
158
+ await this.withWriteLock(async () => {
159
+ const current = await this.readFile();
160
+ if (!current)
161
+ return;
162
+ await this.decode(current);
163
+ await trashFile(this.sessionFilePath, this.options);
164
+ });
183
165
  }
184
166
  catch (error) {
185
- // Ignore ENOENT errors - file already doesn't exist
186
- if (error.code !== "ENOENT") {
187
- const err = error instanceof Error ? error : new Error(String(error));
188
- log("WARN", `Failed to clear session: ${err.message}`);
189
- }
167
+ this.storeError("clear", error);
168
+ }
169
+ }
170
+ /** Prevent a slow mint from replacing credentials saved by a newer login. */
171
+ async saveIfCurrent(token, expected) {
172
+ try {
173
+ return await this.withWriteLock(async () => {
174
+ if (!equalToken(await this.loadUnlocked(), expected))
175
+ return false;
176
+ await this.saveUnlocked(token);
177
+ return true;
178
+ });
179
+ }
180
+ catch (error) {
181
+ this.storeError("save", error);
182
+ }
183
+ }
184
+ async clearIfCurrent(expected) {
185
+ try {
186
+ return await this.withWriteLock(async () => {
187
+ if (!equalToken(await this.loadUnlocked(), expected))
188
+ return false;
189
+ await trashFile(this.sessionFilePath, this.options);
190
+ return true;
191
+ });
192
+ }
193
+ catch (error) {
194
+ this.storeError("clear", error);
190
195
  }
191
196
  }
192
197
  }
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/auth/session-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/auth/session-store.ts"],"names":[],"mappings":"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"}
@@ -5,14 +5,26 @@
5
5
  */
6
6
  import type { Page } from "playwright";
7
7
  import type { AppConfig } from "../types/index.js";
8
+ import { BrowserAuthError } from "../utils/errors.js";
9
+ export type RequestMfaCode = () => Promise<string>;
10
+ export declare class UnsupportedAuthenticationError extends BrowserAuthError {
11
+ readonly code = "AUTH_UNSUPPORTED";
12
+ constructor(message: string, cause?: Error);
13
+ }
14
+ export declare class MfaApprovalError extends BrowserAuthError {
15
+ readonly code = "AUTH_MFA_FAILED";
16
+ constructor(cause?: Error);
17
+ }
8
18
  /** The browser login sequence for one institution's identity provider. */
9
19
  export interface SSOFlow {
20
+ /** Pass known school and campus selectors without entering credentials. */
21
+ prepareLogin?(page: Page): Promise<void>;
22
+ /** Submit only the public account name so a saved IdP session can resume. */
23
+ identifyAccount?(page: Page): Promise<boolean>;
10
24
  /** True when saved credentials allow an automated sign-in attempt. */
11
25
  hasCredentials(): boolean;
12
- /** Drive the sign-in form. Resolves false on timeout so the caller can fall back. */
26
+ /** Drive the supported headless sign-in form, surfacing MFA in terminal logs. */
13
27
  login(page: Page): Promise<boolean>;
14
- /** Wait for the user to sign in themselves in a visible browser. */
15
- manualLogin(page: Page): Promise<boolean>;
16
28
  }
17
29
  /**
18
30
  * Pick the login sequence for the configured Brightspace host. Schools whose
@@ -20,5 +32,5 @@ export interface SSOFlow {
20
32
  * else uses the default flow, which already covers the common Shibboleth,
21
33
  * CAS, and Microsoft Entra forms.
22
34
  */
23
- export declare function createSSOFlow(config: AppConfig): SSOFlow;
35
+ export declare function createSSOFlow(config: AppConfig, requestMfaCode?: RequestMfaCode): SSOFlow;
24
36
  //# sourceMappingURL=sso-flow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sso-flow.d.ts","sourceRoot":"","sources":["../../src/auth/sso-flow.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAInD,0EAA0E;AAC1E,MAAM,WAAW,OAAO;IACtB,sEAAsE;IACtE,cAAc,IAAI,OAAO,CAAC;IAC1B,qFAAqF;IACrF,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpC,oEAAoE;IACpE,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3C;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAQxD"}
1
+ {"version":3,"file":"sso-flow.d.ts","sourceRoot":"","sources":["../../src/auth/sso-flow.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;AAEnD,qBAAa,8BAA+B,SAAQ,gBAAgB;IAClE,QAAQ,CAAC,IAAI,sBAAsB;gBACvB,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK;CAI3C;AAED,qBAAa,gBAAiB,SAAQ,gBAAgB;IACpD,QAAQ,CAAC,IAAI,qBAAqB;gBACtB,KAAK,CAAC,EAAE,KAAK;CAI1B;AAED,0EAA0E;AAC1E,MAAM,WAAW,OAAO;IACtB,2EAA2E;IAC3E,YAAY,CAAC,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,6EAA6E;IAC7E,eAAe,CAAC,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,sEAAsE;IACtE,cAAc,IAAI,OAAO,CAAC;IAC1B,iFAAiF;IACjF,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACrC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,OAAO,CAczF"}
@@ -5,14 +5,36 @@
5
5
  */
6
6
  import { PurdueSSOFlow } from "./purdue-sso.js";
7
7
  import { SunySSOFlow, isSunyBrightspace } from "./suny-sso.js";
8
+ import { BrowserAuthError } from "../utils/errors.js";
9
+ import { AUTH_COMMAND } from "../utils/commands.js";
10
+ export class UnsupportedAuthenticationError extends BrowserAuthError {
11
+ code = "AUTH_UNSUPPORTED";
12
+ constructor(message, cause) {
13
+ super(message, "headless_login", cause);
14
+ this.name = "UnsupportedAuthenticationError";
15
+ }
16
+ }
17
+ export class MfaApprovalError extends BrowserAuthError {
18
+ code = "AUTH_MFA_FAILED";
19
+ constructor(cause) {
20
+ super(`MFA approval failed or timed out after 5 minutes. Run ${AUTH_COMMAND} to retry.`, "mfa_approval", cause);
21
+ this.name = "MfaApprovalError";
22
+ }
23
+ }
8
24
  /**
9
25
  * Pick the login sequence for the configured Brightspace host. Schools whose
10
26
  * identity provider needs extra steps get their own handler here; everything
11
27
  * else uses the default flow, which already covers the common Shibboleth,
12
28
  * CAS, and Microsoft Entra forms.
13
29
  */
14
- export function createSSOFlow(config) {
15
- const credentials = { username: config.username, password: config.password };
30
+ export function createSSOFlow(config, requestMfaCode) {
31
+ const credentials = {
32
+ username: config.username,
33
+ password: config.password,
34
+ baseUrl: config.baseUrl,
35
+ headless: config.headless,
36
+ requestMfaCode,
37
+ };
16
38
  if (isSunyBrightspace(config.baseUrl)) {
17
39
  return new SunySSOFlow({ ...credentials, campus: config.campus });
18
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sso-flow.js","sourceRoot":"","sources":["../../src/auth/sso-flow.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAY/D;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,MAAiB;IAC7C,MAAM,WAAW,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;IAE7E,IAAI,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,OAAO,IAAI,WAAW,CAAC,EAAE,GAAG,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,IAAI,aAAa,CAAC,WAAW,CAAC,CAAC;AACxC,CAAC"}
1
+ {"version":3,"file":"sso-flow.js","sourceRoot":"","sources":["../../src/auth/sso-flow.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAIpD,MAAM,OAAO,8BAA+B,SAAQ,gBAAgB;IACzD,IAAI,GAAG,kBAAkB,CAAC;IACnC,YAAY,OAAe,EAAE,KAAa;QACxC,KAAK,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;IAC/C,CAAC;CACF;AAED,MAAM,OAAO,gBAAiB,SAAQ,gBAAgB;IAC3C,IAAI,GAAG,iBAAiB,CAAC;IAClC,YAAY,KAAa;QACvB,KAAK,CAAC,yDAAyD,YAAY,YAAY,EAAE,cAAc,EAAE,KAAK,CAAC,CAAC;QAChH,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAcD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,MAAiB,EAAE,cAA+B;IAC9E,MAAM,WAAW,GAAG;QAClB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,cAAc;KACf,CAAC;IAEF,IAAI,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,OAAO,IAAI,WAAW,CAAC,EAAE,GAAG,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,IAAI,aAAa,CAAC,WAAW,CAAC,CAAC;AACxC,CAAC"}
@@ -1,14 +1,17 @@
1
1
  /**
2
2
  * Brightspace MCP Server
3
3
  * Copyright (c) 2026 Rohan Muppa. All rights reserved.
4
- * Licensed under MIT — see LICENSE file for details.
4
+ * Licensed under MIT : see LICENSE file for details.
5
5
  */
6
6
  import type { Page } from "playwright";
7
+ import type { RequestMfaCode } from "./sso-flow.js";
7
8
  interface SunySSOConfig {
8
9
  username?: string;
9
10
  password?: string;
10
11
  /** Campus name or numeric code, matched against SUNY's own dropdown. */
11
12
  campus?: string;
13
+ headless?: boolean;
14
+ requestMfaCode?: RequestMfaCode;
12
15
  }
13
16
  /** True for the shared SUNY Brightspace instance. */
14
17
  export declare function isSunyBrightspace(baseUrl: string): boolean;
@@ -26,8 +29,9 @@ export declare class SunySSOFlow {
26
29
  private defaultFlow;
27
30
  constructor(config: SunySSOConfig);
28
31
  hasCredentials(): boolean;
32
+ prepareLogin(page: Page): Promise<void>;
33
+ identifyAccount(page: Page): Promise<boolean>;
29
34
  login(page: Page): Promise<boolean>;
30
- manualLogin(page: Page): Promise<boolean>;
31
35
  /**
32
36
  * Brightspace's own campus selector renders its buttons in a shadow DOM, so
33
37
  * go straight to the SAML endpoint that selector would have reached.
@@ -1 +1 @@
1
- {"version":3,"file":"suny-sso.d.ts","sourceRoot":"","sources":["../../src/auth/suny-sso.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAcvC,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD,qDAAqD;AACrD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAM1D;AAcD;;;;;;;;GAQG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,WAAW,CAAgB;gBAEvB,MAAM,EAAE,aAAa;IAQjC,cAAc,IAAI,OAAO;IAInB,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAWnC,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAY/C;;;OAGG;YACW,cAAc;IAY5B;;;OAGG;YACW,YAAY;IAwC1B;;;;OAIG;IACH,OAAO,CAAC,UAAU;CAUnB"}
1
+ {"version":3,"file":"suny-sso.d.ts","sourceRoot":"","sources":["../../src/auth/suny-sso.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAYpD,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAOD,qDAAqD;AACrD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAM1D;AAcD;;;;;;;;GAQG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,WAAW,CAAgB;gBAEvB,MAAM,EAAE,aAAa;IAWjC,cAAc,IAAI,OAAO;IAInB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvC,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAI7C,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAUzC;;;OAGG;YACW,cAAc;IAY5B;;;OAGG;YACW,YAAY;IAmC1B;;;;OAIG;IACH,OAAO,CAAC,UAAU;CAUnB"}
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Brightspace MCP Server
3
3
  * Copyright (c) 2026 Rohan Muppa. All rights reserved.
4
- * Licensed under MIT — see LICENSE file for details.
4
+ * Licensed under MIT : see LICENSE file for details.
5
5
  */
6
6
  import { PurdueSSOFlow } from "./purdue-sso.js";
7
7
  import { log } from "../utils/logger.js";
8
+ import { UnsupportedAuthenticationError } from "./sso-flow.js";
8
9
  /** SUNY campuses share one Brightspace tenant behind one Shibboleth IdP. */
9
10
  const SUNY_BRIGHTSPACE_HOST = "mylearning.suny.edu";
10
11
  const SUNY_IDP_ENTITY_ID = "https://idm.suny.edu/shibboleth/idp/";
@@ -51,34 +52,31 @@ export class SunySSOFlow {
51
52
  this.defaultFlow = new PurdueSSOFlow({
52
53
  username: config.username,
53
54
  password: config.password,
55
+ baseUrl: `https://${SUNY_BRIGHTSPACE_HOST}`,
56
+ headless: config.headless,
57
+ requestMfaCode: config.requestMfaCode,
54
58
  });
55
59
  }
56
60
  hasCredentials() {
57
61
  return this.defaultFlow.hasCredentials();
58
62
  }
63
+ async prepareLogin(page) {
64
+ await this.startSamlLogin(page);
65
+ await this.selectCampus(page);
66
+ }
67
+ async identifyAccount(page) {
68
+ return this.defaultFlow.identifyAccount(page);
69
+ }
59
70
  async login(page) {
60
71
  try {
61
72
  await this.startSamlLogin(page);
62
73
  await this.selectCampus(page);
63
74
  }
64
75
  catch (error) {
65
- log("ERROR", "SUNY campus selection failed", error);
66
- return false;
76
+ throw new UnsupportedAuthenticationError("SUNY campus selection could not complete headlessly. Run brightspace-mcp-server setup --suny and select a campus.", error);
67
77
  }
68
78
  return this.defaultFlow.login(page);
69
79
  }
70
- async manualLogin(page) {
71
- try {
72
- // Clear the shadow-DOM selector and, when configured, the campus
73
- // dropdown, so the user lands directly on their campus sign-in page.
74
- await this.startSamlLogin(page);
75
- await this.selectCampus(page);
76
- }
77
- catch (error) {
78
- log("DEBUG", "Could not pre-fill SUNY campus selection", error);
79
- }
80
- return this.defaultFlow.manualLogin(page);
81
- }
82
80
  /**
83
81
  * Brightspace's own campus selector renders its buttons in a shadow DOM, so
84
82
  * go straight to the SAML endpoint that selector would have reached.
@@ -88,7 +86,7 @@ export class SunySSOFlow {
88
86
  if (!currentUrl.includes("/d2l/login"))
89
87
  return;
90
88
  const origin = new URL(currentUrl).origin;
91
- log("INFO", "Campus selector detected — navigating directly to SUNY's SAML endpoint");
89
+ log("INFO", "Campus selector detected : navigating directly to SUNY's SAML endpoint");
92
90
  await page.goto(`${origin}/d2l/lp/auth/saml/initiate-login?entityId=${SUNY_IDP_ENTITY_ID}`, { waitUntil: "domcontentloaded", timeout: 30000 });
93
91
  }
94
92
  /**
@@ -104,7 +102,7 @@ export class SunySSOFlow {
104
102
  await page.waitForSelector(SELECTORS.campusSelect, { timeout: 45000 });
105
103
  }
106
104
  catch {
107
- log("DEBUG", "No SUNY campus dropdown found — continuing to the sign-in form");
105
+ log("DEBUG", "No SUNY campus dropdown found : continuing to the sign-in form");
108
106
  return;
109
107
  }
110
108
  const options = await page.$$eval(`${SELECTORS.campusSelect} option`, (nodes) => nodes
@@ -114,16 +112,11 @@ export class SunySSOFlow {
114
112
  }))
115
113
  .filter((option) => option.value !== ""));
116
114
  if (!this.config.campus) {
117
- log("WARN", "No campus configured — choose your campus in the browser window.");
118
- log("INFO", "Set one with: brightspace-mcp-server setup --suny");
119
- return;
115
+ throw new UnsupportedAuthenticationError("No SUNY campus configured. Run brightspace-mcp-server setup --suny.");
120
116
  }
121
117
  const match = this.findCampus(options);
122
118
  if (!match) {
123
- log("WARN", `Campus "${this.config.campus}" did not match any SUNY campus.`);
124
- log("INFO", `Available: ${options.map((o) => o.label).join(", ")}`);
125
- log("WARN", "Choose your campus in the browser window to continue.");
126
- return;
119
+ throw new UnsupportedAuthenticationError(`Configured campus did not match SUNY. Available campuses: ${options.map((o) => o.label).join(", ")}`);
127
120
  }
128
121
  log("INFO", `Selecting campus ${match.label} (${match.value})`);
129
122
  await page.selectOption(SELECTORS.campusSelect, match.value);