From f19707042d611308ddbfcfb812f508f25b76a35d Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:20:58 +0530 Subject: [PATCH 1/4] docs: surface pub/sub, locks, mesh, predicates in Python capabilities --- .../content/docs/python/getting-started/capabilities.mdx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/python/getting-started/capabilities.mdx b/docs/content/docs/python/getting-started/capabilities.mdx index 21ba0fd0..5c9c9d65 100644 --- a/docs/content/docs/python/getting-started/capabilities.mdx +++ b/docs/content/docs/python/getting-started/capabilities.mdx @@ -11,6 +11,7 @@ import { CalendarClock, Activity, Plug, + Network, } from "lucide-react"; taskito ships a broad feature set out of the box. If you're evaluating it — or @@ -66,7 +67,13 @@ These are the capabilities most often assumed absent. They aren't. icon={} title="Extensibility" href="/python/guides/extensibility" - description="Pluggable serializers, per-task middleware, a fully async API, and Postgres or Redis backends." + description="Pluggable serializers, per-task middleware, enqueue predicates, argument interception, a fully async API, and Postgres or Redis backends." + /> + } + title="Distribution" + href="/python/guides/core/pubsub" + description="Topic pub/sub fan-out to independent subscribers, distributed locks, a decentralized work-stealing mesh, and streaming partial results." /> From ed08962d1d683192b735eabb9ddba6ba44711772 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:20:58 +0530 Subject: [PATCH 2/4] docs: add circuit breakers to Java capabilities --- docs/content/docs/java/getting-started/capabilities.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/content/docs/java/getting-started/capabilities.mdx b/docs/content/docs/java/getting-started/capabilities.mdx index 02201f7a..cd844e74 100644 --- a/docs/content/docs/java/getting-started/capabilities.mdx +++ b/docs/content/docs/java/getting-started/capabilities.mdx @@ -26,6 +26,7 @@ core — pick a backend, describe tasks, run workers. |---|---| | Retries with backoff curves (`RetryPolicy`), dead-letter queue | [Retries](/java/guides/reliability/retries) · [Dead-letter](/java/guides/reliability/dead-letter) | | Per-attempt timeouts | [Timeouts](/java/guides/reliability/timeouts) | +| Circuit breakers (`CircuitBreakerConfig`) | [Circuit breakers](/java/guides/reliability/circuit-breakers) | | Idempotent enqueue (`uniqueKey`) | [Idempotency](/java/guides/reliability/idempotency) | | Distributed locks | [Locks](/java/guides/reliability/locks) | | At-least-once delivery | [Guarantees](/java/guides/reliability/guarantees) | From 819bfbd32abb72b10db0f0ed378a3e59311d81a3 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:30:08 +0530 Subject: [PATCH 3/4] docs: add Java structured-notes guide --- .../docs/java/guides/observability/meta.json | 2 +- .../docs/java/guides/observability/notes.mdx | 109 ++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 docs/content/docs/java/guides/observability/notes.mdx diff --git a/docs/content/docs/java/guides/observability/meta.json b/docs/content/docs/java/guides/observability/meta.json index c6d9beae..ac58e2a4 100644 --- a/docs/content/docs/java/guides/observability/meta.json +++ b/docs/content/docs/java/guides/observability/meta.json @@ -1 +1 @@ -{ "title": "Observability", "pages": ["monitoring", "logging"] } +{ "title": "Observability", "pages": ["monitoring", "logging", "notes"] } diff --git a/docs/content/docs/java/guides/observability/notes.mdx b/docs/content/docs/java/guides/observability/notes.mdx new file mode 100644 index 00000000..c6b266e1 --- /dev/null +++ b/docs/content/docs/java/guides/observability/notes.mdx @@ -0,0 +1,109 @@ +--- +title: Structured Notes +description: "Attach a bounded map of annotations to a job at enqueue time — capped at 15 fields, dashboard-rendered." +--- + +`notes` is a structured annotation field on every job — a small map (at most +**15** top-level keys) you attach when enqueuing and read back from the job. +Unlike `metadata`, which is a free-form JSON string blob, `notes` is validated +when you build the enqueue options and rendered by the dashboard as a key/value +table. + +## When to use `notes` vs `metadata` + +| | `notes` | `metadata` | +|---|---|---| +| Type at enqueue | `Map` | `String` (pre-encoded JSON) | +| Top-level fields | ≤ 15 | unbounded | +| Validation | At build time | None | +| Dashboard render | Key/value table | Raw JSON dump | +| Survives DLQ retry | Yes | Yes | +| Best for | User-visible annotations | Operational/debug context | + +Use **`notes`** for short, user-readable annotations a human or the dashboard +would want to scan: customer IDs, business-priority reasons, short comments. Use +**`metadata`** when you need to attach an opaque JSON blob without size +constraints (trace IDs, request envelopes, etc.). + +## Writing notes + +Pass a `Map` to `notes(...)` on the enqueue-options builder: + +```java +import org.byteveda.taskito.task.EnqueueOptions; +import java.util.Map; + +String id = taskito.enqueue(processOrder, order, EnqueueOptions.builder() + .notes(Map.of( + "customer_id", "cus_abc", + "tier", "gold", + "priority_reason", "VIP onboarding")) + .build()); +``` + +Passing `null` clears any previously set notes. The map is validated and +canonically encoded to JSON at `build()` time, so an invalid map fails fast +before the job is enqueued. + +## Reading notes back + +`Job` carries the raw JSON string on `notes`, plus a parsed view via +`notesMap()`: + +```java +import org.byteveda.taskito.model.Job; +import java.util.Map; +import java.util.Optional; + +Optional> notes = taskito.getJob(id) + .flatMap(Job::notesMap); + +notes.ifPresent(n -> System.out.println(n.get("customer_id"))); // cus_abc +``` + +`notesMap()` returns `Optional.empty()` when the job carries no notes, and the +untouched JSON string is on `Job.notes` if you need it verbatim. + +## Validation rules + +Validation runs when the options are built (the Rust storage layer receives an +already-encoded JSON string and stores it verbatim). The contract lives in +`org.byteveda.taskito.serialization.Notes`: + +| Constraint | Default | Constant | +|---|---|---| +| Max top-level fields | 15 | `Notes.MAX_FIELDS` | +| Max key length | 64 chars | `Notes.MAX_KEY_LENGTH` | +| Max string value length | 500 chars | `Notes.MAX_VALUE_LENGTH` | +| Max nesting depth | 3 | `Notes.MAX_DEPTH` | +| Max encoded size | 4096 bytes | `Notes.MAX_BYTES` | + +Values may be any JSON primitive (string, number, boolean, null), plus nested +`List` or `Map` within the depth cap. + +Violations throw `NotesValidationException` +(`org.byteveda.taskito.errors.NotesValidationException`); the message names the +offending key or constraint so it can be surfaced directly to end users: + +```java +import org.byteveda.taskito.errors.NotesValidationException; + +try { + EnqueueOptions.builder().notes(tooManyFields).build(); +} catch (NotesValidationException e) { + // "notes may not have more than 15 fields, got 20" +} +``` + +## Dashboard rendering + +Notes are surfaced on the job detail page as a fixed-size key/value table next +to the Metadata card. Because the field is capped at 15 entries, the table is +always small enough to scan without scrolling. + +## Cross-backend behavior + +The `notes` column lives on the `jobs`, `dead_letter`, and `archived_jobs` +tables on SQLite and PostgreSQL, and rides along with the job's JSON +representation on Redis. Notes survive DLQ moves and retries (the original notes +are restored on the re-enqueued job), job replay, and archival. From 577b197e42ae6918ad3fc9a0db6b080aa441c842 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:30:08 +0530 Subject: [PATCH 4/4] docs: add Node dashboard SSO guide --- .../docs/node/guides/operations/meta.json | 2 +- .../docs/node/guides/operations/sso.mdx | 322 ++++++++++++++++++ 2 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 docs/content/docs/node/guides/operations/sso.mdx diff --git a/docs/content/docs/node/guides/operations/meta.json b/docs/content/docs/node/guides/operations/meta.json index adb52714..394b335b 100644 --- a/docs/content/docs/node/guides/operations/meta.json +++ b/docs/content/docs/node/guides/operations/meta.json @@ -1 +1 @@ -{ "title": "Operations", "pages": ["backends", "inspection", "dashboard", "dashboard-api", "mesh", "keda", "cli", "testing", "security", "troubleshooting", "deployment", "migration"] } +{ "title": "Operations", "pages": ["backends", "inspection", "dashboard", "dashboard-api", "sso", "mesh", "keda", "cli", "testing", "security", "troubleshooting", "deployment", "migration"] } diff --git a/docs/content/docs/node/guides/operations/sso.mdx b/docs/content/docs/node/guides/operations/sso.mdx new file mode 100644 index 00000000..68bfb4ef --- /dev/null +++ b/docs/content/docs/node/guides/operations/sso.mdx @@ -0,0 +1,322 @@ +--- +title: SSO (OAuth & OIDC) +description: "Sign in to the dashboard with Google, GitHub, or any OIDC provider. Domain/org allowlists, OAuth-only mode." +--- + +The dashboard's [session auth](/node/guides/operations/dashboard) supports native +sign-in for **Google**, **GitHub**, and any **OIDC-compliant** provider (Okta, +Auth0, Keycloak, Microsoft Entra, Dex, …). Multiple OIDC providers can run side +by side, each its own button on the login screen. OAuth requires session auth to +be enabled (`serveDashboard(queue, { authEnabled: true })`, or +`taskito dashboard --auth`; the dashboard serves openly by default) and is itself +off by default — setting any provider's env vars turns it on, alongside password +login unless you opt out. + + + ID-token validation for Google and generic OIDC runs on Node's built-in + `node:crypto` — signature verification against the provider's JWKS, then the + issuer / audience / nonce / expiry claim checks. Unlike the Java SDK (which + needs `nimbus-jose-jwt` on the classpath), **the Node SDK bundles no JOSE + library and needs no extra install** — everything works with `@byteveda/taskito` + alone on Node 20+. GitHub login is plain OAuth2 (no id-token to verify) and + likewise needs nothing extra. + + +## How it works + +>D: GET /api/auth/providers + D-->>B: { password_enabled, providers: [...] } + + Note over B: user clicks "Continue with Google" + B->>D: GET /api/auth/oauth/start/google?next=/jobs + Note over D: mint state + nonce + PKCE verifier
persist state row (5-min TTL, single-use) + D-->>B: 302 to provider authorize URL + + B->>P: GET /authorize?... + Note over P: user consents + P-->>B: 302 to /api/auth/oauth/callback/google?code=...&state=... + + B->>D: GET /api/auth/oauth/callback/google + Note over D: consume state (deleted before parsing)
exchange code for tokens + D->>P: POST token endpoint (code + code_verifier) + P-->>D: { id_token, access_token } + Note over D: verify JWKS signature / iss / aud / exp / nonce
enforce allowlist
get-or-create user, create session + D-->>B: 302 to next
Set-Cookie: taskito_session, taskito_csrf`} +/> + +State is single-use — `OAuthStateStore.consume` deletes the row (from the +queue's settings store, under `auth:oauth_state:`) before parsing it, so a +replayed callback finds nothing — and time-bounded (5-minute TTL). PKCE with +`S256`, the OIDC nonce, and the ID-token signature (against the provider's JWKS) +are all enforced server-side. + +`serveDashboard` wires this up for you: with `authEnabled: true` and no explicit +`oauth` option, it builds the flow from the `TASKITO_DASHBOARD_OAUTH_*` +environment variables. Invalid or partial config is logged and OAuth is disabled +(password login still works) rather than crashing startup. + +```ts +import { Queue, serveDashboard } from "@byteveda/taskito"; + +const queue = new Queue({ dbPath: "taskito.db" }); +// authEnabled turns on session auth; OAuth is built from the +// TASKITO_DASHBOARD_OAUTH_* env vars when they are set. +serveDashboard(queue, { port: 8787, authEnabled: true }); +``` + + + To configure providers in code instead of env vars, build the flow yourself + and pass it as `oauth`: + + ```ts + import { Queue, OAuthFlow, oauthConfigFromEnv, serveDashboard } from "@byteveda/taskito"; + + const queue = new Queue({ dbPath: "taskito.db" }); + const config = oauthConfigFromEnv(); // or hand-build an OAuthConfig + serveDashboard(queue, { + authEnabled: true, + oauth: config ? new OAuthFlow(queue, config) : undefined, + }); + ``` + + +## Quick start: Google login + +1. **Create an OAuth client** in the + [Google Cloud Console → APIs & Services → Credentials](https://console.cloud.google.com/apis/credentials) + (type *Web application*) and register the callback URL: + + ``` + https://taskito.your-company.com/api/auth/oauth/callback/google + ``` + + For local development, `http://localhost:8787/api/auth/oauth/callback/google` + works without HTTPS (8787 is the dashboard's default port). + +2. **Set env vars** before starting the dashboard: + + ```bash + export TASKITO_DASHBOARD_OAUTH_REDIRECT_BASE_URL=https://taskito.your-company.com + export TASKITO_DASHBOARD_OAUTH_GOOGLE_CLIENT_ID=...apps.googleusercontent.com + export TASKITO_DASHBOARD_OAUTH_GOOGLE_CLIENT_SECRET=... + # Restrict logins to your Google Workspace domain: + export TASKITO_DASHBOARD_OAUTH_GOOGLE_ALLOWED_DOMAINS=your-company.com + ``` + +3. **Start the dashboard** with session auth on + (`serveDashboard(queue, { authEnabled: true })` or `taskito dashboard --auth`). + The login screen now shows a "Continue with Google" button above the password + form. + +When exactly one domain is allowlisted, the dashboard also passes it to Google as +the `hd` hint so the right Workspace account is pre-selected — a UX nicety only; +enforcement always happens server-side against the verified email. + +## GitHub login + +GitHub has no OIDC id-token, so identity comes from `GET /user` and +`GET /user/emails`; org membership is verified via +`GET /orgs/{org}/members/{login}`. + +1. Create a [GitHub OAuth App](https://github.com/settings/developers). Set + the *Authorization callback URL* to + `https://taskito.your-company.com/api/auth/oauth/callback/github`. + +2. Env vars: + + ```bash + export TASKITO_DASHBOARD_OAUTH_REDIRECT_BASE_URL=https://taskito.your-company.com + export TASKITO_DASHBOARD_OAUTH_GITHUB_CLIENT_ID=Iv1.xxxxx + export TASKITO_DASHBOARD_OAUTH_GITHUB_CLIENT_SECRET=... + # Restrict logins to members of these GitHub orgs: + export TASKITO_DASHBOARD_OAUTH_GITHUB_ALLOWED_ORGS=your-org,partner-org + ``` + +When `_ALLOWED_ORGS` is set, the requested scope automatically grows from +`read:user user:email` to add `read:org`, so the membership check returns +reliable results for private orgs. + + + A GitHub account with no `primary && verified` email (from + `GET /user/emails`) always lands in the `viewer` role, even if listed in + `TASKITO_DASHBOARD_OAUTH_ADMIN_EMAILS` — an unverified email can't be + trusted to grant admin. + + +## Generic OIDC (Okta, Auth0, Keycloak, Microsoft, …) + +Generic OIDC providers are **named slots** — each gets its own callback URL, +its own namespaced users, and its own button on the login screen. + +```bash +export TASKITO_DASHBOARD_OAUTH_REDIRECT_BASE_URL=https://taskito.your-company.com + +# List the slots first. +export TASKITO_DASHBOARD_OAUTH_OIDC_PROVIDERS=okta,microsoft + +# Then per-slot config (slot name uppercased; '-' becomes '_'). +export TASKITO_DASHBOARD_OAUTH_OIDC_OKTA_CLIENT_ID=... +export TASKITO_DASHBOARD_OAUTH_OIDC_OKTA_CLIENT_SECRET=... +export TASKITO_DASHBOARD_OAUTH_OIDC_OKTA_DISCOVERY_URL=https://acme.okta.com/.well-known/openid-configuration +export TASKITO_DASHBOARD_OAUTH_OIDC_OKTA_LABEL="Acme SSO" +export TASKITO_DASHBOARD_OAUTH_OIDC_OKTA_ALLOWED_DOMAINS=your-company.com + +export TASKITO_DASHBOARD_OAUTH_OIDC_MICROSOFT_CLIENT_ID=... +export TASKITO_DASHBOARD_OAUTH_OIDC_MICROSOFT_CLIENT_SECRET=... +export TASKITO_DASHBOARD_OAUTH_OIDC_MICROSOFT_DISCOVERY_URL=https://login.microsoftonline.com/TENANT/v2.0/.well-known/openid-configuration +export TASKITO_DASHBOARD_OAUTH_OIDC_MICROSOFT_LABEL="Microsoft 365" +``` + +Each slot requires `_CLIENT_ID`, `_CLIENT_SECRET`, and `_DISCOVERY_URL` — a slot +listed in `_OIDC_PROVIDERS` with any of those missing fails startup +(fail-fast). The callback URL for each slot is +`{REDIRECT_BASE_URL}/api/auth/oauth/callback/{slot}` — register that exact +URL with the identity provider. + +Slot names must match `^[a-z][a-z0-9_-]{0,31}$` and can't collide with +`google` / `github` (the built-ins). A user signing in through an OIDC slot is +namespaced as `{slot}:{subject}`, so two providers issuing overlapping subject +values never collide. Because the env prefix uppercases the slot and turns `-` +into `_`, two slots that would map to the same prefix (for example `okta-eu` and +`okta_eu`) are rejected at startup rather than silently sharing one credential +set. + +## Role assignment for OAuth users + +The first time someone signs in via any provider, the dashboard decides their +role with this precedence: + +1. **`TASKITO_DASHBOARD_OAUTH_ADMIN_EMAILS` match** — a case-insensitive match + against a **verified** email → `admin`. +2. **Everyone else** → `viewer`. There is no first-user fallback: even the very + first OAuth login gets `viewer` unless its verified email is on the admin + list. Admin access comes only from the allowlist, the first-run setup screen, + or the `TASKITO_DASHBOARD_ADMIN_USER` / `TASKITO_DASHBOARD_ADMIN_PASSWORD` + bootstrap. + +```bash +export TASKITO_DASHBOARD_OAUTH_ADMIN_EMAILS=alice@your-company.com,bob@your-company.com +``` + + + If providers are configured but `TASKITO_DASHBOARD_OAUTH_ADMIN_EMAILS` is + empty, the dashboard logs a startup warning — every OAuth login would get + `viewer`, so an OAuth-only deployment would have zero admins. Seed at least one + admin via the allowlist or the env bootstrap. + + +A user's role is **not** re-evaluated on later logins — change it from the +dashboard once the user exists. Their email and display name refresh from each +login's claims. + +## OAuth-only mode + +Disable password login entirely: + +```bash +export TASKITO_DASHBOARD_PASSWORD_AUTH_ENABLED=false +``` + +Startup **fails** (an `OAuthConfigError` is raised at config-parse time) if you +disable password auth without configuring at least one provider — that would +leave no way to log in. With password auth off and at least one provider +configured, the login page hides the username/password form and renders only the +provider buttons. The flag accepts `true/false`, `1/0`, `yes/no`, `on/off` and +defaults to `true`. + +## Security model + +| Control | Implementation | +|---|---| +| **PKCE** | `S256` challenge — `base64url(sha256(verifier))` — derived from a 32-byte random verifier (RFC 7636). | +| **State** | 32-byte URL-safe random, persisted server-side in the queue's settings store under `auth:oauth_state:` with a 5-minute TTL. Deleted on first read — a replayed callback always fails. | +| **Nonce** | 16-byte random, sent in the OIDC authorize request, checked against the ID-token's `nonce` claim. (GitHub has no id-token, so nonce is unused there; PKCE still applies.) | +| **ID-token signature** | Verified against the provider's JWKS using Node's built-in `node:crypto`. Only asymmetric algorithms are accepted — `RS256/384/512`, `PS256/384/512`, `ES256/384/512`; an `alg:none` or HMAC (`HS*`) token has no matching asymmetric key and is rejected. | +| **iss / aud / exp** | All checked; 60-second clock-skew tolerance on `exp`; a multi-audience token must additionally carry `azp == client_id`. | +| **Discovery endpoints** | The `authorization_endpoint`, `token_endpoint`, and `jwks_uri` read from the discovery document must be `https` (plain `http` only for local hosts) before any code or client secret is sent. | +| **Open redirect** | The `next` query param must be a bare rooted path (`isSafeRedirect`) — no scheme, no host, no `//` or `/\` prefix. Falls back to `/`. | +| **HTTPS required** | The redirect base URL must be `https://` unless the host is `localhost` / `127.0.0.1` / `::1`. A misconfiguration is rejected at config-parse time. | +| **Provider tokens** | Never persisted — only the verified identity produces a Taskito session. | +| **Cross-provider identity** | A given `(slot, subject)` always maps to one user (`{slot}:{subject}`); the same email at two different providers creates two distinct users. | + +## API surface + +| Method | Path | What it does | +|---|---|---| +| `GET` | `/api/auth/providers` | Public. `{password_enabled, providers: [{slot, label, type}]}` for the login UI. When OAuth is unconfigured, falls through to the password-only handler. | +| `GET` | `/api/auth/oauth/start/{slot}` | Public. Mints state, 302s to the provider's authorize URL. Accepts `?next=/path` (validated). | +| `GET` | `/api/auth/oauth/callback/{slot}` | Public. Validates state, exchanges the code, enforces the allowlist, creates/refreshes the user, sets cookies, 302s to `next`. | + +The callback sets the same `taskito_session` + `taskito_csrf` cookies as +password login (`HttpOnly` on the session cookie, `SameSite=Strict`, `Secure` +unless `secureCookies: false`) — every other dashboard route works identically +once you're signed in. + +## Troubleshooting + +Callback failures redirect to `/login?error=` rather than rendering +JSON: + +- **`oauth_state_invalid`** — the state row expired (5-minute window) or was + already consumed. Usually the user pressed back/refresh after the provider + redirected; have them restart the flow. +- **`oauth_denied`** — the identity was fetched successfully but rejected by + an allowlist (`ALLOWED_DOMAINS` / `ALLOWED_ORGS`). Widen the allowlist or + remove it. +- **`oauth_failed`** — token exchange, signature verification, or a claim + check failed. Check server logs for the underlying message (issuer + mismatch, expired token, missing claim, provider error). +- **`oauth_not_configured`** (`404` on `start`/`callback`) — the slot isn't + registered. Either the env vars weren't set, or config parsing failed at + startup (check for a startup warning) and OAuth degraded to disabled. + +**Provider button missing** — `GET /api/auth/providers` returns the list the +login screen renders. If a provider you configured isn't there, check the +server log at startup: `buildOauthFlowFromEnv` catches any `OAuthConfigError` +from a partial or invalid config (for example a Google client id with no +matching secret) and logs `OAuth disabled — invalid configuration: …`, +disabling OAuth entirely rather than failing to start. + +## Env var reference + +```bash +# Required when any provider is configured. +TASKITO_DASHBOARD_OAUTH_REDIRECT_BASE_URL=https://taskito.company.com + +# Google. +TASKITO_DASHBOARD_OAUTH_GOOGLE_CLIENT_ID=... +TASKITO_DASHBOARD_OAUTH_GOOGLE_CLIENT_SECRET=... +TASKITO_DASHBOARD_OAUTH_GOOGLE_ALLOWED_DOMAINS=company.com,partner.com # optional + +# GitHub. +TASKITO_DASHBOARD_OAUTH_GITHUB_CLIENT_ID=Iv1.xxxxx +TASKITO_DASHBOARD_OAUTH_GITHUB_CLIENT_SECRET=... +TASKITO_DASHBOARD_OAUTH_GITHUB_ALLOWED_ORGS=org1,org2 # optional + +# Generic OIDC — list slots, then configure each one. +TASKITO_DASHBOARD_OAUTH_OIDC_PROVIDERS=okta,microsoft +TASKITO_DASHBOARD_OAUTH_OIDC_OKTA_CLIENT_ID=... +TASKITO_DASHBOARD_OAUTH_OIDC_OKTA_CLIENT_SECRET=... +TASKITO_DASHBOARD_OAUTH_OIDC_OKTA_DISCOVERY_URL=https://acme.okta.com/.well-known/openid-configuration +TASKITO_DASHBOARD_OAUTH_OIDC_OKTA_LABEL=Acme SSO # optional +TASKITO_DASHBOARD_OAUTH_OIDC_OKTA_ALLOWED_DOMAINS=company.com # optional + +# Role bootstrap. +TASKITO_DASHBOARD_OAUTH_ADMIN_EMAILS=alice@company.com,bob@company.com # optional + +# Disable password login (OAuth-only mode). Defaults to true. +TASKITO_DASHBOARD_PASSWORD_AUTH_ENABLED=false # optional +``` + +Session auth itself (required for OAuth) is enabled with +`serveDashboard(queue, { authEnabled: true })` or `taskito dashboard --auth`, and +an initial admin can be bootstrapped headlessly with +`TASKITO_DASHBOARD_ADMIN_USER` / `TASKITO_DASHBOARD_ADMIN_PASSWORD` — see the +[dashboard guide](/node/guides/operations/dashboard).