brightspace-mcp-server 1.6.2 → 2.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 (92) hide show
  1. package/README.md +34 -7
  2. package/build/api/client.d.ts +5 -3
  3. package/build/api/client.d.ts.map +1 -1
  4. package/build/api/client.js +71 -79
  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 +16 -0
  11. package/build/auth/auth-cooldown.d.ts.map +1 -0
  12. package/build/auth/auth-cooldown.js +47 -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 +114 -0
  17. package/build/auth/auth-lock.js.map +1 -0
  18. package/build/auth/auth-runner.d.ts +17 -7
  19. package/build/auth/auth-runner.d.ts.map +1 -1
  20. package/build/auth/auth-runner.js +122 -26
  21. package/build/auth/auth-runner.js.map +1 -1
  22. package/build/auth/browser-auth.d.ts +19 -67
  23. package/build/auth/browser-auth.d.ts.map +1 -1
  24. package/build/auth/browser-auth.js +258 -574
  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 +18 -36
  47. package/build/auth/purdue-sso.d.ts.map +1 -1
  48. package/build/auth/purdue-sso.js +165 -192
  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 +14 -3
  55. package/build/auth/sso-flow.d.ts.map +1 -1
  56. package/build/auth/sso-flow.js +16 -1
  57. package/build/auth/sso-flow.js.map +1 -1
  58. package/build/auth/suny-sso.d.ts +3 -2
  59. package/build/auth/suny-sso.d.ts.map +1 -1
  60. package/build/auth/suny-sso.js +15 -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 +29 -73
  73. package/build/auth-cli.js.map +1 -1
  74. package/build/index.js +8 -4
  75. package/build/index.js.map +1 -1
  76. package/build/setup.js +14 -8
  77. package/build/setup.js.map +1 -1
  78. package/build/types/index.d.ts +6 -0
  79. package/build/types/index.d.ts.map +1 -1
  80. package/build/utils/config-store.d.ts +1 -0
  81. package/build/utils/config-store.d.ts.map +1 -1
  82. package/build/utils/config-store.js +3 -0
  83. package/build/utils/config-store.js.map +1 -1
  84. package/build/utils/config.d.ts +2 -1
  85. package/build/utils/config.d.ts.map +1 -1
  86. package/build/utils/config.js +30 -10
  87. package/build/utils/config.js.map +1 -1
  88. package/build/utils/secure-config.d.ts +6 -0
  89. package/build/utils/secure-config.d.ts.map +1 -0
  90. package/build/utils/secure-config.js +57 -0
  91. package/build/utils/secure-config.js.map +1 -0
  92. package/package.json +4 -2
@@ -1,22 +1,16 @@
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
+ const EMAIL_SELECTORS = ["input[type=email]", "input[name=loginfmt]"];
10
+ const PASSWORD_SELECTORS = ["input[type=password]", "input[name=passwd]"];
11
+ const SUBMIT_SELECTORS = ["#idSIButton9", "input[type=submit]", "button[type=submit]"];
12
+ const FIELD_TIMEOUT_MS = 30_000;
13
+ const FIELD_POLL_MS = 250;
20
14
  /**
21
15
  * Entra's number-match digits. The tenant shows a two-digit number that has to
22
16
  * be typed into Microsoft Authenticator, and nothing else on the machine
@@ -28,12 +22,14 @@ const NUMBER_MATCH_SELECTOR = "#idRichContext_DisplaySign";
28
22
  const NUMBER_MATCH_POLL_MS = 2000;
29
23
  /** A person has to find their phone, unlock it, and read a prompt. */
30
24
  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());
25
+ /** Microsoft expects Purdue's full sign-in name, while setup also accepts a career account. */
26
+ function signInName(username, baseUrl) {
27
+ const isPurdue = baseUrl && new URL(baseUrl).hostname.toLowerCase() === "purdue.brightspace.com";
28
+ return isPurdue && !username.includes("@") ? `${username}@purdue.edu` : username;
34
29
  }
35
30
  export class PurdueSSOFlow {
36
31
  config;
32
+ accountHintSubmitted = false;
37
33
  constructor(config) {
38
34
  this.config = config;
39
35
  }
@@ -43,204 +39,195 @@ export class PurdueSSOFlow {
43
39
  hasCredentials() {
44
40
  return Boolean(this.config.username && this.config.password);
45
41
  }
42
+ async prepareLogin(page) {
43
+ await this.handleCampusSelector(page);
44
+ }
45
+ /** First half of Brightspace Bar's choreography, with no password access. */
46
+ async identifyAccount(page) {
47
+ if (!this.config.username)
48
+ return false;
49
+ const email = signInName(this.config.username, this.config.baseUrl);
50
+ if (!await this.fillWhenReady(page, EMAIL_SELECTORS, email))
51
+ return false;
52
+ if (!await this.clickWhenReady(page, SUBMIT_SELECTORS))
53
+ return false;
54
+ this.accountHintSubmitted = true;
55
+ return true;
56
+ }
46
57
  /**
47
58
  * 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.
59
+ * Handles the school selector, saved credentials, device MFA approval, and stay-signed-in.
49
60
  *
50
61
  * @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
62
+ * @returns true after reaching Brightspace home; failures are typed errors
52
63
  */
53
64
  async login(page) {
54
65
  try {
55
66
  log("INFO", "Starting SSO login flow");
56
67
  // Step 1: Handle campus selector on purdue.brightspace.com/d2l/login
57
68
  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)
69
+ // Restored Microsoft state can lead directly to MFA or stay-signed-in.
70
+ const postCredential = await this.anyVisible(page, [
71
+ NUMBER_MATCH_SELECTOR,
72
+ "#idDiv_SAOTCAS_Title",
73
+ "#idDiv_SAOTCC_Title",
74
+ "#KmsiCheckboxField",
75
+ ]);
76
+ const kmsi = await page.getByText("Stay signed in?").first().isVisible().catch(() => false);
77
+ if (!page.url().includes("/d2l/home") && !postCredential && !kmsi)
78
+ await this.enterCredentials(page);
79
+ // Wait for device approval and print Microsoft's number match.
61
80
  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
81
  return true;
68
82
  }
69
83
  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;
84
+ if (error instanceof BrowserAuthError)
85
+ throw error;
86
+ throw new UnsupportedAuthenticationError("The identity provider could not complete headless sign-in. Check saved credentials and supported MFA settings.", error);
93
87
  }
94
88
  }
95
89
  async handleCampusSelector(page) {
96
90
  const currentUrl = page.url();
97
91
  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
92
+ // Follow the live Purdue control first, as Brightspace Bar does, so a
93
+ // tenant-side destination change does not leave this client behind.
94
+ const campus = page.getByText(/Purdue West Lafayette/i).first();
95
+ if (await campus.isVisible().catch(() => false)) {
96
+ log("INFO", "Campus selector detected : selecting Purdue West Lafayette");
97
+ await campus.click();
98
+ return;
99
+ }
100
+ // Retain the known endpoint as a fallback if the control has not rendered.
100
101
  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 });
102
+ log("INFO", "Campus selector detected : navigating directly to Shibboleth IdP");
103
+ await page.goto(`${baseUrl}/d2l/lp/auth/saml/initiate-login?entityId=https://idp.purdue.edu/idp/shibboleth`, { waitUntil: "domcontentloaded", timeout: 30000 });
103
104
  }
104
- // Already on sso.purdue.edu or past the campus selector — nothing to do
105
+ // Already on sso.purdue.edu or past the campus selector : nothing to do
105
106
  }
106
107
  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");
118
- }
119
- log("INFO", "Entering credentials");
120
- const usernameField = await this.findVisible(page, usernameSelector);
121
- if (usernameField) {
122
- await usernameField.fill(this.config.username);
108
+ if (!this.config.username)
109
+ throw new BrowserAuthError("Username is required for SSO login", "credentials");
110
+ if (!this.config.password)
111
+ throw new BrowserAuthError("Password is required for SSO login", "credentials");
112
+ log("INFO", "Entering credentials");
113
+ if (!this.accountHintSubmitted) {
114
+ const email = signInName(this.config.username, this.config.baseUrl);
115
+ if (!await this.fillWhenReady(page, EMAIL_SELECTORS, email)) {
116
+ throw new UnsupportedAuthenticationError("The Microsoft email field did not appear. Headless sign-in cannot continue.");
123
117
  }
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);
118
+ if (!await this.clickWhenReady(page, SUBMIT_SELECTORS)) {
119
+ throw new UnsupportedAuthenticationError("The Microsoft email submit button did not appear. Headless sign-in cannot continue.");
132
120
  }
133
- const submitButton = await this.findVisible(page, SELECTORS.formSubmit);
134
- if (submitButton) {
135
- await submitButton.click();
136
- }
137
- else {
138
- // Fallback: just hit Enter on the password field
139
- await passwordField?.press('Enter');
140
- }
141
- await page.waitForLoadState("networkidle");
142
121
  }
143
- catch (error) {
144
- log("WARN", "Automated credentials entry failed, will fallback to manual login.", error);
145
- throw error;
122
+ this.accountHintSubmitted = false;
123
+ if (!await this.fillWhenReady(page, PASSWORD_SELECTORS, this.config.password)) {
124
+ throw new UnsupportedAuthenticationError("The Microsoft password field did not appear. Headless sign-in cannot continue.");
146
125
  }
147
- }
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;
126
+ if (!await this.clickWhenReady(page, SUBMIT_SELECTORS)) {
127
+ throw new UnsupportedAuthenticationError("The Microsoft password submit button did not appear. Headless sign-in cannot continue.");
160
128
  }
161
- return null;
162
129
  }
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;
130
+ /** Ported from Brightspace Bar's proven four-step Entra choreography. */
131
+ async actWhenReady(page, selectors, act) {
132
+ const deadline = Date.now() + FIELD_TIMEOUT_MS;
133
+ do {
134
+ for (const selector of selectors) {
135
+ const target = page.locator(selector).first();
136
+ if (await target.isVisible().catch(() => false)) {
137
+ await act(target);
138
+ return true;
139
+ }
140
+ }
141
+ await page.waitForTimeout(FIELD_POLL_MS);
142
+ } while (Date.now() < deadline);
143
+ return false;
144
+ }
145
+ async fillWhenReady(page, selectors, value) {
146
+ return this.actWhenReady(page, selectors, target => target.fill(value));
147
+ }
148
+ async clickWhenReady(page, selectors) {
149
+ // Entra often detaches the button after the click has already navigated.
150
+ return this.actWhenReady(page, selectors, target => target.click().catch(() => { }));
151
+ }
152
+ /** Match Brightspace Bar's selector loop instead of trusting the first DOM match. */
153
+ async anyVisible(page, selectors) {
154
+ for (const selector of selectors) {
155
+ if (await page.locator(selector).first().isVisible().catch(() => false))
156
+ return true;
186
157
  }
187
- log("WARN", "Sign-in page did not appear after submitting the account name");
158
+ return false;
188
159
  }
160
+ /** Brightspace Bar's bounded number/auth/KMSI polling loop. */
189
161
  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);
162
+ if (!this.config.baseUrl) {
163
+ throw new UnsupportedAuthenticationError("A school URL is required to verify headless authentication.");
164
+ }
165
+ const deadline = Date.now() + MFA_TIMEOUT_MS;
166
+ let challenged = false;
167
+ let announced = null;
198
168
  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()}`);
169
+ while (Date.now() < deadline) {
170
+ const number = await this.readNumberMatch(page);
171
+ const challengeVisible = number !== null ||
172
+ await page.locator("#idDiv_SAOTCAS_Title").first().isVisible().catch(() => false) ||
173
+ await page.locator("#idDiv_SAOTCC_Title").first().isVisible().catch(() => false);
174
+ if (challengeVisible && !challenged) {
175
+ challenged = true;
176
+ log("WARN", "Waiting up to 5 minutes for Microsoft MFA approval on your device.");
177
+ }
178
+ if (number && number !== announced) {
179
+ announced = number;
180
+ log("WARN", `Number match: ${number}. Enter it in Microsoft Authenticator.`);
181
+ }
182
+ if (await this.isAuthenticated(page)) {
183
+ log("INFO", "Login successful - verified Brightspace home");
184
+ return;
185
+ }
186
+ await this.clickProvenKmsi(page);
187
+ await page.waitForTimeout(NUMBER_MATCH_POLL_MS);
188
+ }
214
189
  }
215
190
  catch (error) {
216
- throw new BrowserAuthError("MFA approval timed out after 5 minutes", "mfa_approval", error);
191
+ if (challenged)
192
+ throw new MfaApprovalError(error);
193
+ throw new UnsupportedAuthenticationError("Headless sign-in stopped before a supported MFA challenge completed.", error);
217
194
  }
218
- finally {
219
- pending.done = true;
220
- release();
221
- await watcher;
195
+ if (challenged)
196
+ throw new MfaApprovalError();
197
+ throw new UnsupportedAuthenticationError("Sign-in did not reach a supported MFA challenge or Brightspace within 5 minutes.");
198
+ }
199
+ /** The login shell also exposes D2L.LP, so verify origin and home as well. */
200
+ async isAuthenticated(page) {
201
+ try {
202
+ const expected = new URL(this.config.baseUrl);
203
+ const current = new URL(page.url());
204
+ if (current.origin !== expected.origin || !/^\/d2l\/home(?:\/|$)/.test(current.pathname))
205
+ return false;
206
+ const cookies = await page.context().cookies(expected.origin);
207
+ if (!cookies.some(cookie => cookie.name === "d2lSessionVal" && Boolean(cookie.value)))
208
+ return false;
209
+ return await page.evaluate(() => {
210
+ const d2l = window.D2L;
211
+ return Boolean(d2l?.LP);
212
+ });
213
+ }
214
+ catch {
215
+ // Redirects can replace the execution context. Keep polling; this
216
+ // verdict never causes credentials to be entered a second time.
217
+ return false;
222
218
  }
223
219
  }
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
- ]);
220
+ async clickProvenKmsi(page) {
221
+ if (new URL(page.url()).hostname !== "login.microsoftonline.com")
222
+ return;
223
+ const proven = await page.locator("#KmsiCheckboxField").first().isVisible().catch(() => false) ||
224
+ await page.getByText("Stay signed in?").first().isVisible().catch(() => false);
225
+ if (!proven)
226
+ return;
227
+ const yes = page.locator("#idSIButton9").first();
228
+ if (await yes.isVisible().catch(() => false)) {
229
+ await yes.click().catch(() => { });
230
+ log("DEBUG", 'Clicked Yes on "Stay signed in?"');
244
231
  }
245
232
  }
246
233
  /** The digits on screen, or null when Entra is not showing any. */
@@ -251,22 +238,8 @@ export class PurdueSSOFlow {
251
238
  if (!(await sign.isVisible().catch(() => false)))
252
239
  return null;
253
240
  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
- }
241
+ const number = text?.trim();
242
+ return number && /^\d{1,3}$/.test(number) ? number : null;
270
243
  }
271
244
  }
272
245
  //# 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"}