History

Feat/selfhost google sso (#1834)

* feat(host-selfhost): Google sign-in with domain-allowlist admission * feat(host-selfhost): generic OIDC SSO sign-in with verified-domain admission * refactor(host-selfhost): extract SSO admission helpers, trim comments

Charlie Meyer committed 2 months ago

Commit b22d5361 parent 31a804212 files · +620 −68Browse files

Unified · Split

12 files
  1. .changeset/selfhost-sso-sign-in.md +9 −0
  2. apps/host-selfhost/package.json +1 −0
  3. apps/host-selfhost/src/auth/better-auth.ts +59 −4
  4. apps/host-selfhost/src/auth/sso.test.ts +313 −0
  5. apps/host-selfhost/src/auth/sso.ts +46 −0
  6. apps/host-selfhost/src/config.ts +79 −0
  7. apps/host-selfhost/src/system/api.ts +13 −1
  8. apps/host-selfhost/src/system/handlers.ts +10 −0
  9. apps/host-selfhost/web/auth-client.ts +3 −0
  10. apps/host-selfhost/web/auth-config.ts +29 −0
  11. apps/host-selfhost/web/login.tsx +55 −5
  12. bun.lock +3 −58
.changeset/selfhost-sso-sign-in.md+9−0
@@ -0,0 +1,9 @@
1---
2"executor": patch
3---
4
5**Self-host: bring-your-own SSO (Google, Okta, any OIDC IdP) with a verified-domain allowlist**
6
7Operators can enable a single OIDC sign-in provider on a self-hosted instance by setting `EXECUTOR_SSO_PROVIDER_ID`, `EXECUTOR_SSO_CLIENT_ID`, `EXECUTOR_SSO_CLIENT_SECRET`, and `EXECUTOR_SSO_ALLOWED_DOMAINS` (comma-separated email domains), plus `EXECUTOR_SSO_DISCOVERY_URL` for providers without a preset (`google` is preset; `EXECUTOR_SSO_PROVIDER_NAME` overrides the button label). The login page renders a "Continue with <provider>" button when configured (discovered through the new unauthenticated `GET /api/auth-config`, which returns provider id + display name only), and the MCP OAuth connect flow's login step gains the same option since it lands on the same page.
8
9The domain allowlist replaces the invite code for SSO sign-ups: a sign-in whose IdP-verified email (`email_verified`) has an allowlisted domain auto-joins the instance organization as a member; unverified emails and any other domain are refused. Enabling the provider without an allowlist is refused at boot, as is a half-configured client id/secret pair, so SSO can never silently become open registration. Email/password sign-in and invite-based signup are unchanged. The end-to-end flow (discovery → redirect → consent → callback → membership) is exercised in tests against an emulated OIDC IdP from `@executor-js/emulate`.
apps/host-selfhost/package.json+1−0
@@ -52,6 +52,7 @@ · 51 unmodified lines
52 },52 },
53 "devDependencies": {53 "devDependencies": {
54 "@effect/vitest": "catalog:",54 "@effect/vitest": "catalog:",
55 "@executor-js/emulate": "^0.14.0",
55 "@executor-js/vite-plugin": "workspace:*",56 "@executor-js/vite-plugin": "workspace:*",
56 "@tailwindcss/vite": "catalog:",57 "@tailwindcss/vite": "catalog:",
57 "@tanstack/router-plugin": "^1.167.12",58 "@tanstack/router-plugin": "^1.167.12",
apps/host-selfhost/src/auth/better-auth.ts+59−4
@@ -1,6 +1,13 @@
1import { betterAuth, type BetterAuthOptions } from "better-auth";1import { betterAuth, type BetterAuthOptions } from "better-auth";
2import { APIError } from "better-auth/api";2import { APIError } from "better-auth/api";
3import { admin, bearer, deviceAuthorization, mcp, organization } from "better-auth/plugins";3import {
4 admin,
5 bearer,
6 deviceAuthorization,
7 genericOAuth,
8 mcp,
9 organization,
10} from "better-auth/plugins";
4import { apiKey } from "@better-auth/api-key";11import { apiKey } from "@better-auth/api-key";
5import { type Client } from "@libsql/client";12import { type Client } from "@libsql/client";
6import { LibsqlDialect, type LibsqlDialectConfig } from "@libsql/kysely-libsql";13import { LibsqlDialect, type LibsqlDialectConfig } from "@libsql/kysely-libsql";
@@ -9,6 +16,7 @@ import { Context } from "effect"; · 2 unmodified lines
9import { loadConfig } from "../config";16import { loadConfig } from "../config";
10import { seedOrgAndAdmin } from "./seed";17import { seedOrgAndAdmin } from "./seed";
11import { consumeInviteCode, ensureInviteCodeTable, findRedeemableCode } from "./invites";18import { consumeInviteCode, ensureInviteCodeTable, findRedeemableCode } from "./invites";
19import { isAdmitted, isOAuthCallback, ssoProviderConfig } from "./sso";
12 20
13// The self-service signup gate: present only on the live (phase-2) auth21// The self-service signup gate: present only on the live (phase-2) auth
14// instance, so the bootstrap seed's `createUser` — which22// instance, so the bootstrap seed's `createUser` — which
@@ -165,6 +173,14 @@ const makeAuthOptions = (client: Client, getOrganizationId: () => string, gate?: · 150 unmodified lines
165 // is the page the user opens to confirm the code — the self-host app serves173 // is the page the user opens to confirm the code — the self-host app serves
166 // it at /device (this is also the Better Auth default; pinned for clarity).174 // it at /device (this is also the Better Auth default; pinned for clarity).
167 deviceAuthorization({ verificationUri: "/device" }),175 deviceAuthorization({ verificationUri: "/device" }),
176 // The operator-configured SSO provider (see config.ts), spoken over plain
177 // OIDC discovery so Google, Okta, Entra, or any compliant IdP slots in —
178 // and so tests can point it at an emulated IdP. The domain gate below is
179 // what admits or refuses the users this creates; enabling the provider
180 // alone never opens registration. Always in the plugin tuple (an empty
181 // provider list serves no routes that match) so the inferred `auth.api`
182 // shape doesn't depend on the environment.
183 genericOAuth({ config: config.sso ? [ssoProviderConfig(config.sso)] : [] }),
168 // `consentPage` makes the MCP authorize flow redirect to a human approval184 // `consentPage` makes the MCP authorize flow redirect to a human approval
169 // screen instead of auto-issuing a code — but ONLY when the request185 // screen instead of auto-issuing a code — but ONLY when the request
170 // carries `prompt=consent`. MCP clients don't send that, so the self-host186 // carries `prompt=consent`. MCP clients don't send that, so the self-host
@@ -199,8 +215,26 @@ const makeAuthOptions = (client: Client, getOrganizationId: () => string, gate?: · 28 unmodified lines
199 ? {215 ? {
200 user: {216 user: {
201 create: {217 create: {
202 before: async (_user, context) => {218 before: async (user, context) => {
203 if (context?.path !== SIGNUP_PATH) return;219 if (context?.path !== SIGNUP_PATH) {
220 // SSO sign-ups arrive on an OAuth callback path; the
221 // verified-domain allowlist gates them in place of an
222 // invite code. Server-side creation (the seed, admin
223 // add-user) passes.
224 const sso = config.sso;
225 if (
226 isOAuthCallback(context?.path) &&
227 !(sso !== undefined && isAdmitted(sso, user))
228 ) {
229 // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: a Better Auth create hook rejects a request by throwing APIError
230 throw new APIError("FORBIDDEN", {
231 message: sso
232 ? `Sign-ups are restricted to verified ${sso.allowedDomains.map((d) => `@${d}`).join(", ")} accounts.`
233 : "SSO sign-up is not enabled on this instance.",
234 });
235 }
236 return;
237 }
204 if (await orgHasNoMembers(gate)) return; // first user claims the org238 if (await orgHasNoMembers(gate)) return; // first user claims the org
205 const code = inviteCodeFrom(context);239 const code = inviteCodeFrom(context);
206 if (!code) {240 if (!code) {
@@ -217,9 +251,30 @@ const makeAuthOptions = (client: Client, getOrganizationId: () => string, gate?: · 10 unmodified lines
217 }251 }
218 },252 },
219 after: async (user, context) => {253 after: async (user, context) => {
220 if (context?.path !== SIGNUP_PATH) return;
221 const auth = gate.getAuth();254 const auth = gate.getAuth();
222 if (!auth) return;255 if (!auth) return;
256 if (context?.path !== SIGNUP_PATH) {
257 // An SSO user that reached `after` was admitted by
258 // `before`; joining the instance org as a member is what an
259 // invite redemption would have done. Server-side creation
260 // (no callback path) is left alone — the seed manages its
261 // own membership.
262 const sso = config.sso;
263 if (
264 isOAuthCallback(context?.path) &&
265 sso !== undefined &&
266 isAdmitted(sso, user)
267 ) {
268 await auth.api.addMember({
269 body: {
270 userId: user.id,
271 role: "member",
272 organizationId: gate.organizationId,
273 },
274 });
275 }
276 return;
277 }
223 // First user into an empty org becomes its owner (no code).278 // First user into an empty org becomes its owner (no code).
224 if (await orgHasNoMembers(gate)) {279 if (await orgHasNoMembers(gate)) {
225 await auth.api.addMember({280 await auth.api.addMember({
apps/host-selfhost/src/auth/sso.test.ts+313−0
@@ -0,0 +1,313 @@
1import { mkdtempSync } from "node:fs";
2import { tmpdir } from "node:os";
3import { join } from "node:path";
4
5import { afterAll, describe, expect, it, test } from "@effect/vitest";
6import { createEmulator } from "@executor-js/emulate";
7
8import { mintInviteCode } from "../testing/mint-invite";
9
10// Real Better Auth path with an SSO provider configured — and a REAL IdP on the
11// wire: an @executor-js/emulate Okta instance serving OIDC discovery,
12// authorize, and token, so the round-trip tests below exercise the same
13// discovery -> redirect -> consent -> callback flow a production Google/Okta
14// deployment does. Emulator + registered client + env must all exist before
15// the app import, so `loadConfig` sees a fully-configured instance at boot.
16const BASE = "http://localhost:4788";
17const CALLBACK = `${BASE}/api/auth/oauth2/callback/okta`;
18
19const idp = await createEmulator({ service: "okta", port: 4790 });
20afterAll(() => idp.close());
21
22const registration = await fetch(`${idp.url}/oauth2/v1/clients`, {
23 method: "POST",
24 headers: { "content-type": "application/json" },
25 body: JSON.stringify({
26 client_name: "executor-selfhost-test",
27 redirect_uris: [CALLBACK],
28 grant_types: ["authorization_code"],
29 response_types: ["code"],
30 token_endpoint_auth_method: "client_secret_post",
31 }),
32}).then((r) => r.json() as Promise<{ client_id: string; client_secret: string }>);
33
34const createIdpUser = (email: string, firstName: string) =>
35 fetch(`${idp.url}/api/v1/users`, {
36 method: "POST",
37 headers: { "content-type": "application/json", authorization: "SSWS test" },
38 body: JSON.stringify({ profile: { login: email, email, firstName, lastName: "User" } }),
39 });
40await createIdpUser("alice@example.com", "Alice");
41await createIdpUser("mallory@evil.test", "Mallory");
42
43process.env.EXECUTOR_DATA_DIR = mkdtempSync(join(tmpdir(), "eh-sso-"));
44process.env.BETTER_AUTH_SECRET = "test-secret-0123456789-abcdefghijklmnop-qrstuv";
45process.env.EXECUTOR_BOOTSTRAP_ADMIN_EMAIL = "admin@test.local";
46process.env.EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD = "admin-password-123";
47process.env.EXECUTOR_SSO_PROVIDER_ID = "okta";
48process.env.EXECUTOR_SSO_DISCOVERY_URL = `${idp.url}/.well-known/openid-configuration`;
49process.env.EXECUTOR_SSO_CLIENT_ID = registration.client_id;
50process.env.EXECUTOR_SSO_CLIENT_SECRET = registration.client_secret;
51process.env.EXECUTOR_SSO_ALLOWED_DOMAINS = "Example.com, @second.example ,";
52
53const { loadConfig } = await import("../config");
54const { emailDomain } = await import("./sso");
55const { makeSelfHostApiHandler } = await import("../app");
56
57const { handler, dispose } = await makeSelfHostApiHandler();
58afterAll(() => dispose());
59
60const SSO_ENV_KEYS = [
61 "EXECUTOR_SSO_PROVIDER_ID",
62 "EXECUTOR_SSO_PROVIDER_NAME",
63 "EXECUTOR_SSO_DISCOVERY_URL",
64 "EXECUTOR_SSO_CLIENT_ID",
65 "EXECUTOR_SSO_CLIENT_SECRET",
66 "EXECUTOR_SSO_ALLOWED_DOMAINS",
67] as const;
68
69// Run a block with the SSO env vars swapped out, restoring them afterwards so
70// the booted instance's request-time config reads stay consistent.
71const withSsoEnv = <T>(overrides: Record<string, string | undefined>, run: () => T): T => {
72 const saved = Object.fromEntries(SSO_ENV_KEYS.map((k) => [k, process.env[k]]));
73 for (const key of SSO_ENV_KEYS) {
74 const value = overrides[key];
75 if (value === undefined) delete process.env[key];
76 else process.env[key] = value;
77 }
78 // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: env save/restore around config reads must restore on assertion failure
79 try {
80 return run();
81 } finally {
82 for (const key of SSO_ENV_KEYS) {
83 const value = saved[key];
84 if (value === undefined) delete process.env[key];
85 else process.env[key] = value;
86 }
87 }
88};
89
90describe("sso config resolution", () => {
91 it("normalizes the domain allowlist (trim, lowercase, strip @, drop empties)", () => {
92 const sso = loadConfig().sso;
93 expect(sso).toBeDefined();
94 expect(sso!.allowedDomains).toEqual(["example.com", "second.example"]);
95 });
96
97 it("derives the display name from the provider id when not set", () => {
98 expect(loadConfig().sso!.providerName).toBe("Okta");
99 });
100
101 it("is undefined when no SSO env is set", () => {
102 withSsoEnv({}, () => {
103 expect(loadConfig().sso).toBeUndefined();
104 });
105 });
106
107 it("refuses half-configured credentials", () => {
108 withSsoEnv({ EXECUTOR_SSO_CLIENT_ID: "id-only" }, () => {
109 expect(() => loadConfig()).toThrow(/must be set together/);
110 });
111 });
112
113 it("refuses credentials without a provider id", () => {
114 withSsoEnv({ EXECUTOR_SSO_CLIENT_ID: "id", EXECUTOR_SSO_CLIENT_SECRET: "secret" }, () => {
115 expect(() => loadConfig()).toThrow(/EXECUTOR_SSO_PROVIDER_ID/);
116 });
117 });
118
119 it("presets Google's discovery document so only credentials + domains are needed", () => {
120 withSsoEnv(
121 {
122 EXECUTOR_SSO_PROVIDER_ID: "google",
123 EXECUTOR_SSO_CLIENT_ID: "id.apps.googleusercontent.com",
124 EXECUTOR_SSO_CLIENT_SECRET: "secret",
125 EXECUTOR_SSO_ALLOWED_DOMAINS: "example.com",
126 },
127 () => {
128 const sso = loadConfig().sso;
129 expect(sso!.discoveryUrl).toBe(
130 "https://accounts.google.com/.well-known/openid-configuration",
131 );
132 expect(sso!.providerName).toBe("Google");
133 },
134 );
135 });
136
137 it("refuses a provider without a known or explicit discovery URL", () => {
138 withSsoEnv(
139 {
140 EXECUTOR_SSO_PROVIDER_ID: "okta",
141 EXECUTOR_SSO_CLIENT_ID: "id",
142 EXECUTOR_SSO_CLIENT_SECRET: "secret",
143 EXECUTOR_SSO_ALLOWED_DOMAINS: "example.com",
144 },
145 () => {
146 expect(() => loadConfig()).toThrow(/EXECUTOR_SSO_DISCOVERY_URL/);
147 },
148 );
149 });
150
151 it("refuses a configured provider without a domain allowlist", () => {
152 withSsoEnv(
153 {
154 EXECUTOR_SSO_PROVIDER_ID: "okta",
155 EXECUTOR_SSO_DISCOVERY_URL: "https://idp.example/.well-known/openid-configuration",
156 EXECUTOR_SSO_CLIENT_ID: "id",
157 EXECUTOR_SSO_CLIENT_SECRET: "secret",
158 },
159 () => {
160 expect(() => loadConfig()).toThrow(/EXECUTOR_SSO_ALLOWED_DOMAINS/);
161 },
162 );
163 });
164});
165
166describe("emailDomain", () => {
167 it("extracts the lowercased domain of a well-formed address", () => {
168 expect(emailDomain("User@Example.COM")).toBe("example.com");
169 });
170
171 it("returns null for malformed addresses instead of a matchable domain", () => {
172 expect(emailDomain("@example.com")).toBeNull();
173 expect(emailDomain("user@")).toBeNull();
174 expect(emailDomain("no-at-sign")).toBeNull();
175 });
176});
177
178test("auth-config advertises the configured provider (id + name only)", async () => {
179 const res = await handler(new Request(`${BASE}/api/auth-config`));
180 expect(res.status).toBe(200);
181 expect(await res.json()).toEqual({ ssoProviders: [{ id: "okta", name: "Okta" }] });
182});
183
184// --- The full OIDC round-trip against the emulated IdP -----------------------
185
186const decodeHtml = (value: string): string =>
187 value
188 .replaceAll("&amp;", "&")
189 .replaceAll("&quot;", '"')
190 .replaceAll("&#x27;", "'")
191 .replaceAll("&lt;", "<")
192 .replaceAll("&gt;", ">");
193
194const formFields = (form: string): Record<string, string> => {
195 const fields: Record<string, string> = {};
196 for (const input of form.matchAll(/<input\b[^>]*>/gi)) {
197 const tag = input[0];
198 const name = tag.match(/\bname=["']([^"']+)["']/i)?.[1];
199 const value = tag.match(/\bvalue=["']([^"']*)["']/i)?.[1] ?? "";
200 if (name) fields[decodeHtml(name)] = decodeHtml(value);
201 }
202 return fields;
203};
204
205// Drive the whole flow as the given IdP account: sign-in redirect -> IdP
206// consent page (pick the account's form) -> callback back into the app.
207// Returns the callback response plus the cookies it set.
208const signInThroughIdp = async (email: string) => {
209 const start = await handler(
210 new Request(`${BASE}/api/auth/sign-in/oauth2`, {
211 method: "POST",
212 headers: { "content-type": "application/json" },
213 body: JSON.stringify({ providerId: "okta", callbackURL: "/" }),
214 }),
215 );
216 expect(start.status).toBe(200);
217 const { url } = (await start.json()) as { url: string };
218 expect(url.startsWith(idp.url)).toBe(true);
219 const stateCookies = start.headers
220 .getSetCookie()
221 .map((cookie) => cookie.split(";")[0]!)
222 .join("; ");
223
224 const consentHtml = await fetch(url).then((r) => r.text());
225 const form = [...consentHtml.matchAll(/<form[\s\S]*?<\/form>/gi)]
226 .map((m) => m[0])
227 .find((f) => f.includes(email));
228 expect(form).toBeDefined();
229 const action = decodeHtml(form!.match(/\baction=["']([^"']+)["']/i)![1]!);
230 const consent = await fetch(action, {
231 method: "POST",
232 headers: { "content-type": "application/x-www-form-urlencoded" },
233 body: new URLSearchParams(formFields(form!)),
234 redirect: "manual",
235 });
236 expect(consent.status).toBe(302);
237 const callbackUrl = consent.headers.get("location")!;
238 expect(callbackUrl.startsWith(CALLBACK)).toBe(true);
239
240 const callback = await handler(new Request(callbackUrl, { headers: { cookie: stateCookies } }));
241 const cookies = callback.headers
242 .getSetCookie()
243 .map((cookie) => cookie.split(";")[0]!)
244 .join("; ");
245 return { callback, cookies };
246};
247
248test("an allowlisted-domain IdP account signs in and joins the org as a member", async () => {
249 const { callback, cookies } = await signInThroughIdp("alice@example.com");
250 expect(callback.status).toBe(302);
251 expect(callback.headers.get("location")).toBe("/");
252
253 const session = await handler(
254 new Request(`${BASE}/api/auth/get-session`, { headers: { cookie: cookies } }),
255 );
256 expect(session.status).toBe(200);
257 const sessionBody = (await session.json()) as { user?: { email?: string } };
258 expect(sessionBody.user?.email).toBe("alice@example.com");
259
260 const orgs = await handler(
261 new Request(`${BASE}/api/auth/organization/list`, { headers: { cookie: cookies } }),
262 );
263 expect(orgs.status).toBe(200);
264 expect(((await orgs.json()) as unknown[]).length).toBe(1);
265});
266
267test("an IdP account outside the allowlist is refused and gets no session", async () => {
268 const { callback, cookies } = await signInThroughIdp("mallory@evil.test");
269 // The rejection surfaces as better-auth's error redirect, not a success.
270 expect(callback.status).toBe(302);
271 expect(callback.headers.get("location")).not.toBe("/");
272 expect(callback.headers.get("location")).toContain("error");
273
274 const session = await handler(
275 new Request(`${BASE}/api/auth/get-session`, { headers: { cookie: cookies } }),
276 );
277 const sessionBody = (await session.json()) as { user?: unknown } | null;
278 expect(sessionBody?.user ?? null).toBeNull();
279});
280
281// --- Invite-code regressions with SSO configured ------------------------------
282
283test("invite-gated email signup still works with an SSO provider configured", async () => {
284 const inviteCode = await mintInviteCode(handler);
285 const signUp = await handler(
286 new Request(`${BASE}/api/auth/sign-up/email`, {
287 method: "POST",
288 headers: { "content-type": "application/json" },
289 body: JSON.stringify({
290 email: "member@test.local",
291 password: "member-password-123",
292 name: "Member",
293 inviteCode,
294 }),
295 }),
296 );
297 expect(signUp.status).toBe(200);
298});
299
300test("email signup without an invite is still refused with an SSO provider configured", async () => {
301 const signUp = await handler(
302 new Request(`${BASE}/api/auth/sign-up/email`, {
303 method: "POST",
304 headers: { "content-type": "application/json" },
305 body: JSON.stringify({
306 email: "stranger@example.com",
307 password: "stranger-password-123",
308 name: "Stranger",
309 }),
310 }),
311 );
312 expect(signUp.status).toBe(403);
313});
apps/host-selfhost/src/auth/sso.ts+46−0
@@ -0,0 +1,46 @@
1import { type SsoConfig } from "../config";
2
3// Better Auth serves OAuth sign-in callbacks at `/oauth2/callback/:providerId`
4// (genericOAuth) and `/callback/:providerId` (built-in social providers) — the
5// only paths an IdP-initiated user creation arrives on, so this splits "a
6// stranger signed in at the IdP" from server-side creation (the seed, admin
7// add-user), which never carries either.
8export const isOAuthCallback = (path: string | undefined): boolean =>
9 path?.startsWith("/oauth2/callback/") === true || path?.startsWith("/callback/") === true;
10
11// The domain of a well-formed address, or null — so a malformed email can never
12// match an allowlist entry (`emailDomain("@example.com")` is null, not "example.com").
13export const emailDomain = (email: string): string | null => {
14 const at = email.lastIndexOf("@");
15 if (at <= 0 || at === email.length - 1) return null;
16 return email.slice(at + 1).toLowerCase();
17};
18
19// Admission = the IdP vouches for the address (`email_verified`, mapped to
20// `emailVerified` by the genericOAuth callback) AND its domain is allowlisted.
21// Without the verified check, anyone could register an IdP account with a
22// made-up allowlisted address and walk in.
23export const isAdmitted = (
24 sso: SsoConfig,
25 user: { email: string; emailVerified: boolean },
26): boolean => {
27 if (!user.emailVerified) return false;
28 const domain = emailDomain(user.email);
29 return domain !== null && sso.allowedDomains.includes(domain);
30};
31
32// The genericOAuth registration for the configured provider, derived from its
33// OIDC discovery document. For Google with a single allowed domain, `hd`
34// pre-filters the account chooser — a UX hint only (Google treats it as
35// advisory); the create-hook gate is the enforcement.
36export const ssoProviderConfig = (sso: SsoConfig) => ({
37 providerId: sso.providerId,
38 clientId: sso.clientId,
39 clientSecret: sso.clientSecret,
40 discoveryUrl: sso.discoveryUrl,
41 scopes: ["openid", "email", "profile"],
42 pkce: true,
43 ...(sso.providerId === "google" && sso.allowedDomains.length === 1
44 ? { authorizationUrlParams: { hd: sso.allowedDomains[0]! } }
45 : {}),
46});
apps/host-selfhost/src/config.ts+79−0
@@ -18,6 +18,26 @@ import { · 17 unmodified lines
18export const SELF_HOST_NAMESPACE = "executor_selfhost";18export const SELF_HOST_NAMESPACE = "executor_selfhost";
19export const SELF_HOST_SCHEMA_VERSION = "1.0.0";19export const SELF_HOST_SCHEMA_VERSION = "1.0.0";
20 20
21/**
22 * SSO sign-in for the self-host login page and the MCP OAuth connect flow: one
23 * OIDC provider (Google, Okta, Entra, any discovery-compliant IdP) resolved
24 * from the environment. Present only when the operator configured it; the
25 * allowlist is what replaces the invite code for SSO sign-ups (the domain IS
26 * the invite), so it is required whenever the provider is enabled.
27 */
28export interface SsoConfig {
29 /** URL-safe id — also the OAuth callback path segment and the button key. */
30 readonly providerId: string;
31 /** Display name for the login button (“Continue with <name>”). */
32 readonly providerName: string;
33 /** The IdP's OIDC discovery document (…/.well-known/openid-configuration). */
34 readonly discoveryUrl: string;
35 readonly clientId: string;
36 readonly clientSecret: string;
37 /** Lowercased email domains admitted without an invite code. */
38 readonly allowedDomains: readonly string[];
39}
40
21export interface SelfHostConfig {41export interface SelfHostConfig {
22 /** Bind address. Defaults to loopback. */42 /** Bind address. Defaults to loopback. */
23 readonly host: string;43 readonly host: string;
@@ -53,6 +73,8 @@ export interface SelfHostConfig { · 29 unmodified lines
53 * minutes (the same pattern as MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS on cloud).73 * minutes (the same pattern as MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS on cloud).
54 */74 */
55 readonly sandboxTimeoutMs: number | undefined;75 readonly sandboxTimeoutMs: number | undefined;
76 /** SSO sign-in, or undefined when the operator hasn't configured it. */
77 readonly sso: SsoConfig | undefined;
56 /**78 /**
57 * How long an MCP session may sit idle before the in-process store evicts it,79 * How long an MCP session may sit idle before the in-process store evicts it,
58 * or undefined for the store's own default (30 minutes). 0 disables eviction.80 * or undefined for the store's own default (30 minutes). 0 disables eviction.
@@ -172,11 +194,68 @@ export const loadConfig = (): SelfHostConfig => { · 113 unmodified lines
172 organizationName: process.env.EXECUTOR_ORG_NAME ?? "Default",194 organizationName: process.env.EXECUTOR_ORG_NAME ?? "Default",
173 orgSlug: resolveOrgSlug(),195 orgSlug: resolveOrgSlug(),
174 sandboxTimeoutMs: resolveSandboxTimeoutMs(),196 sandboxTimeoutMs: resolveSandboxTimeoutMs(),
197 sso: resolveSso(),
175 mcpSessionIdleTtlMs: resolveMcpSessionIdleTtlMs(),198 mcpSessionIdleTtlMs: resolveMcpSessionIdleTtlMs(),
176 toolsSyncTtlMs: resolveToolsSyncTtlMs(),199 toolsSyncTtlMs: resolveToolsSyncTtlMs(),
177 };200 };
178};201};
179 202
203// Well-known discovery documents for providers an operator can name without
204// hunting down the URL. Anything else (Okta, Entra, Auth0, …) has a
205// tenant-specific issuer, so EXECUTOR_SSO_DISCOVERY_URL is required for it.
206const DISCOVERY_PRESETS: Record<string, string> = {
207 google: "https://accounts.google.com/.well-known/openid-configuration",
208};
209
210// The provider id doubles as the OAuth callback path segment
211// (`/api/auth/oauth2/callback/<id>`), so it must be URL-safe.
212const PROVIDER_ID_PATTERN = /^[a-z0-9-]{1,48}$/;
213
214// A half-configured provider is refused rather than silently ignored (same
215// posture as resolveSandboxTimeoutMs): an operator who set some of the
216// variables should find out at boot, not by staring at a login page with no
217// button. An empty domain allowlist is refused too — without it, SSO sign-in
218// would be open registration for anyone with an account at the IdP, bypassing
219// the invite gate entirely.
220const resolveSso = (): SsoConfig | undefined => {
221 const clientId = process.env.EXECUTOR_SSO_CLIENT_ID?.trim();
222 const clientSecret = process.env.EXECUTOR_SSO_CLIENT_SECRET?.trim();
223 if (!clientId && !clientSecret) return undefined;
224 if (!clientId || !clientSecret) {
225 // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: refuse to boot on half-configured SSO credentials
226 throw new Error("EXECUTOR_SSO_CLIENT_ID and EXECUTOR_SSO_CLIENT_SECRET must be set together");
227 }
228 const providerId = process.env.EXECUTOR_SSO_PROVIDER_ID?.trim().toLowerCase() ?? "";
229 if (!PROVIDER_ID_PATTERN.test(providerId)) {
230 // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: refuse to boot on a missing/malformed provider id
231 throw new Error(
232 'EXECUTOR_SSO_PROVIDER_ID is required when SSO is configured (1-48 chars of [a-z0-9-], e.g. "google" or "okta") — it names the provider and its OAuth callback path',
233 );
234 }
235 const discoveryUrl =
236 process.env.EXECUTOR_SSO_DISCOVERY_URL?.trim() || DISCOVERY_PRESETS[providerId];
237 if (!discoveryUrl) {
238 // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: refuse to boot without a way to reach the IdP
239 throw new Error(
240 `EXECUTOR_SSO_DISCOVERY_URL is required for provider ${JSON.stringify(providerId)} (the IdP's …/.well-known/openid-configuration URL)`,
241 );
242 }
243 const allowedDomains = (process.env.EXECUTOR_SSO_ALLOWED_DOMAINS ?? "")
244 .split(",")
245 .map((domain) => domain.trim().replace(/^@/, "").toLowerCase())
246 .filter((domain) => domain.length > 0);
247 if (allowedDomains.length === 0) {
248 // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: SSO sign-in without a domain allowlist is open registration; refuse to boot
249 throw new Error(
250 'EXECUTOR_SSO_ALLOWED_DOMAINS is required when SSO is configured (comma-separated email domains, e.g. "example.com") — it is what gates sign-ups in place of an invite code',
251 );
252 }
253 const providerName =
254 process.env.EXECUTOR_SSO_PROVIDER_NAME?.trim() ||
255 providerId.charAt(0).toUpperCase() + providerId.slice(1);
256 return { providerId, providerName, discoveryUrl, clientId, clientSecret, allowedDomains };
257};
258
180// A malformed value is refused rather than silently ignored: an operator who259// A malformed value is refused rather than silently ignored: an operator who
181// sets the knob and typos it should find out at boot, not by watching a260// sets the knob and typos it should find out at boot, not by watching a
182// runaway execution use the 5-minute default.261// runaway execution use the 5-minute default.
apps/host-selfhost/src/system/api.ts+13−1
@@ -7,8 +7,10 @@ import { Schema } from "effect"; · 6 unmodified lines
7// GET /api/health readiness probe (used by the container healthcheck)7// GET /api/health readiness probe (used by the container healthcheck)
8// GET /api/setup-status whether the instance still needs first-run setup, so8// GET /api/setup-status whether the instance still needs first-run setup, so
9// the pre-login SPA can route a fresh operator to /setup9// the pre-login SPA can route a fresh operator to /setup
10// GET /api/auth-config which SSO sign-in providers are configured, so the
11// login page knows which provider buttons to render
10//12//
11// Both are deliberately unauthenticated and return only booleans/status — no13// All are deliberately unauthenticated and return only booleans/status — no
12// sensitive data — so they can be read before anyone has signed in.14// sensitive data — so they can be read before anyone has signed in.
13// ---------------------------------------------------------------------------15// ---------------------------------------------------------------------------
14 16
@@ -21,6 +23,10 @@ export class SystemError extends Schema.TaggedErrorClass<SystemError>()( · 6 unmodified lines
21export const HealthResponse = Schema.Struct({ status: Schema.String });23export const HealthResponse = Schema.Struct({ status: Schema.String });
22export const SetupStatusResponse = Schema.Struct({ needsSetup: Schema.Boolean });24export const SetupStatusResponse = Schema.Struct({ needsSetup: Schema.Boolean });
23export const InviteStatusResponse = Schema.Struct({ valid: Schema.Boolean });25export const InviteStatusResponse = Schema.Struct({ valid: Schema.Boolean });
26// Provider ids + display names only — never credentials or allowlists.
27export const AuthConfigResponse = Schema.Struct({
28 ssoProviders: Schema.Array(Schema.Struct({ id: Schema.String, name: Schema.String })),
29});
24 30
25const InviteStatusParams = { code: Schema.String };31const InviteStatusParams = { code: Schema.String };
26 32
@@ -37,6 +43,12 @@ export const SystemApi = HttpApiGroup.make("system") · 10 unmodified lines
37 error: [SystemError],43 error: [SystemError],
38 }),44 }),
39 )45 )
46 .add(
47 HttpApiEndpoint.get("authConfig", "/auth-config", {
48 success: AuthConfigResponse,
49 error: [SystemError],
50 }),
51 )
40 .add(52 .add(
41 HttpApiEndpoint.get("inviteStatus", "/invite-status/:code", {53 HttpApiEndpoint.get("inviteStatus", "/invite-status/:code", {
42 params: InviteStatusParams,54 params: InviteStatusParams,
apps/host-selfhost/src/system/handlers.ts+10−0
@@ -6,6 +6,7 @@ import { SystemError, SystemHttpApi } from "./api"; · 5 unmodified lines
6import { BetterAuth, countOrgMembers, type BetterAuthHandle } from "../auth/better-auth";6import { BetterAuth, countOrgMembers, type BetterAuthHandle } from "../auth/better-auth";
7import { SelfHostDb, type SelfHostDbHandle } from "../db/self-host-db";7import { SelfHostDb, type SelfHostDbHandle } from "../db/self-host-db";
8import { findRedeemableCode } from "../auth/invites";8import { findRedeemableCode } from "../auth/invites";
9import { loadConfig } from "../config";
9 10
10// ---------------------------------------------------------------------------11// ---------------------------------------------------------------------------
11// Handlers for the public system API. Unauthenticated; every DB touch is an12// Handlers for the public system API. Unauthenticated; every DB touch is an
@@ -40,6 +41,15 @@ export const SystemHandlers = HttpApiBuilder.group(SystemHttpApi, "system", (han · 28 unmodified lines
40 return { needsSetup: count === 0 };41 return { needsSetup: count === 0 };
41 }),42 }),
42 )43 )
44 .handle("authConfig", () =>
45 // Which SSO provider the operator configured — id + display name only, so
46 // the pre-login page knows which button to render. Config is env-derived
47 // and boot-validated, so this read cannot fail.
48 Effect.sync(() => {
49 const sso = loadConfig().sso;
50 return { ssoProviders: sso ? [{ id: sso.providerId, name: sso.providerName }] : [] };
51 }),
52 )
43 .handle("inviteStatus", ({ params }) =>53 .handle("inviteStatus", ({ params }) =>
44 Effect.gen(function* () {54 Effect.gen(function* () {
45 const { client } = yield* SelfHostDb;55 const { client } = yield* SelfHostDb;
apps/host-selfhost/web/auth-client.ts+3−0
@@ -1,9 +1,12 @@
1import { createAuthClient } from "better-auth/react";1import { createAuthClient } from "better-auth/react";
2import { genericOAuthClient } from "better-auth/client/plugins";
2 3
3// Better Auth browser client. Talks to the self-host server's /api/auth (same4// Better Auth browser client. Talks to the self-host server's /api/auth (same
4// origin); the session cookie it sets is what the shared AuthProvider's5// origin); the session cookie it sets is what the shared AuthProvider's
5// /account/me query and all API calls authenticate with. Only the login form6// /account/me query and all API calls authenticate with. Only the login form
6// and sign-out use this — auth STATE comes from the shared AuthProvider.7// and sign-out use this — auth STATE comes from the shared AuthProvider.
8// `genericOAuthClient` adds `signIn.oauth2` for the configured SSO provider.
7export const authClient = createAuthClient({9export const authClient = createAuthClient({
8 baseURL: `${window.location.origin}/api/auth`,10 baseURL: `${window.location.origin}/api/auth`,
11 plugins: [genericOAuthClient()],
9});12});
apps/host-selfhost/web/auth-config.ts+29−0
@@ -0,0 +1,29 @@
1// Pre-login read of which SSO providers the operator configured, so the login
2// page knows which provider buttons to render. Same boundary as setup-status: a
3// plain same-origin fetch that runs before the atom registry exists. Fails soft
4// to "no providers" — the email/password form is always available, so a hiccup
5// here degrades to the baseline login, never a lockout.
6
7export interface SsoProvider {
8 readonly id: string;
9 readonly name: string;
10}
11
12const isSsoProvider = (value: unknown): value is SsoProvider =>
13 typeof value === "object" &&
14 value !== null &&
15 typeof (value as { id?: unknown }).id === "string" &&
16 typeof (value as { name?: unknown }).name === "string";
17
18export const fetchSsoProviders = async (): Promise<readonly SsoProvider[]> => {
19 const response = await fetch("/api/auth-config", { credentials: "same-origin" }).then(
20 (r) => r,
21 () => null,
22 );
23 if (!response?.ok) return [];
24 const data = (await response.json().then(
25 (d) => d,
26 () => ({}),
27 )) as { ssoProviders?: unknown };
28 return Array.isArray(data.ssoProviders) ? data.ssoProviders.filter(isSsoProvider) : [];
29};
apps/host-selfhost/web/login.tsx+55−5
@@ -1,17 +1,19 @@
1import { useState, type FormEvent } from "react";1import { useEffect, useState, type FormEvent } from "react";
2 2
3import { Button } from "@executor-js/react/components/button";3import { Button } from "@executor-js/react/components/button";
4import { Input } from "@executor-js/react/components/input";4import { Input } from "@executor-js/react/components/input";
5import { Label } from "@executor-js/react/components/label";5import { Label } from "@executor-js/react/components/label";
6 6
7import { authClient } from "./auth-client";7import { authClient } from "./auth-client";
8import { fetchSsoProviders, type SsoProvider } from "./auth-config";
8import { AuthLayout } from "./auth-layout";9import { AuthLayout } from "./auth-layout";
9import { postLoginTarget } from "../src/auth/return-to";10import { postLoginTarget } from "../src/auth/return-to";
10 11
11// Self-host login: email + password sign-in via Better Auth. On success we12// Self-host login: email + password sign-in via Better Auth, plus a provider
12// reload so the shared AuthProvider re-reads /account/me and the AuthGate swaps13// button per configured SSO provider (read from /api/auth-config). On
13// in the app. (Cloud's equivalent is a WorkOS redirect — this is the14// success we reload so the shared AuthProvider re-reads /account/me and the
14// provider-specific piece injected into the shared shell.)15// AuthGate swaps in the app. (Cloud's equivalent is a WorkOS redirect — this is
16// the provider-specific piece injected into the shared shell.)
15//17//
16// There is no self-signup here: open registration is closed. New people join by18// There is no self-signup here: open registration is closed. New people join by
17// redeeming an invite — either the full /join/<code> link, or by entering the19// redeeming an invite — either the full /join/<code> link, or by entering the
@@ -27,6 +29,17 @@ export const LoginPage = () => { · 9 unmodified lines
27 const [code, setCode] = useState("");29 const [code, setCode] = useState("");
28 const [error, setError] = useState<string | null>(null);30 const [error, setError] = useState<string | null>(null);
29 const [busy, setBusy] = useState(false);31 const [busy, setBusy] = useState(false);
32 const [ssoProviders, setSsoProviders] = useState<readonly SsoProvider[]>([]);
33
34 useEffect(() => {
35 let cancelled = false;
36 void fetchSsoProviders().then((providers) => {
37 if (!cancelled) setSsoProviders(providers);
38 });
39 return () => {
40 cancelled = true;
41 };
42 }, []);
30 43
31 const signIn = async (event: FormEvent) => {44 const signIn = async (event: FormEvent) => {
32 event.preventDefault();45 event.preventDefault();
@@ -41,6 +54,22 @@ export const LoginPage = () => { · 8 unmodified lines
41 window.location.href = postLogin;54 window.location.href = postLogin;
42 };55 };
43 56
57 const signInWithSso = async (providerId: string) => {
58 setBusy(true);
59 setError(null);
60 // Better Auth redirects the browser to the IdP; on callback it lands on
61 // `callbackURL`, so the deep-link target survives the round trip the same
62 // way it does for the email form's post-login navigation.
63 const result = await authClient.signIn.oauth2({
64 providerId,
65 callbackURL: postLogin,
66 });
67 if (result.error) {
68 setBusy(false);
69 setError(result.error.message ?? "Sign in failed");
70 }
71 };
72
44 const redeem = (event: FormEvent) => {73 const redeem = (event: FormEvent) => {
45 event.preventDefault();74 event.preventDefault();
46 const trimmed = code.trim();75 const trimmed = code.trim();
@@ -94,6 +123,27 @@ export const LoginPage = () => { · 47 unmodified lines
94 <Button type="submit" disabled={busy} className="w-full">123 <Button type="submit" disabled={busy} className="w-full">
95 {busy ? "…" : "Sign in"}124 {busy ? "…" : "Sign in"}
96 </Button>125 </Button>
126 {ssoProviders.length > 0 && (
127 <>
128 <div className="flex items-center gap-3">
129 <div className="h-px flex-1 bg-border" />
130 <span className="text-xs text-muted-foreground">or</span>
131 <div className="h-px flex-1 bg-border" />
132 </div>
133 {ssoProviders.map((provider) => (
134 <Button
135 key={provider.id}
136 type="button"
137 variant="outline"
138 disabled={busy}
139 onClick={() => void signInWithSso(provider.id)}
140 className="w-full"
141 >
142 Continue with {provider.name}
143 </Button>
144 ))}
145 </>
146 )}
97 </form>147 </form>
98 ) : (148 ) : (
99 <form onSubmit={redeem} className="space-y-4">149 <form onSubmit={redeem} className="space-y-4">
bun.lock+3−58
@@ -256,6 +256,7 @@ · 255 unmodified lines
256 },256 },
257 "devDependencies": {257 "devDependencies": {
258 "@effect/vitest": "catalog:",258 "@effect/vitest": "catalog:",
259 "@executor-js/emulate": "^0.14.0",
259 "@executor-js/vite-plugin": "workspace:*",260 "@executor-js/vite-plugin": "workspace:*",
260 "@tailwindcss/vite": "catalog:",261 "@tailwindcss/vite": "catalog:",
261 "@tanstack/router-plugin": "^1.167.12",262 "@tanstack/router-plugin": "^1.167.12",
@@ -1412,24 +1413,6 @@ · 1150 unmodified lines
1412 1413
1413 "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="],1414 "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="],
1414 1415
1415 "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],
1416
1417 "@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="],
1418
1419 "@azure/core-client": ["@azure/core-client@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "tslib": "^2.6.2" } }, "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w=="],
1420
1421 "@azure/core-rest-pipeline": ["@azure/core-rest-pipeline@1.23.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.4", "tslib": "^2.6.2" } }, "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ=="],
1422
1423 "@azure/core-tracing": ["@azure/core-tracing@1.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ=="],
1424
1425 "@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="],
1426
1427 "@azure/identity": ["@azure/identity@4.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.2", "@azure/core-rest-pipeline": "^1.17.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^5.5.0", "@azure/msal-node": "^5.1.0", "open": "^10.1.0", "tslib": "^2.2.0" } }, "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw=="],
1428
1429 "@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="],
1430
1431 "@azure/msal-browser": ["@azure/msal-browser@5.6.3", "", { "dependencies": { "@azure/msal-common": "16.4.1" } }, "sha512-sTjMtUm+bJpENU/1WlRzHEsgEHppZDZ1EtNyaOODg/sQBtMxxJzGB+MOCM+T2Q5Qe1fKBrdxUmjyRxm0r7Ez9w=="],
1432
1433 "@azure/msal-common": ["@azure/msal-common@16.10.0", "", {}, "sha512-iYtjpanlv6963Jprs0MvzIap07V+QhultjQctfbEDQCflsDAEeO3R7XnVA5gk30fhoBFLdgJT7VqO0TGsEsN9w=="],1416 "@azure/msal-common": ["@azure/msal-common@16.10.0", "", {}, "sha512-iYtjpanlv6963Jprs0MvzIap07V+QhultjQctfbEDQCflsDAEeO3R7XnVA5gk30fhoBFLdgJT7VqO0TGsEsN9w=="],
1434 1417
1435 "@azure/msal-node": ["@azure/msal-node@5.3.0", "", { "dependencies": { "@azure/msal-common": "16.10.0", "jsonwebtoken": "^9.0.0" } }, "sha512-fXtJX811pX8y8QlrQqBSH6+plvWyKZDI0IxkheAcyAw9OtcpXyFivmTC7eGUqutLWaDlKXuQ3yOESD4zAmkjHg=="],1418 "@azure/msal-node": ["@azure/msal-node@5.3.0", "", { "dependencies": { "@azure/msal-common": "16.10.0", "jsonwebtoken": "^9.0.0" } }, "sha512-fXtJX811pX8y8QlrQqBSH6+plvWyKZDI0IxkheAcyAw9OtcpXyFivmTC7eGUqutLWaDlKXuQ3yOESD4zAmkjHg=="],
@@ -2336,7 +2319,7 @@ · 900 unmodified lines
2336 2319
2337 "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="],2320 "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="],
2338 2321
2339 "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],2322 "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="],
2340 2323
2341 "@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/configuration": "0.214.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/core": "2.6.1", "@opentelemetry/exporter-logs-otlp-grpc": "0.214.0", "@opentelemetry/exporter-logs-otlp-http": "0.214.0", "@opentelemetry/exporter-logs-otlp-proto": "0.214.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.214.0", "@opentelemetry/exporter-metrics-otlp-http": "0.214.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.214.0", "@opentelemetry/exporter-prometheus": "0.214.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.214.0", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/exporter-trace-otlp-proto": "0.214.0", "@opentelemetry/exporter-zipkin": "2.6.1", "@opentelemetry/instrumentation": "0.214.0", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/propagator-b3": "2.6.1", "@opentelemetry/propagator-jaeger": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-gl2XvQBJuPjhGcw9SsnQO5qxChAPMuGRPFaD8lqtF+Cey91NgGUQ0sio2vlDFOSm3JOLzc44vL+OAfx1dXuZjg=="],2324 "@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/configuration": "0.214.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/core": "2.6.1", "@opentelemetry/exporter-logs-otlp-grpc": "0.214.0", "@opentelemetry/exporter-logs-otlp-http": "0.214.0", "@opentelemetry/exporter-logs-otlp-proto": "0.214.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.214.0", "@opentelemetry/exporter-metrics-otlp-http": "0.214.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.214.0", "@opentelemetry/exporter-prometheus": "0.214.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.214.0", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/exporter-trace-otlp-proto": "0.214.0", "@opentelemetry/exporter-zipkin": "2.6.1", "@opentelemetry/instrumentation": "0.214.0", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/propagator-b3": "2.6.1", "@opentelemetry/propagator-jaeger": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-gl2XvQBJuPjhGcw9SsnQO5qxChAPMuGRPFaD8lqtF+Cey91NgGUQ0sio2vlDFOSm3JOLzc44vL+OAfx1dXuZjg=="],
2342 2325
@@ -3278,8 +3261,6 @@ · 935 unmodified lines
3278 3261
3279 "@typescript/vfs": ["@typescript/vfs@1.6.4", "", { "dependencies": { "debug": "^4.4.3" }, "peerDependencies": { "typescript": "*" } }, "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ=="],3262 "@typescript/vfs": ["@typescript/vfs@1.6.4", "", { "dependencies": { "debug": "^4.4.3" }, "peerDependencies": { "typescript": "*" } }, "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ=="],
3280 3263
3281 "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.5", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw=="],
3282
3283 "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],3264 "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
3284 3265
3285 "@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="],3266 "@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="],
@@ -4990,22 +4971,12 @@ · 1704 unmodified lines
4990 4971
4991 "performance-now": ["performance-now@2.1.0", "", {}, "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="],4972 "performance-now": ["performance-now@2.1.0", "", {}, "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="],
4992 4973
4993 "pg": ["pg@8.20.0", "", { "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", "pg-protocol": "^1.13.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA=="],
4994
4995 "pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="],
4996
4997 "pg-connection-string": ["pg-connection-string@2.12.0", "", {}, "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ=="],
4998
4999 "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],4974 "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
5000 4975
5001 "pg-pool": ["pg-pool@3.13.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA=="],
5002
5003 "pg-protocol": ["pg-protocol@1.13.0", "", {}, "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w=="],4976 "pg-protocol": ["pg-protocol@1.13.0", "", {}, "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w=="],
5004 4977
5005 "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],4978 "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
5006 4979
5007 "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="],
5008
5009 "piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="],4980 "piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="],
5010 4981
5011 "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],4982 "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
@@ -5462,16 +5433,12 @@ · 450 unmodified lines
5462 5433
5463 "split-string": ["split-string@3.1.0", "", { "dependencies": { "extend-shallow": "^3.0.0" } }, "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw=="],5434 "split-string": ["split-string@3.1.0", "", { "dependencies": { "extend-shallow": "^3.0.0" } }, "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw=="],
5464 5435
5465 "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
5466
5467 "spotify-web-api-node": ["spotify-web-api-node@5.0.2", "", { "dependencies": { "superagent": "^6.1.0" } }, "sha512-r82dRWU9PMimHvHEzL0DwEJrzFk+SMCVfq249SLt3I7EFez7R+jeoKQd+M1//QcnjqlXPs2am4DFsGk8/GCsrA=="],5436 "spotify-web-api-node": ["spotify-web-api-node@5.0.2", "", { "dependencies": { "superagent": "^6.1.0" } }, "sha512-r82dRWU9PMimHvHEzL0DwEJrzFk+SMCVfq249SLt3I7EFez7R+jeoKQd+M1//QcnjqlXPs2am4DFsGk8/GCsrA=="],
5468 5437
5469 "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],5438 "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
5470 5439
5471 "sql-highlight": ["sql-highlight@6.1.0", "", {}, "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA=="],5440 "sql-highlight": ["sql-highlight@6.1.0", "", {}, "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA=="],
5472 5441
5473 "sql.js": ["sql.js@1.14.1", "", {}, "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A=="],
5474
5475 "srvx": ["srvx@0.11.15", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-iXsux0UcOjdvs0LCMa2Ws3WwcDUozA3JN3BquNXkaFPP7TpRqgunKdEgoZ/uwb1J6xaYHfxtz9Twlh6yzwM6Tg=="],5442 "srvx": ["srvx@0.11.15", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-iXsux0UcOjdvs0LCMa2Ws3WwcDUozA3JN3BquNXkaFPP7TpRqgunKdEgoZ/uwb1J6xaYHfxtz9Twlh6yzwM6Tg=="],
5476 5443
5477 "sshpk": ["sshpk@1.18.0", "", { "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", "dashdash": "^1.12.0", "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, "bin": { "sshpk-conv": "bin/sshpk-conv", "sshpk-sign": "bin/sshpk-sign", "sshpk-verify": "bin/sshpk-verify" } }, "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ=="],5444 "sshpk": ["sshpk@1.18.0", "", { "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", "dashdash": "^1.12.0", "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, "bin": { "sshpk-conv": "bin/sshpk-conv", "sshpk-sign": "bin/sshpk-sign", "sshpk-verify": "bin/sshpk-verify" } }, "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ=="],
@@ -5902,10 +5869,6 @@ · 424 unmodified lines
5902 5869
5903 "@astrojs/react/vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],5870 "@astrojs/react/vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
5904 5871
5905 "@azure/identity/@azure/msal-node": ["@azure/msal-node@5.1.2", "", { "dependencies": { "@azure/msal-common": "16.4.1", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-DoeSJ9U5KPAIZoHsPywvfEj2MhBniQe0+FSpjLUTdWoIkI999GB5USkW6nNEHnIaLVxROHXvprWA1KzdS1VQ4A=="],
5906
5907 "@azure/msal-browser/@azure/msal-common": ["@azure/msal-common@16.4.1", "", {}, "sha512-Bl8f+w37xkXsYh7QRkAKCFGYtWMYuOVO7Lv+BxILrvGz3HbIEF22Pt0ugyj0QPOl6NLrHcnNUQ9yeew98P/5iw=="],
5908
5909 "@babel/core/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],5872 "@babel/core/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
5910 5873
5911 "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],5874 "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@@ -6156,20 +6119,12 @@ · 244 unmodified lines
6156 6119
6157 "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],6120 "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
6158 6121
6159 "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="],
6160
6161 "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],6122 "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
6162 6123
6163 "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="],
6164
6165 "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],6124 "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
6166 6125
6167 "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="],
6168
6169 "@opentelemetry/exporter-prometheus/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],6126 "@opentelemetry/exporter-prometheus/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
6170 6127
6171 "@opentelemetry/exporter-prometheus/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="],
6172
6173 "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],6128 "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
6174 6129
6175 "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="],6130 "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="],
@@ -6204,24 +6159,18 @@ · 28 unmodified lines
6204 6159
6205 "@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],6160 "@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
6206 6161
6207 "@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="],
6208
6209 "@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="],6162 "@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="],
6210 6163
6211 "@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="],6164 "@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.8.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww=="],
6212 6165
6213 "@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],6166 "@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
6214 6167
6215 "@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],6168 "@opentelemetry/sdk-metrics/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
6216
6217 "@opentelemetry/sdk-metrics/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
6218 6169
6219 "@opentelemetry/sdk-node/@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.6.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ=="],6170 "@opentelemetry/sdk-node/@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.6.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ=="],
6220 6171
6221 "@opentelemetry/sdk-node/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],6172 "@opentelemetry/sdk-node/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
6222 6173
6223 "@opentelemetry/sdk-node/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="],
6224
6225 "@opentelemetry/sdk-node/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="],6174 "@opentelemetry/sdk-node/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="],
6226 6175
6227 "@opentelemetry/sdk-node/@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.6.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/core": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw=="],6176 "@opentelemetry/sdk-node/@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.6.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/core": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw=="],
@@ -6840,10 +6789,6 @@ · 612 unmodified lines
6840 6789
6841 "@astrojs/react/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="],6790 "@astrojs/react/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="],
6842 6791
6843 "@azure/identity/@azure/msal-node/@azure/msal-common": ["@azure/msal-common@16.4.1", "", {}, "sha512-Bl8f+w37xkXsYh7QRkAKCFGYtWMYuOVO7Lv+BxILrvGz3HbIEF22Pt0ugyj0QPOl6NLrHcnNUQ9yeew98P/5iw=="],
6844
6845 "@azure/identity/@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
6846
6847 "@babel/helper-annotate-as-pure/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="],6792 "@babel/helper-annotate-as-pure/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="],
6848 6793
6849 "@babel/helper-annotate-as-pure/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="],6794 "@babel/helper-annotate-as-pure/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="],