Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
8d16657
add monaco editor and simple trino client
labrenbe Feb 23, 2026
132bcb6
fix: run query shortcut not working when editor is selected
labrenbe Feb 23, 2026
da4c2c1
wip: add oidc authentication with better-auth
labrenbe Feb 25, 2026
0d0bcaf
fix invisible Enter symbol on button
labrenbe Feb 25, 2026
b018079
replace per-component theme detection with shared reactive state
labrenbe Feb 25, 2026
3983942
Add missing i18n message
labrenbe Feb 25, 2026
3aa197a
Remove undici & skip tls validation in dev through env
labrenbe Feb 25, 2026
8e6f333
Document tech debt in new file
labrenbe Feb 25, 2026
94f2fdc
disable all TLS validation when running as dev server
labrenbe Feb 25, 2026
c74e1b2
Merge remote-tracking branch 'origin/main' into feat/simple-trino-client
labrenbe Feb 25, 2026
2640e9e
Merge remote-tracking branch 'origin/main' into feat/simple-trino-client
labrenbe Feb 25, 2026
3c9bec4
Use pino for logs and add missing i18n
labrenbe Feb 25, 2026
033bf56
Move trino query execution to server-side form action
labrenbe Feb 26, 2026
5016014
Replace localStorage-polling hydration check with body.hydrated class
labrenbe Feb 26, 2026
70b04ae
Merge remote-tracking branch 'origin/feat/simple-trino-client' into f…
labrenbe Feb 27, 2026
d068f2b
Add authentication with OIDC using static config through envs
labrenbe Feb 27, 2026
5235388
Add dev setup script with Keycloak OIDC for Trino and mock OIDC for e…
labrenbe Mar 2, 2026
7891715
Switch Trino query form to JSON dataType
labrenbe Mar 2, 2026
997a76e
fix feedback from AI review
labrenbe Mar 2, 2026
8341ae1
make claim used for username configurable via env
labrenbe Mar 2, 2026
c0bfd9a
Update package-lock.json
labrenbe Mar 2, 2026
d9459bd
fix CI checks
labrenbe Mar 2, 2026
1cba644
fix ci errors
labrenbe Mar 3, 2026
d17a61a
Add note about dev/setup.sh to AGENTS.md
labrenbe Mar 3, 2026
0b32ca6
Add missing Trino connection validation to tech debt
labrenbe Mar 3, 2026
b5b6c25
Merge remote-tracking branch 'origin/main' into feat/oidc
labrenbe Mar 3, 2026
443c285
Remove password from connKey
labrenbe Mar 4, 2026
ddf25d5
remove broken authentication when oidc is diabled
labrenbe Mar 4, 2026
5c48758
fix UI bugs related to no-auth
labrenbe Mar 4, 2026
0141202
fix Docker build
labrenbe Mar 4, 2026
00015a1
Add /metrics to public paths
labrenbe Mar 5, 2026
fcdb1d6
fix: use log instead of console
labrenbe Mar 5, 2026
d664448
decouple logger from SvelteKit so it's importable outside the framework
labrenbe Mar 5, 2026
2c4083a
pin @better-auth/cli as a dev dependency and stop using @latest in mi…
labrenbe Mar 5, 2026
99e6064
remove OIDC RP-initiated logout to avoid terminating the user's entir…
labrenbe Mar 6, 2026
7c80445
improve code readibility
labrenbe Mar 6, 2026
e8244a6
use sveltekitCookies plugin to handle cookie management instead of ma…
labrenbe Mar 9, 2026
7e7e20a
enable better-auth openAPI plugin to expose API reference endpoints
labrenbe Mar 9, 2026
f1023e5
Merge remote-tracking branch 'origin/main' into feat/oidc
labrenbe Mar 9, 2026
e918a60
Replace (() => …) with .by()
labrenbe Mar 9, 2026
d6123c5
fix build issue
labrenbe Mar 9, 2026
8fac34f
Scope build-time placeholder env vars to the RUN instruction instead …
labrenbe Mar 9, 2026
a95dc3f
Replace manual .env file parser with Node.js built-in process.loadEnv…
labrenbe Mar 9, 2026
7f8ada2
Replace manual polyfill with Node.js built-in in E2E and Playwright…
labrenbe Mar 9, 2026
ad3131f
Add explanatory comment for the better-call version override in packa…
labrenbe Mar 10, 2026
14a0297
Revert vitest-browser-svelte to ^2.0.2
labrenbe Mar 10, 2026
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
File renamed without changes.
19 changes: 18 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
# Add project environment variables here when needed.
# A long random secret used to sign sessions and tokens (min 32 characters)
STACKABLE_UI_SESSION_SECRET=change-me-to-a-long-random-secret-min-32-chars

# The publicly accessible base URL of this application
STACKABLE_UI_BASE_URL=http://localhost:5173

# OIDC discovery URL (Keycloak, Entra ID, or any compliant OIDC provider)
STACKABLE_UI_OIDC_DISCOVERY_URL=https://your-idp.example.com/realms/your-realm/.well-known/openid-configuration

# The client ID and secret registered in your OIDC provider
STACKABLE_UI_OIDC_CLIENT_ID=stackable-ui
STACKABLE_UI_OIDC_CLIENT_SECRET=your-client-secret

# OIDC claim used as the username for Trino impersonation (default: preferred_username)
# STACKABLE_UI_OIDC_USERNAME_CLAIM=preferred_username

# Path to the SQLite database file (must be on a persistent volume in Kubernetes)
STACKABLE_UI_SQLITE_PATH=/data/auth.db

# Logging
# LOG_LEVEL=debug # Minimum log level (debug, info, warn, error). Default: debug in dev, info in prod.
Expand Down
6 changes: 6 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
STACKABLE_UI_SQLITE_PATH=.data/test-auth.db
STACKABLE_UI_OIDC_CLIENT_ID=mock-client
STACKABLE_UI_OIDC_CLIENT_SECRET=mock-secret
STACKABLE_UI_OIDC_DISCOVERY_URL=http://localhost:9090/.well-known/openid-configuration
STACKABLE_UI_SESSION_SECRET=e2e-test-session-secret-that-is-long-enough
STACKABLE_UI_BASE_URL=http://localhost:4173
10 changes: 0 additions & 10 deletions .github/workflows/pr_checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ jobs:
tests:
name: E2E Tests
runs-on: ubuntu-latest
env:
PLAYWRIGHT_BASE_URL: http://localhost:4173
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand All @@ -70,13 +68,5 @@ jobs:
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium firefox

- name: Build application
run: npm run build

- name: Start application
run: |
npm run preview &
timeout 60 bash -c 'until curl -sf http://localhost:4173 > /dev/null; do sleep 1; done'

- name: Run E2E tests
run: npm run test:e2e
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Thumbs.db

# Env
.env
.env.development
.env.development.bak
!.env.example
!.env.test

Expand All @@ -33,6 +35,9 @@ dist/

# Playwright
e2e/test-results
e2e/.auth
# Paraglide
src/lib/paraglide
project.inlang/cache/
.data/*
!.data/.gitkeep
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,14 @@ nvm install # installs the version from .node-version
nvm use # activates it in the current shell
```

### Local Setup

For a pre-configured local dev environment using OIDC, deploy & configure Keycloak and Trino on a kind cluster:

```bash
./dev/setup.sh # Creates and configures Keycloak. Deploys Trino with OIDC authentication. Runs auth database migration. Writes to .env.development which is used by dev server.
```

### Development

```bash
Expand Down
16 changes: 16 additions & 0 deletions TECH_DEBT.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ Trino error messages and Node.js exception messages are returned to the browser

---

### SQLite session store prevents horizontal scaling

**File:** `src/lib/server/auth.ts`, `deploy/helm/stackable-ui/values.yaml`

better-auth uses SQLite (via better-sqlite3) for session and user storage. SQLite only supports a single writer, so the deployment is limited to `replicaCount: 1`. A single pod failure means complete downtime with no failover. The long-term fix is to switch to PostgreSQL or a stateless session store (JWT/Redis) to allow horizontal scaling.

---

## API & Validation

### API route request body not validated with Zod
Expand All @@ -64,6 +72,14 @@ The `$effect` that persists connection settings only resets `queryId`, not `rows

---

### No validation that the connection target is a Trino instance

**File:** `src/routes/(app)/trino/+page.server.ts`

The query action sends whatever SQL the user provides to the configured connection URL without first verifying that the endpoint is actually a Trino instance. A user could point the URL at any HTTP server, and the app would blindly POST to it. We should validate new connections (e.g. by calling Trino's `/v1/info` endpoint) and reject URLs that do not respond as a Trino server.

---

## Infrastructure

### No Content Security Policy headers
Expand Down
5 changes: 4 additions & 1 deletion deploy/helm/stackable-ui/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ metadata:
name: {{ include "stackable-ui.fullname" . }}
labels:
{{- include "stackable-ui.labels" . | nindent 4 }}
data: {}
data:
oidc-discovery-url: {{ .Values.auth.discoveryUrl | quote }}
oidc-client-id: {{ .Values.auth.clientId | quote }}
base-url: {{ .Values.auth.baseUrl | quote }}
37 changes: 36 additions & 1 deletion deploy/helm/stackable-ui/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,52 @@ spec:
value: {{ .Values.config.nodeEnv | quote }}
- name: PORT
value: {{ .Values.service.targetPort | quote }}
- name: SESSION_SECRET
- name: STACKABLE_UI_SESSION_SECRET
valueFrom:
secretKeyRef:
name: {{ include "stackable-ui.fullname" . }}
key: session-secret
- name: STACKABLE_UI_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: {{ include "stackable-ui.fullname" . }}
key: oidc-client-secret
- name: STACKABLE_UI_BASE_URL
valueFrom:
configMapKeyRef:
name: {{ include "stackable-ui.fullname" . }}
key: base-url
- name: STACKABLE_UI_OIDC_DISCOVERY_URL
valueFrom:
configMapKeyRef:
name: {{ include "stackable-ui.fullname" . }}
key: oidc-discovery-url
- name: STACKABLE_UI_OIDC_CLIENT_ID
valueFrom:
configMapKeyRef:
name: {{ include "stackable-ui.fullname" . }}
key: oidc-client-id
- name: STACKABLE_UI_SQLITE_PATH
value: {{ .Values.config.databasePath | quote }}
{{- if .Values.auth.usernameClaim }}
- name: STACKABLE_UI_OIDC_USERNAME_CLAIM
value: {{ .Values.auth.usernameClaim | quote }}
{{- end }}
volumeMounts:
- name: tmp
mountPath: /tmp
- name: data
mountPath: /data
volumes:
- name: tmp
emptyDir: {}
- name: data
{{- if .Values.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ if .Values.persistence.existingClaim }}{{ .Values.persistence.existingClaim }}{{ else }}{{ include "stackable-ui.fullname" . }}-data{{ end }}
{{- else }}
emptyDir: {}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
Expand Down
17 changes: 17 additions & 0 deletions deploy/helm/stackable-ui/templates/pvc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "stackable-ui.fullname" . }}-data
labels:
{{- include "stackable-ui.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.persistence.accessMode }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- with .Values.persistence.storageClassName }}
storageClassName: {{ . }}
{{- end }}
{{- end }}
1 change: 1 addition & 0 deletions deploy/helm/stackable-ui/templates/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ metadata:
type: Opaque
stringData:
session-secret: {{ include "stackable-ui.sessionSecret" . | quote }}
oidc-client-secret: {{ .Values.auth.clientSecret | quote }}
32 changes: 32 additions & 0 deletions deploy/helm/stackable-ui/values.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
# Default values for stackable-ui

# NOTE: SQLite requires a single writer. Keep replicaCount at 1 when
# persistence.enabled is true to avoid concurrent-write corruption.
replicaCount: 1

image:
Expand Down Expand Up @@ -113,6 +115,36 @@ affinity: {}
config:
# Node.js environment
nodeEnv: production
# SQLite database path inside the container (should be on the persistence volume)
databasePath: "/data/auth.db"

# Session secret for signing cookies (auto-generated if not provided)
sessionSecret: ""

# OIDC authentication configuration
auth:
# The publicly accessible base URL of this application (used for OIDC callback URLs)
# Required. Example: https://stackable-ui.example.com
baseUrl: ""
# OIDC discovery URL (the full .well-known/openid-configuration URL)
# Required. Example: https://your-idp.example.com/realms/your-realm/.well-known/openid-configuration
discoveryUrl: ""
# Client ID registered in the OIDC provider
# Required.
clientId: ""
# Client secret registered in the OIDC provider
# Required.
clientSecret: ""
# OIDC claim used as the username for Trino impersonation (default: preferred_username)
usernameClaim: ""

# SQLite persistence for the Better Auth database.
# NOTE: Requires replicaCount: 1 — SQLite does not support concurrent writes from multiple pods.
persistence:
enabled: true
size: 1Gi
# Storage class name. Leave empty to use the cluster default.
storageClassName: ""
accessMode: ReadWriteOnce
# Set to reuse an existing PVC instead of creating a new one.
existingClaim: ""
41 changes: 41 additions & 0 deletions dev/keycloak.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
containers:
- name: keycloak
image: quay.io/keycloak/keycloak:26.1
args: ['start-dev']
env:
- name: KC_BOOTSTRAP_ADMIN_USERNAME
value: admin
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
value: admin
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: keycloak
namespace: default
spec:
type: NodePort
selector:
app: keycloak
ports:
- port: 8080
targetPort: 8080
nodePort: 30080
Loading