Skip to content

Latest commit

 

History

History
212 lines (156 loc) · 9.19 KB

File metadata and controls

212 lines (156 loc) · 9.19 KB
title Authentication
description JWT sessions, OAuth providers, email 2FA, external API keys, and desktop pairing for OpenSail

Tesslate OpenSail

Overview

OpenSail supports five authentication paths, all enforced by the orchestrator:

  1. Password (bcrypt) with optional email 2FA
  2. OAuth login (GitHub, Google)
  3. OAuth connection for third-party services (deployments, git providers, MCP platform apps)
  4. External API keys (tsk_*) for the external agent API and gateway
  5. Desktop pairing (tsk_* exchanged via deep link and stored in Stronghold)

Sessions use short-lived JWT access tokens with refresh token rotation. CSRF protection is a double-submit cookie pattern. Secrets at rest (OAuth tokens, channel credentials, deployment credentials) are encrypted with Fernet.

Password and email 2FA

`POST /api/auth/register` with `email`, `username`, `password`. The orchestrator hashes the password with bcrypt and returns an access token. The first account on a fresh install is promoted to admin. `POST /api/auth/login` with username or email and password. Returns an access token (default 30 min) and sets a refresh cookie (default 14 days). When `TWO_FA_ENABLED=true`, login triggers `POST /api/auth/2fa/send` which emails a 6-digit code. Submit with `POST /api/auth/2fa/verify`. Codes are Argon2 hashed, limited to 5 attempts, and expire in minutes. Access tokens renew automatically via the refresh cookie. No explicit refresh call is needed from the client.

Required environment variables for email flows: SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_SENDER_EMAIL, SMTP_USE_TLS.

OAuth login providers

Two providers ship out of the box. Both are optional; absence of credentials disables the provider gracefully.

Scopes: `user:email`, `read:user`.
| Variable | Purpose |
|----------|---------|
| `GITHUB_CLIENT_ID` | Client ID from github.com/settings/developers |
| `GITHUB_CLIENT_SECRET` | Client secret |
| `GITHUB_OAUTH_REDIRECT_URI` | Must match the OAuth app exactly, including protocol and path |

Local dev callback: `http://localhost/api/auth/github/callback`.
Scopes: `openid`, `email`, `profile`.
| Variable | Purpose |
|----------|---------|
| `GOOGLE_CLIENT_ID` | OAuth 2.0 client ID |
| `GOOGLE_CLIENT_SECRET` | Client secret |
| `GOOGLE_OAUTH_REDIRECT_URI` | Must match the Cloud Console setting |

Local dev callback: `http://localhost/api/auth/google/callback`.
Multiple providers can link to the same account. If the OAuth email matches an existing user, providers attach automatically.

OAuth for deployment targets and git

Connect providers for push-based deployments and private repo import. Configure client IDs and secrets under Settings -> Integrations. Tokens are Fernet-encrypted before being stored in DeploymentCredential.

Provider Env variables
Vercel VERCEL_CLIENT_ID, VERCEL_CLIENT_SECRET, VERCEL_OAUTH_REDIRECT_URI
Netlify NETLIFY_CLIENT_ID, NETLIFY_CLIENT_SECRET, NETLIFY_OAUTH_REDIRECT_URI
Heroku HEROKU_CLIENT_ID, HEROKU_CLIENT_SECRET, HEROKU_OAUTH_REDIRECT_URI
DigitalOcean DIGITALOCEAN_CLIENT_ID, DIGITALOCEAN_CLIENT_SECRET, DIGITALOCEAN_OAUTH_REDIRECT_URI

Git providers (GitHub, GitLab, Bitbucket) are optional. Without them you can still import public repos.

External API keys

The external agent API lets machine users invoke agents with a bearer token. Keys are minted from Settings -> API Keys (POST /api/external-api-keys). They are SHA-256 hashed before storage (ExternalAPIKey). Only the newly minted secret is ever shown.

Visit `Settings -> API Keys` and click **Create key**. Name it, pick scope (project-scoped or team-scoped), and copy the `tsk_*` secret immediately. `POST /api/external/agent/invoke` with `Authorization: Bearer tsk_...`. Returns `task_id` and `events_url` right away. Subscribe to `GET /api/external/agent/events/{task_id}` (SSE) or poll `GET /api/external/agent/status/{task_id}`. Delete the key in the UI or `DELETE /api/external-api-keys/{id}`. The hash is wiped; in-flight tasks abort on their next iteration.

See the external agent API guide for scopes, rate limits, and webhook callbacks.

Desktop pairing

The desktop app can pair to a cloud instance (tesslate.com or your own self-hosted cluster). Pairing unlocks the marketplace, bidirectional sync, and the remote K8s runtime.

In the cloud instance, `Settings -> API Keys -> Create desktop key`. This returns a short-lived deep-link URL (`opensail://pair?token=...`). Click the URL on the machine running the desktop app. The Tauri deep-link handler exchanges the token for a long-lived `tsk_*` key. The key is written to the Tauri Stronghold vault (encrypted on disk with a per-machine passphrase). The sidecar never sees the plaintext key at rest; it is injected into the per-request Authorization header by the Rust shell. `GET /api/desktop/session/ping` confirms pairing. The tray icon turns green.

Every cloud call made by the desktop app uses this key. Unpair by deleting it in the desktop Settings page, which also revokes it on the cloud side.

Session management

Access tokens default to 30 minutes (`ACCESS_TOKEN_EXPIRE_MINUTES`). Refresh tokens default to 14 days (`REFRESH_TOKEN_EXPIRE_DAYS`). Refresh is automatic while the browser tab is open. Production sets `COOKIE_SECURE=true`, `COOKIE_SAMESITE=lax`, and `COOKIE_DOMAIN=.your-apex.tld` so subdomain project URLs authenticate cleanly. `POST /api/auth/logout` clears the refresh cookie and invalidates the server-side refresh record.

Security model

Concern Mechanism
Password storage bcrypt with per-user salt
2FA codes Argon2 hashed, 5-attempt cap, minute TTL
Refresh rotation One-time-use refresh tokens, detected reuse invalidates the session
Secrets at rest Fernet (CHANNEL_ENCRYPTION_KEY, DEPLOYMENT_ENCRYPTION_KEY, falls back to a key derived from SECRET_KEY)
CSRF Double-submit cookie pattern with X-CSRF-Token header
OAuth state Signed, single-use state token; mismatches abort the flow
API keys tsk_* prefix, SHA-256 hashed in DB, shown once at creation
Desktop pairing Short-lived deep-link token; long-lived key lives in Stronghold
Rotating `SECRET_KEY` invalidates existing JWTs and may make Fernet-encrypted data unrecoverable unless you also rotate `CHANNEL_ENCRYPTION_KEY` and `DEPLOYMENT_ENCRYPTION_KEY` in sync.

Troubleshooting

Click **Forgot Password** on login, enter your email, and follow the link. Resets require a working SMTP configuration. Check the caps-lock state, try username instead of email, and check the email inbox (and spam) for a 2FA code if `TWO_FA_ENABLED=true`. Browser cache flushes often resolve stale cookies. The OAuth app's callback URL must exactly match `*_OAUTH_REDIRECT_URI`, including protocol (`http` vs `https`) and path. Just log in again. Data is safe; sessions can expire after extended inactivity. Check that `COOKIE_DOMAIN` covers the subdomain you are on in production. Verify the deep-link scheme (`opensail://`) is registered on the OS. On Windows, reinstall with admin rights if the registration is missing. The token expires in minutes; mint a fresh one.

Next steps

Mint keys, invoke the external agent API, set scopes. Install the desktop app and pair it to your cloud. OAuth, SMTP, and cookie settings for self-hosted. Full security overview in the architecture page.