Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
24.21.0
24.22.1
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
24.21.0
24.22.1
20,915 changes: 10,576 additions & 10,339 deletions package-lock.json

Large diffs are not rendered by default.

113 changes: 56 additions & 57 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,95 +30,94 @@
},
"devDependencies": {
"@axe-core/playwright": "^4.13.0",
"@cyclonedx/cyclonedx-npm": "^6.0.0",
"@eslint/compat": "^2.0.2",
"@cyclonedx/cyclonedx-npm": "^6.0.1",
"@eslint/compat": "^2.1.1",
"@eslint/js": "^9.39.2",
"@faker-js/faker": "^10.4.0",
"@faker-js/faker": "^10.6.0",
"@iconify-json/lsicon": "^1.2.5",
"@iconify-json/material-icon-theme": "^1.2.67",
"@iconify-json/material-symbols": "^1.2.74",
"@iconify-json/vscode-icons": "^1.2.55",
"@inlang/paraglide-js": "^2.12.0",
"@iconify-json/material-icon-theme": "^1.2.72",
"@iconify-json/material-symbols": "^1.2.95",
"@iconify-json/vscode-icons": "^1.2.91",
"@inlang/paraglide-js": "^2.29.0",
"@playwright/test": "1.61.1",
"@sveltejs/adapter-node": "^5.5.3",
"@sveltejs/adapter-node": "^5.5.7",
"@sveltejs/enhanced-img": "^0.11.0",
"@sveltejs/kit": "^2.53.0",
"@sveltejs/kit": "^2.70.3",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.2.1",
"@testcontainers/postgresql": "^12.0.1",
"@types/node": "^24",
"@tailwindcss/vite": "^4.3.3",
"@testcontainers/postgresql": "^12.2.1",
"@types/node": "^24.19.2",
"@types/papaparse": "^5.5.2",
"@types/pg": "^8.20.0",
"@types/tar-stream": "^3.1.4",
"@types/pg": "^8.23.1",
"@vitest/browser-playwright": "^4.1.11",
"@vitest/coverage-v8": "^4.1.11",
"antlr-ng": "^1.0.10",
"archunit": "^2.3.3",
"daisyui": "^5.5.19",
"archunit": "^2.5.4",
"daisyui": "^5.7.50",
"devalue": "^5.9.4",
"drizzle-kit": "^0.31.10",
"eslint": "^9.39.2",
"drizzle-kit": "^0.31.11",
"eslint": "^9.39.5",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-better-tailwindcss": "^4.3.0",
"eslint-plugin-better-tailwindcss": "^4.9.1",
"eslint-plugin-check-file": "^3.3.2",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-security": "^4.0.0",
"eslint-plugin-svelte": "^3.15.0",
"globals": "^16.5.0",
"knip": "^6.37.0",
"oauth2-mock-server": "^8.2.2",
"pino-pretty": "^13.1.3",
"prettier": "^3.8.1",
"prettier-plugin-svelte": "^3.5.0",
"prettier-plugin-tailwindcss": "^0.7.2",
"svelte": "^5.53.3",
"svelte-check": "^4.4.3",
"sveltekit-superforms": "^2.30.0",
"eslint-plugin-security": "^4.2.0",
"eslint-plugin-svelte": "^3.23.1",
"globals": "^17.13.0",
"knip": "^6.42.0",
"oauth2-mock-server": "^9.3.0",
"pino-pretty": "^13.2.0",
"prettier": "^3.9.10",
"prettier-plugin-svelte": "^3.5.2",
"prettier-plugin-tailwindcss": "^0.8.1",
"svelte": "^5.57.2",
"svelte-check": "^4.7.7",
"sveltekit-superforms": "^2.31.0",
"tailwindcss": "^4.2.1",
"testcontainers": "^12.0.1",
"tsx": "^4.23.13",
"typescript": "^5.9.3",
"typescript-eslint": "^8.56.1",
"unplugin-icons": "^23.0.1",
"vite": "^7.3.1",
"testcontainers": "^12.2.1",
"tsx": "^4.23.15",
"typescript": "^6.0.3",
"typescript-eslint": "^8.71.2",
"unplugin-icons": "^24.0.0",
"vite": "^7.3.7",
"vitest": "^4.1.11",
"vitest-browser-svelte": "^2.0.2",
"zod": "^4.3.6"
"vitest-browser-svelte": "^2.2.1",
"zod": "^4.6.7"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.1041.0",
"@aws-sdk/lib-storage": "^3.1045.0",
"@better-auth/drizzle-adapter": "^1.6.20",
"@aws-sdk/client-s3": "^3.1152.0",
"@aws-sdk/lib-storage": "^3.1152.0",
"@better-auth/drizzle-adapter": "^1.7.12",
"@eslint-community/regexpp": "^4.12.2",
"@internationalized/date": "^3.11.0",
"@smithy/node-http-handler": "4.9.1",
"@internationalized/date": "^3.12.4",
"@smithy/node-http-handler": "^4.12.1",
"adm-zip": "^0.6.1",
"antlr4-c3": "^3.4.4",
"antlr4ng": "^3.0.16",
"better-auth": "^1.6.33",
"drizzle-orm": "^0.45.2",
"hyparquet": "^1.25.8",
"hyparquet-compressors": "^1.1.1",
"monaco-editor": "^0.55.1",
"papaparse": "^5.5.3",
"pg": "^8.21.0",
"pino": "^10.3.1",
"pretty-bytes": "^7.1.0",
"better-auth": "^1.7.12",
"drizzle-orm": "^0.45.4",
"hyparquet": "^1.31.5",
"hyparquet-compressors": "^1.1.2",
"monaco-editor": "^0.57.0",
"papaparse": "^5.7.0",
"pg": "^8.23.1",
"pino": "^10.4.0",
"pretty-bytes": "^7.2.0",
"prom-client": "^15.1.3",
"tar-stream": "^3.2.0",
"undici": "^7.24.4"
"tar-stream": "^3.2.2",
"undici": "^7.30.1"
},
"//overrides": {
"@sveltejs/kit > cookie": "SvelteKit requests ^0.6.0, which excludes the cookie validation security fix in 0.7.x. Remove when SvelteKit requires a patched version.",
"monaco-editor > dompurify": "Monaco pins DOMPurify to 3.2.7. Use ^3.4.16 for subsequent sanitisation security fixes; remove when Monaco requires an equally patched or newer version."
"monaco-editor > dompurify": "Monaco pins DOMPurify to 3.4.15. Use ^3.4.17 for subsequent sanitisation security fixes; remove when Monaco requires an equally patched or newer version."
},
"overrides": {
"@sveltejs/kit": {
"cookie": "^0.7.2"
},
"monaco-editor": {
"dompurify": "^3.4.16"
"dompurify": "^3.4.17"
}
}
}
5 changes: 1 addition & 4 deletions src/lib/auth-client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { createAuthClient } from 'better-auth/svelte';
import { genericOAuthClient } from 'better-auth/client/plugins';

export const authClient = createAuthClient({
plugins: [genericOAuthClient()]
});
export const authClient = createAuthClient();
5 changes: 2 additions & 3 deletions src/lib/components/editor/MonacoEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@

// Start loading in parallel with the rest of the page — not deferred to onMount.
// Guarded by `browser` because SvelteKit evaluates component scripts on the server too.
const workerImport = browser ? import('monaco-editor/esm/vs/editor/editor.worker?worker') : null;
const workerImport = browser ? import('monaco-editor/editor/editor.worker?worker') : null;
// The Trino SQL language pulls in the ANTLR parser (~1 MB); load it lazily alongside Monaco.
// It has no runtime dependency on monaco-editor, so starting it early does not
// load Monaco before the locale's NLS bundle below.
Expand All @@ -151,8 +151,7 @@
// Load Monaco's NLS bundle for the active locale. Static import strings are
// required — Vite cannot bundle bare-specifier template literals at build time.
if (getLocale() === 'de') {
// @ts-expect-error — Monaco ESM nls bundle has no types
await import('monaco-editor/esm/nls.messages.de.js');
await import('monaco-editor/nls/lang/de.js');
}

// By the time onMount fires the imports are likely already resolved.
Expand Down
5 changes: 2 additions & 3 deletions src/lib/components/editor/TextEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
return extLanguageMap[extension] ?? 'plaintext';
});

const workerImport = browser ? import('monaco-editor/esm/vs/editor/editor.worker?worker') : null;
const workerImport = browser ? import('monaco-editor/editor/editor.worker?worker') : null;

function toMonacoTheme(t: string): string {
return t === 'dark' ? 'vs-dark' : 'vs';
Expand All @@ -156,8 +156,7 @@
// Load Monaco's NLS bundle for the active locale. Static import strings are
// required — Vite cannot bundle bare-specifier template literals at build time.
if (getLocale() === 'de') {
// @ts-expect-error — Monaco ESM nls bundle has no types
await import('monaco-editor/esm/nls.messages.de.js');
await import('monaco-editor/nls/lang/de.js');
}

const EditorWorker = (await workerImport!).default;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/editor/TextEditor.svelte.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getLocale, setLocale } from '$lib/paraglide/runtime.js';
// TextEditor registers its save action, not Monaco itself.
const addAction = vi.hoisted(() => vi.fn());

vi.mock('monaco-editor/esm/vs/editor/editor.worker?worker', () => ({
vi.mock('monaco-editor/editor/editor.worker?worker', () => ({
default: class {}
}));

Expand Down
2 changes: 1 addition & 1 deletion src/lib/server/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('user.additionalFields.trinoUser', () => {

it('may be written by the OIDC callback', async () => {
await expect(
create({ email: 'alice@example.com', trinoUser: 'alice' }, '/oauth2/callback/:providerId')
create({ email: 'alice@example.com', trinoUser: 'alice' }, '/callback/:id')
).resolves.toBeUndefined();
});

Expand Down
26 changes: 16 additions & 10 deletions src/lib/server/auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { betterAuth } from 'better-auth';
import { APIError } from 'better-auth/api';
import { APIError, callbackOAuth } from 'better-auth/api';
import { drizzleAdapter } from '@better-auth/drizzle-adapter';
import { genericOAuth, openAPI } from 'better-auth/plugins';
import { sveltekitCookies } from 'better-auth/svelte-kit';
Expand All @@ -19,8 +19,9 @@ const log = loggingModule?.logger.child({ module: 'auth' });
// trinoUser is the Trino user we impersonate, so only the OIDC callback may
// write it. Marking the field `input: false` would also block that: better-auth
// drops such fields from the provider profile on sign-up (see #371). Database
// hooks see the endpoint's route template, not the request path.
const trinoUserWritePath = '/oauth2/callback/:providerId';
// hooks see the endpoint's route template, not the request path. The generic
// OAuth provider shares the social sign-in callback endpoint.
const trinoUserWritePath = callbackOAuth.path;

function guardTrinoUserWrite(data: Record<string, unknown>, context: { path?: string } | null) {
if (!('trinoUser' in data) || context?.path === trinoUserWritePath) return;
Expand All @@ -39,6 +40,9 @@ const extraScopes = (env.STACKABLE_COCKPIT_OIDC_EXTRA_SCOPES ?? '')

const oidcScopes = [...new Set([...defaultScopes, ...extraScopes])];

// OIDC profile claims are untyped; only non-empty strings are usable.
const claim = (value: unknown) => (typeof value === 'string' && value ? value : undefined);

// OIDC is enabled only when all required OIDC env vars are present.
export const oidcEnabled = !!(
env.STACKABLE_COCKPIT_OIDC_DISCOVERY_URL &&
Expand Down Expand Up @@ -98,10 +102,12 @@ export const auth = betterAuth({
// Trino user claim reach existing users instead of only new ones.
overrideUserInfo: true,
mapProfileToUser: async (profile) => {
const fullName = [profile.given_name, profile.family_name]
const fullName = [claim(profile.given_name), claim(profile.family_name)]
.filter(Boolean)
.join(' ');
const claimedTrinoUser = profile[trinoUserClaim];
const preferredUsername = claim(profile.preferred_username);
const email = claim(profile.email);
const claimedTrinoUser = claim(profile[trinoUserClaim]);
if (claimedTrinoUser) {
log?.debug(
{ trino_user_claim: trinoUserClaim },
Expand All @@ -114,13 +120,13 @@ export const auth = betterAuth({
);
}
return {
name: profile.name || fullName || profile.preferred_username || profile.email,
email: profile.email || '',
emailVerified: profile.email_verified ?? false,
image: profile.picture || null,
name: claim(profile.name) || fullName || preferredUsername || email,
email: email ?? '',
emailVerified: profile.email_verified === true,
image: claim(profile.picture),
// No `|| ''`: without an email better-auth rejects the sign-in before
// creating the user, so this never stores an empty Trino user.
trinoUser: claimedTrinoUser || profile.preferred_username || profile.email
trinoUser: claimedTrinoUser || preferredUsername || email
};
}
}
Expand Down
Loading
Loading