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,22 +1,17 @@
1
1
  /**
2
2
  * Purdue 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 { BrowserAuthError } from "../utils/errors.js";
7
7
  import { log } from "../utils/logger.js";
8
- const SELECTORS = {
9
- usernameInput: "input#username",
10
- passwordInput: "input#password",
11
- submitButton: 'button[name="_eventId_proceed"]',
12
- staySignedInYes: "input[type=submit][value='Yes']",
13
- // Purdue's proceed button, D2L's own, and the generic submit used by Entra
14
- // (input#idSIButton9) and most other identity providers.
15
- formSubmit: 'button[name="_eventId_proceed"], button.d2l-button, input[type="submit"]',
16
- };
17
- // Entra labels the account-name button "Next" and only shows "Sign in" on the
18
- // view that follows. Both are input#idSIButton9, so the label tells them apart.
19
- const ACCOUNT_NAME_BUTTON = /^next$/i;
8
+ import { MfaApprovalError, UnsupportedAuthenticationError } from "./sso-flow.js";
9
+ import { AUTH_COMMAND } from "../utils/commands.js";
10
+ const EMAIL_SELECTORS = ["input[type=email]", "input[name=loginfmt]"];
11
+ const PASSWORD_SELECTORS = ["input[type=password]", "input[name=passwd]"];
12
+ const SUBMIT_SELECTORS = ["#idSIButton9", "input[type=submit]", "button[type=submit]"];
13
+ const FIELD_TIMEOUT_MS = 30_000;
14
+ const FIELD_POLL_MS = 250;
20
15
  /**
21
16
  * Entra's number-match digits. The tenant shows a two-digit number that has to
22
17
  * be typed into Microsoft Authenticator, and nothing else on the machine
@@ -24,16 +19,22 @@ const ACCOUNT_NAME_BUTTON = /^next$/i;
24
19
  * logged. Plain DOM text, no OCR.
25
20
  */
26
21
  const NUMBER_MATCH_SELECTOR = "#idRichContext_DisplaySign";
22
+ const MFA_CODE_SELECTORS = ["#idTxtBx_SAOTCC_OTC", 'input[name="otc"]'];
23
+ const MFA_CODE_SUBMIT_SELECTORS = ["#idSubmit_SAOTCC_Continue", "#idSIButton9"];
27
24
  /** How often to look for the number while waiting on MFA. */
28
25
  const NUMBER_MATCH_POLL_MS = 2000;
29
26
  /** A person has to find their phone, unlock it, and read a prompt. */
30
27
  const MFA_TIMEOUT_MS = 5 * 60 * 1000;
31
- /** Visible text of a button, or the value of a submit input. */
32
- async function controlLabel(handle) {
33
- return handle.evaluate((el) => (el.value || el.textContent || "").trim());
28
+ /** Microsoft expects Purdue's full sign-in name, while setup also accepts a career account. */
29
+ function signInName(username, baseUrl) {
30
+ const isPurdue = baseUrl && new URL(baseUrl).hostname.toLowerCase() === "purdue.brightspace.com";
31
+ return isPurdue && !username.includes("@") ? `${username}@purdue.edu` : username;
34
32
  }
35
33
  export class PurdueSSOFlow {
36
34
  config;
35
+ accountHintSubmitted = false;
36
+ /** One authenticator code per login. See submitMfaCode. */
37
+ mfaCodeSubmitted = false;
37
38
  constructor(config) {
38
39
  this.config = config;
39
40
  }
@@ -43,204 +44,236 @@ export class PurdueSSOFlow {
43
44
  hasCredentials() {
44
45
  return Boolean(this.config.username && this.config.password);
45
46
  }
47
+ async prepareLogin(page) {
48
+ await this.handleCampusSelector(page);
49
+ }
50
+ /** First half of Brightspace Bar's choreography, with no password access. */
51
+ async identifyAccount(page) {
52
+ if (!this.config.username)
53
+ return false;
54
+ const email = signInName(this.config.username, this.config.baseUrl);
55
+ if (!await this.fillWhenReady(page, EMAIL_SELECTORS, email))
56
+ return false;
57
+ if (!await this.clickWhenReady(page, SUBMIT_SELECTORS))
58
+ return false;
59
+ this.accountHintSubmitted = true;
60
+ return true;
61
+ }
46
62
  /**
47
63
  * Execute the complete Microsoft Entra ID SSO login flow for Purdue.
48
- * Handles institution selector, email/password entry, MFA (TOTP or manual), and "stay signed in" prompt.
64
+ * Handles the school selector, saved credentials, device MFA approval, and stay-signed-in.
49
65
  *
50
66
  * @param page - Playwright page instance (already navigated to Brightspace or redirected to login)
51
- * @returns true on successful login (URL contains /d2l/home), false on timeout/failure
67
+ * @returns true after reaching Brightspace home; failures are typed errors
52
68
  */
53
69
  async login(page) {
54
70
  try {
55
71
  log("INFO", "Starting SSO login flow");
56
72
  // Step 1: Handle campus selector on purdue.brightspace.com/d2l/login
57
73
  await this.handleCampusSelector(page);
58
- // Step 2: Enter username + password on sso.purdue.edu (Shibboleth)
59
- await this.enterCredentials(page);
60
- // Step 3: Handle MFA (TOTP automated or manual approval)
74
+ // Restored Microsoft state can lead directly to MFA or stay-signed-in.
75
+ const postCredential = await this.anyVisible(page, [
76
+ NUMBER_MATCH_SELECTOR,
77
+ "#idDiv_SAOTCAS_Title",
78
+ "#idDiv_SAOTCC_Title",
79
+ "#KmsiCheckboxField",
80
+ ]);
81
+ const kmsi = await page.getByText("Stay signed in?").first().isVisible().catch(() => false);
82
+ if (!page.url().includes("/d2l/home") && !postCredential && !kmsi)
83
+ await this.enterCredentials(page);
84
+ // Wait for device approval and print Microsoft's number match.
61
85
  await this.handleMFA(page);
62
- // Step 4: Handle "Stay signed in?" prompt
63
- await this.handleStaySignedIn(page);
64
- // Step 5: Wait for successful redirect to Brightspace home
65
- await page.waitForURL(/\/d2l\/home/, { timeout: 120000 });
66
- log("INFO", "Login successful - reached Brightspace home");
67
86
  return true;
68
87
  }
69
88
  catch (error) {
70
- log("ERROR", "SSO login flow failed", error);
71
- return false;
72
- }
73
- }
74
- /**
75
- * Manual login fallback: let the user type credentials and complete MFA themselves.
76
- * The browser stays open in headed mode while we wait for /d2l/home.
77
- */
78
- async manualLogin(page) {
79
- try {
80
- log("INFO", "Starting manual login flow (no saved credentials)");
81
- log("INFO", "Please log in using the browser window that just opened.");
82
- // Navigate past the campus selector so the user lands on the Shibboleth form
83
- await this.handleCampusSelector(page);
84
- // Wait up to 5 minutes for the user to complete login manually
85
- log("INFO", "Waiting up to 5 minutes for you to complete login and MFA...");
86
- await page.waitForURL(/\/d2l\/home/, { timeout: 300000 });
87
- log("INFO", "Manual login successful - reached Brightspace home");
88
- return true;
89
- }
90
- catch (error) {
91
- log("ERROR", "Manual login flow failed or timed out", error);
92
- return false;
89
+ if (error instanceof BrowserAuthError)
90
+ throw error;
91
+ throw new UnsupportedAuthenticationError("The identity provider could not complete headless sign-in. Check saved credentials and supported MFA settings.", error);
93
92
  }
94
93
  }
95
94
  async handleCampusSelector(page) {
96
95
  const currentUrl = page.url();
97
96
  if (currentUrl.includes("purdue.brightspace.com") && currentUrl.includes("/d2l/login")) {
98
- // Campus selector buttons are inside a shadow DOM — navigate directly
99
- // to Purdue's Shibboleth SAML endpoint instead of clicking them
97
+ // Follow the live Purdue control first, as Brightspace Bar does, so a
98
+ // tenant-side destination change does not leave this client behind.
99
+ const campus = page.getByText(/Purdue West Lafayette/i).first();
100
+ if (await campus.isVisible().catch(() => false)) {
101
+ log("INFO", "Campus selector detected : selecting Purdue West Lafayette");
102
+ await campus.click();
103
+ return;
104
+ }
105
+ // Retain the known endpoint as a fallback if the control has not rendered.
100
106
  const baseUrl = new URL(currentUrl).origin;
101
- log("INFO", "Campus selector detected — navigating directly to Shibboleth IdP");
102
- await page.goto(`${baseUrl}/d2l/lp/auth/saml/initiate-login?entityId=https://idp.purdue.edu/idp/shibboleth`, { waitUntil: "networkidle", timeout: 30000 });
107
+ log("INFO", "Campus selector detected : navigating directly to Shibboleth IdP");
108
+ await page.goto(`${baseUrl}/d2l/lp/auth/saml/initiate-login?entityId=https://idp.purdue.edu/idp/shibboleth`, { waitUntil: "domcontentloaded", timeout: 30000 });
103
109
  }
104
- // Already on sso.purdue.edu or past the campus selector — nothing to do
110
+ // Already on sso.purdue.edu or past the campus selector : nothing to do
105
111
  }
106
112
  async enterCredentials(page) {
107
- try {
108
- log("DEBUG", "Waiting for login form");
109
- // Wait for either Purdue's username or Albany's userName (or typical email fields)
110
- // Use a shorter timeout so it falls back to manual login quickly if unrecognized
111
- const usernameSelector = 'input#username, input#userName, input[type="email"]';
112
- await page.waitForSelector(usernameSelector, { timeout: 10000 });
113
- if (!this.config.username) {
114
- throw new BrowserAuthError("Username is required for SSO login", "credentials");
115
- }
116
- if (!this.config.password) {
117
- throw new BrowserAuthError("Password is required for SSO login", "credentials");
113
+ if (!this.config.username)
114
+ throw new BrowserAuthError("Username is required for SSO login", "credentials");
115
+ if (!this.config.password)
116
+ throw new BrowserAuthError("Password is required for SSO login", "credentials");
117
+ log("INFO", "Entering credentials");
118
+ if (!this.accountHintSubmitted) {
119
+ const email = signInName(this.config.username, this.config.baseUrl);
120
+ if (!await this.fillWhenReady(page, EMAIL_SELECTORS, email)) {
121
+ throw new UnsupportedAuthenticationError("The Microsoft email field did not appear. Headless sign-in cannot continue.");
118
122
  }
119
- log("INFO", "Entering credentials");
120
- const usernameField = await this.findVisible(page, usernameSelector);
121
- if (usernameField) {
122
- await usernameField.fill(this.config.username);
123
+ if (!await this.clickWhenReady(page, SUBMIT_SELECTORS)) {
124
+ throw new UnsupportedAuthenticationError("The Microsoft email submit button did not appear. Headless sign-in cannot continue.");
123
125
  }
124
- // Microsoft Entra puts a password box on its account-name page as well,
125
- // but the button there only advances to the next view. Submit the account
126
- // name first so the password lands on the page that actually signs in.
127
- await this.submitAccountName(page);
128
- const passwordSelector = 'input#password, input[type="password"]';
129
- const passwordField = await this.findVisible(page, passwordSelector);
130
- if (passwordField) {
131
- await passwordField.fill(this.config.password);
132
- }
133
- const submitButton = await this.findVisible(page, SELECTORS.formSubmit);
134
- if (submitButton) {
135
- await submitButton.click();
126
+ }
127
+ this.accountHintSubmitted = false;
128
+ if (!await this.fillWhenReady(page, PASSWORD_SELECTORS, this.config.password)) {
129
+ throw new UnsupportedAuthenticationError("The Microsoft password field did not appear. Headless sign-in cannot continue.");
130
+ }
131
+ if (!await this.clickWhenReady(page, SUBMIT_SELECTORS)) {
132
+ throw new UnsupportedAuthenticationError("The Microsoft password submit button did not appear. Headless sign-in cannot continue.");
133
+ }
134
+ }
135
+ /** Ported from Brightspace Bar's proven four-step Entra choreography. */
136
+ async actWhenReady(page, selectors, act) {
137
+ const deadline = Date.now() + FIELD_TIMEOUT_MS;
138
+ do {
139
+ for (const selector of selectors) {
140
+ const target = page.locator(selector).first();
141
+ if (await target.isVisible().catch(() => false)) {
142
+ await act(target);
143
+ return true;
144
+ }
136
145
  }
137
- else {
138
- // Fallback: just hit Enter on the password field
139
- await passwordField?.press('Enter');
146
+ await page.waitForTimeout(FIELD_POLL_MS);
147
+ } while (Date.now() < deadline);
148
+ return false;
149
+ }
150
+ async fillWhenReady(page, selectors, value) {
151
+ return this.actWhenReady(page, selectors, target => target.fill(value));
152
+ }
153
+ async clickWhenReady(page, selectors) {
154
+ // Entra often detaches the button after the click has already navigated.
155
+ return this.actWhenReady(page, selectors, target => target.click().catch(() => { }));
156
+ }
157
+ /** Match Brightspace Bar's selector loop instead of trusting the first DOM match. */
158
+ async anyVisible(page, selectors) {
159
+ for (const selector of selectors) {
160
+ if (await page.locator(selector).first().isVisible().catch(() => false))
161
+ return true;
162
+ }
163
+ return false;
164
+ }
165
+ /** Brightspace Bar's bounded number/auth/KMSI polling loop. */
166
+ async handleMFA(page) {
167
+ if (!this.config.baseUrl) {
168
+ throw new UnsupportedAuthenticationError("A school URL is required to verify headless authentication.");
169
+ }
170
+ const deadline = Date.now() + MFA_TIMEOUT_MS;
171
+ let challenged = false;
172
+ let announced = null;
173
+ try {
174
+ while (Date.now() < deadline) {
175
+ if (await this.submitMfaCode(page))
176
+ challenged = true;
177
+ const number = await this.readNumberMatch(page);
178
+ const challengeVisible = number !== null ||
179
+ await page.locator("#idDiv_SAOTCAS_Title").first().isVisible().catch(() => false) ||
180
+ await page.locator("#idDiv_SAOTCC_Title").first().isVisible().catch(() => false);
181
+ if (challengeVisible && !challenged) {
182
+ challenged = true;
183
+ log("WARN", "Waiting up to 5 minutes for Microsoft MFA approval on your device.");
184
+ }
185
+ if (number && number !== announced) {
186
+ announced = number;
187
+ log("WARN", `Number match: ${number}. Enter it in Microsoft Authenticator.`);
188
+ }
189
+ if (await this.isAuthenticated(page)) {
190
+ log("INFO", "Login successful - verified Brightspace home");
191
+ return;
192
+ }
193
+ await this.clickProvenKmsi(page);
194
+ await page.waitForTimeout(NUMBER_MATCH_POLL_MS);
140
195
  }
141
- await page.waitForLoadState("networkidle");
142
196
  }
143
197
  catch (error) {
144
- log("WARN", "Automated credentials entry failed, will fallback to manual login.", error);
145
- throw error;
198
+ if (error instanceof BrowserAuthError)
199
+ throw error;
200
+ if (challenged)
201
+ throw new MfaApprovalError(error);
202
+ throw new UnsupportedAuthenticationError("Headless sign-in stopped before a supported MFA challenge completed.", error);
146
203
  }
204
+ if (challenged)
205
+ throw new MfaApprovalError();
206
+ throw new UnsupportedAuthenticationError("Sign-in did not reach a supported MFA challenge or Brightspace within 5 minutes.");
147
207
  }
148
- /**
149
- * Resolve a control only when it is actually on screen.
150
- *
151
- * Entra is a single-page app: the account-name view stays in the DOM once
152
- * the password view replaces it, so a plain page.$() returns the hidden
153
- * first-page input or button that precedes the live one in document order.
154
- * Filling or clicking those stalls on Playwright's actionability wait.
155
- */
156
- async findVisible(page, selector) {
157
- for (const handle of await page.$$(selector)) {
158
- if (await handle.isVisible())
159
- return handle;
208
+ async submitMfaCode(page) {
209
+ const input = await this.firstVisible(page, MFA_CODE_SELECTORS);
210
+ if (!input)
211
+ return false;
212
+ if (this.config.headless === false)
213
+ return false;
214
+ // Ask once per login. This runs on every two-second poll, and Microsoft
215
+ // commonly leaves the field on screen while it validates, so without this
216
+ // a correct code gets a second prompt on the next tick. That prompt blocks
217
+ // on stdin, and the deadline is only checked between iterations, so the
218
+ // five-minute budget can never fire while parked there.
219
+ if (this.mfaCodeSubmitted)
220
+ return false;
221
+ if (!this.config.requestMfaCode) {
222
+ throw new UnsupportedAuthenticationError(`This MFA method requires a code. Run \`${AUTH_COMMAND}\` in a terminal to enter it.`);
160
223
  }
161
- return null;
224
+ this.mfaCodeSubmitted = true;
225
+ const code = await this.config.requestMfaCode();
226
+ if (!/^\d{6,8}$/.test(code))
227
+ throw new UnsupportedAuthenticationError("The MFA code must contain 6-8 digits.");
228
+ await input.fill(code);
229
+ const submit = await this.firstVisible(page, MFA_CODE_SUBMIT_SELECTORS);
230
+ if (submit)
231
+ await submit.click();
232
+ else
233
+ await input.press("Enter");
234
+ log("INFO", "Authenticator code submitted");
235
+ return true;
162
236
  }
163
- /**
164
- * Advance Microsoft Entra's account-name page.
165
- *
166
- * That page carries a password box of its own, and Playwright reports it as
167
- * visible, so presence cannot tell it apart from a single-page form. The
168
- * button label can: it reads "Next" there and "Sign in" on the view that
169
- * follows. Filling the password against the account-name page leaves the
170
- * browser parked with both fields populated and nothing submitted.
171
- */
172
- async submitAccountName(page) {
173
- const submit = await this.findVisible(page, SELECTORS.formSubmit);
174
- if (!submit)
175
- return;
176
- if (!ACCOUNT_NAME_BUTTON.test(await controlLabel(submit)))
177
- return;
178
- log("INFO", "Account-name page detected — submitting account name");
179
- await submit.click();
180
- // The sign-in view has arrived once the button stops saying "Next".
181
- for (let attempt = 0; attempt < 30; attempt += 1) {
182
- await page.waitForTimeout(500);
183
- const current = await this.findVisible(page, SELECTORS.formSubmit);
184
- if (current && !ACCOUNT_NAME_BUTTON.test(await controlLabel(current)))
185
- return;
237
+ async firstVisible(page, selectors) {
238
+ for (const selector of selectors) {
239
+ const target = page.locator(selector).first();
240
+ if (await target.isVisible().catch(() => false))
241
+ return target;
186
242
  }
187
- log("WARN", "Sign-in page did not appear after submitting the account name");
243
+ return null;
188
244
  }
189
- async handleMFA(page) {
190
- // The number is only on screen while MFA is pending, so the scrape has to
191
- // run alongside the wait rather than before or after it.
192
- const pending = { done: false };
193
- let release = () => { };
194
- const released = new Promise((resolve) => {
195
- release = resolve;
196
- });
197
- const watcher = this.watchNumberMatch(page, pending, released);
245
+ /** The login shell also exposes D2L.LP, so verify origin and home as well. */
246
+ async isAuthenticated(page) {
198
247
  try {
199
- log("WARN", "Waiting for Microsoft MFA approval on your device...");
200
- log("INFO", "Timeout: 5 minutes");
201
- log("INFO", "Approve the sign-in request in Microsoft Authenticator. If it shows a number, it is printed below.");
202
- // Wait for MFA approval by watching for the post-MFA redirect.
203
- // Using waitForURL instead of networkidle because networkidle fires
204
- // after 500ms of no network activity, which can happen while the MFA
205
- // page UI finishes loading but before the user approves the request.
206
- await page.waitForURL((url) => {
207
- const href = url.toString();
208
- return href.includes("/d2l/") ||
209
- href.includes("kmsi") ||
210
- href.includes("/sso/") ||
211
- href.includes("SAMLResponse");
212
- }, { timeout: MFA_TIMEOUT_MS });
213
- log("INFO", `MFA completed - redirected to: ${page.url()}`);
248
+ const expected = new URL(this.config.baseUrl);
249
+ const current = new URL(page.url());
250
+ if (current.origin !== expected.origin || !/^\/d2l\/home(?:\/|$)/.test(current.pathname))
251
+ return false;
252
+ const cookies = await page.context().cookies(expected.origin);
253
+ if (!cookies.some(cookie => cookie.name === "d2lSessionVal" && Boolean(cookie.value)))
254
+ return false;
255
+ return await page.evaluate(() => {
256
+ const d2l = window.D2L;
257
+ return Boolean(d2l?.LP);
258
+ });
214
259
  }
215
- catch (error) {
216
- throw new BrowserAuthError("MFA approval timed out after 5 minutes", "mfa_approval", error);
217
- }
218
- finally {
219
- pending.done = true;
220
- release();
221
- await watcher;
260
+ catch {
261
+ // Redirects can replace the execution context. Keep polling; this
262
+ // verdict never causes credentials to be entered a second time.
263
+ return false;
222
264
  }
223
265
  }
224
- /**
225
- * Log Entra's number-match digits until the MFA wait is over.
226
- *
227
- * Only a CHANGED value is logged: Entra re-mints the number whenever the
228
- * user asks for a new request, and repeating the same digits every two
229
- * seconds would bury everything else in the log.
230
- */
231
- async watchNumberMatch(page, pending, released) {
232
- let last = null;
233
- while (!pending.done) {
234
- const number = await this.readNumberMatch(page);
235
- if (number && number !== last) {
236
- last = number;
237
- log("WARN", `Number match: ${number}. Enter it in Microsoft Authenticator.`);
238
- }
239
- // Racing the release keeps the wait from outliving the MFA it watches.
240
- await Promise.race([
241
- page.waitForTimeout(NUMBER_MATCH_POLL_MS).catch(() => { }),
242
- released,
243
- ]);
266
+ async clickProvenKmsi(page) {
267
+ if (new URL(page.url()).hostname !== "login.microsoftonline.com")
268
+ return;
269
+ const proven = await page.locator("#KmsiCheckboxField").first().isVisible().catch(() => false) ||
270
+ await page.getByText("Stay signed in?").first().isVisible().catch(() => false);
271
+ if (!proven)
272
+ return;
273
+ const yes = page.locator("#idSIButton9").first();
274
+ if (await yes.isVisible().catch(() => false)) {
275
+ await yes.click().catch(() => { });
276
+ log("DEBUG", 'Clicked Yes on "Stay signed in?"');
244
277
  }
245
278
  }
246
279
  /** The digits on screen, or null when Entra is not showing any. */
@@ -251,22 +284,8 @@ export class PurdueSSOFlow {
251
284
  if (!(await sign.isVisible().catch(() => false)))
252
285
  return null;
253
286
  const text = await sign.textContent().catch(() => null);
254
- return text?.trim() || null;
255
- }
256
- async handleStaySignedIn(page) {
257
- try {
258
- log("DEBUG", "Checking for 'Stay signed in?' prompt");
259
- const staySignedInButton = await page.waitForSelector(SELECTORS.staySignedInYes, { timeout: 10000 });
260
- if (staySignedInButton) {
261
- log("INFO", "Clicking 'Yes' on 'Stay signed in?' prompt");
262
- await staySignedInButton.click();
263
- await page.waitForLoadState("networkidle");
264
- }
265
- }
266
- catch (error) {
267
- // Prompt may not appear - this is normal
268
- log("DEBUG", "No 'Stay signed in?' prompt found (this is normal)");
269
- }
287
+ const number = text?.trim();
288
+ return number && /^\d{1,3}$/.test(number) ? number : null;
270
289
  }
271
290
  }
272
291
  //# sourceMappingURL=purdue-sso.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"purdue-sso.js","sourceRoot":"","sources":["../../src/auth/purdue-sso.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"purdue-sso.js","sourceRoot":"","sources":["../../src/auth/purdue-sso.ts"],"names":[],"mappings":"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"}
@@ -1,50 +1,28 @@
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 type { TokenData } from "../types/index.js";
7
- /**
8
- * SessionStore manages encrypted token persistence to disk.
9
- * Uses AES-256-GCM for encryption with a key derived from username + hostname.
10
- */
7
+ import { type SecureStoreOptions } from "./encrypted-store.js";
8
+ /** AES-GCM session persistence with a random key held by the native secret store. */
11
9
  export declare class SessionStore {
10
+ private readonly options;
12
11
  private readonly sessionDir;
13
12
  private readonly sessionFilePath;
14
- constructor(sessionDir?: string);
15
- /**
16
- * Get or create a random salt unique to this installation.
17
- * Stored at ~/.d2l-session/salt with restricted permissions.
18
- */
19
- private getOrCreateSalt;
20
- /**
21
- * Derive AES-256 key from username and hostname using scrypt.
22
- * Uses a per-installation random salt to prevent precomputation attacks.
23
- */
24
- private deriveKey;
25
- /**
26
- * Encrypt plaintext using AES-256-GCM.
27
- * Returns IV, auth tag, and ciphertext as hex strings.
28
- */
29
- private encrypt;
30
- /**
31
- * Decrypt ciphertext using AES-256-GCM.
32
- * Returns plaintext string, or throws if auth tag verification fails.
33
- */
34
- private decrypt;
35
- /**
36
- * Save token to disk with encryption.
37
- * Creates session directory if it doesn't exist.
38
- */
13
+ constructor(sessionDir?: string, options?: SecureStoreOptions);
14
+ private withWriteLock;
15
+ private readFile;
16
+ private decode;
17
+ private loadUnlocked;
18
+ private saveUnlocked;
19
+ private storeError;
39
20
  save(token: TokenData): Promise<void>;
40
- /**
41
- * Load token from disk with decryption.
42
- * Returns null if file doesn't exist or is corrupted (graceful degradation).
43
- */
21
+ /** Missing files return null. Damaged or inaccessible secrets fail without MFA. */
44
22
  load(): Promise<TokenData | null>;
45
- /**
46
- * Clear session by deleting the session file.
47
- */
48
23
  clear(): Promise<void>;
24
+ /** Prevent a slow mint from replacing credentials saved by a newer login. */
25
+ saveIfCurrent(token: TokenData, expected: TokenData | null): Promise<boolean>;
26
+ clearIfCurrent(expected: TokenData): Promise<boolean>;
49
27
  }
50
28
  //# sourceMappingURL=session-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/auth/session-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,KAAK,EAAE,SAAS,EAA8B,MAAM,mBAAmB,CAAC;AAgB/E;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAE7B,UAAU,CAAC,EAAE,MAAM;IAK/B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAmBvB;;;OAGG;IACH,OAAO,CAAC,SAAS;IAcjB;;;OAGG;IACH,OAAO,CAAC,OAAO;IAkBf;;;OAGG;IACH,OAAO,CAAC,OAAO;IAcf;;;OAGG;IACG,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC3C;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAuCvC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAa7B"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/auth/session-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,mBAAmB,CAAC;AAIhE,OAAO,EAAsF,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AA6BnJ,qFAAqF;AACrF,qBAAa,YAAY;IAIuB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHtE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAE7B,UAAU,SAAsB,EAAmB,OAAO,GAAE,kBAAuB;YAKjF,aAAa;YAmBb,QAAQ;YASR,MAAM;YAyBN,YAAY;YAQZ,YAAY;IAK1B,OAAO,CAAC,UAAU;IAKZ,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAY3C,mFAAmF;IAC7E,IAAI,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAWjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B,6EAA6E;IACvE,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAY7E,cAAc,CAAC,QAAQ,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;CAW5D"}