mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 01:54:50 +08:00
* fix(security): verify social-login token audience and bind every purpose token
GHSA-58x4-7mw9-gmqg (critical): POST /auth/signin.email.social accepted any
provider access token that resolved to a victim's email — another app's token or
a GitHub PAT — and signed the caller in as that user. Each provider is now
introspected against an allow-list of OAuth client ids (Google tokeninfo aud/azp
plus email_verified, GitHub /applications/{client_id}/token, Facebook
debug_token app_id) and fails closed when no client is configured. Twitter/X is
refused, since it exposes no verified email. One normaliser rejects an empty id
or email, so an undefined value can no longer reach a find() and be dropped by
TypeORM's undefined:'ignore' behaviour, which returned every user in every
tenant.
GHSA-28wv-vrxj-rp4q (medium): tokens signed with JWT_SECRET were interchangeable.
New signPurposeToken/verifyPurposeToken pin a purpose claim, required non-empty
claims and HS256. Workspace sign-in, invoice share, estimate, invite, team-join,
appointment and password-reset tokens are typed; public invoice and estimate
links are bound to the stored row and the URL id; access-token consumers
(JwtStrategy, RegisterAuthorizationGuard, Zapier, Plane) reject a token whose
purpose says it is something else. Untyped legacy tokens are accepted only where
they are also bound to a stored row, and never on signin.workspace.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(cspell): add the new vocabulary and use US spellings
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(auth): review follow-ups on the purpose-token hardening
Addresses the bot review round on PR #10241. No security behaviour is relaxed;
every change either tightens a check or is a readability fix with the same
runtime semantics, and the affected suites were re-run (9 suites / 155 tests).
- Password reset now goes through `verifyPurposeToken(TokenPurposeEnum.PASSWORD_RESET)`
instead of a bare `verify()` plus a string-literal purpose comparison
(CodeRabbit). It additionally requires a non-empty `id` claim: an `undefined`
id reaching `findOneByIdString` widens the lookup instead of failing closed,
which is exactly the class of bug this PR exists to remove. The stored
password_reset row still binds the token, so this is defence in depth.
`verify` and `JWT_ALGORITHMS` are no longer imported there.
- `JwtStrategy.validate` moves the employeeId/organizationId claim checks into
`attachEmployeeAndOrganizationContext` (SonarCloud: cognitive complexity 16 >
15). The helper RETURNS the UnauthorizedException instead of throwing, so the
exact exceptions and messages the callback received before are unchanged, and
three specs now cover the organization branch (member missing, employee in
another organization, happy path). Control: inverting the rejection makes 12
of the 30 tests in that suite fail.
- `normalizeSocialIdentity` extracts its nested ternary into
`normalizeProviderAccountId` (SonarCloud), same accepted values as before:
trimmed string, or a positive safe integer stringified for GitHub.
- `Number.NaN` over `NaN` in the reschedule-token lifetime (SonarCloud), and the
two unused `catch (error)` bindings in PublicInvoiceService are now bare
`catch`.
Not changed, deliberately: Greptile's P1 "mixed-case emails fail lookup". The
social lookup already queries BOTH the normalised (lowercased) address and the
provider's exact spelling, which is a strict superset of what this code did
before the PR, so nothing regressed. Matching stored emails case-insensitively
would let the holder of `a@x.com` sign into an account stored as `A@x.com` —
a widening of an authentication lookup that password login does not perform —
and belongs in a repo-wide email-normalisation change, not in this advisory fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
947 lines
36 KiB
Bash
947 lines
36 KiB
Bash
# The name of the application.
|
|
APP_NAME="Gauzy"
|
|
|
|
# The URL for the application logo.
|
|
APP_LOGO="http://localhost:4200/assets/images/logos/logo_Gauzy.png"
|
|
|
|
# The signature or tagline for the application.
|
|
APP_SIGNATURE="Gauzy"
|
|
|
|
# The link to the application.
|
|
APP_LINK="http://localhost:4200"
|
|
|
|
# The URL for an email confirmation in the application.
|
|
APP_EMAIL_CONFIRMATION_URL="http://localhost:4200/#/auth/confirm-email"
|
|
|
|
# The URL for magic sign-in in the application.
|
|
APP_MAGIC_SIGN_URL="http://localhost:4200/#/auth/magic-sign-in"
|
|
|
|
# Set true if running inside the Docker container
|
|
IS_DOCKER=false
|
|
|
|
# Deployed release version (git tag, e.g. v111.2.10) and full commit SHA, shown in the
|
|
# web UI footer and returned by GET /api/version. Normally injected automatically at
|
|
# Docker build time (build-args, see .deploy/*/Dockerfile); leave empty for source runs.
|
|
GAUZY_APP_VERSION=
|
|
GAUZY_APP_COMMIT=
|
|
|
|
# Format: https://hook.{region}.make.com/{webhook-id}
|
|
GAUZY_MAKE_WEBHOOK_URL=
|
|
|
|
# Make.com Platforms integration
|
|
GAUZY_MAKE_API_URL="https://hook.us2.make.com/api/v2"
|
|
GAUZY_MAKE_BASE_URL="https://www.make.com"
|
|
GAUZY_MAKE_CLIENT_ID=
|
|
GAUZY_MAKE_CLIENT_SECRET=
|
|
GAUZY_MAKE_REDIRECT_URL="${API_BASE_URL}/api/integration/make-com/oauth/callback"
|
|
GAUZY_MAKE_POST_INSTALL_URL="${CLIENT_BASE_URL}/#/pages/integrations/make"
|
|
GAUZY_MAKE_DEFAULT_SCOPES="offline_access"
|
|
|
|
# ActivePieces platforms integration
|
|
ACTIVEPIECES_BASE_URL="https://cloud.activepieces.com"
|
|
GAUZY_ACTIVEPIECES_API_KEY=
|
|
|
|
#SIM platform integration
|
|
SIM_DEFAULT_BASE_URL="https://www.sim.ai"
|
|
GAUZY_SIM_API_KEY=
|
|
|
|
# Set true if running as a Demo
|
|
DEMO=false
|
|
|
|
# DO (DIGITALOCEAN), AWS, AZURE, CIVO, CW (COREWEAVE), HEROKU, LINODE, LOCAL, OVH, SCALEWAY, VULTR, etc
|
|
CLOUD_PROVIDER=
|
|
|
|
ALLOW_SUPER_ADMIN_ROLE=true
|
|
|
|
# Set to Gauzy API base URL
|
|
API_BASE_URL=http://localhost:3000
|
|
|
|
# Set to Gauzy UI base URL
|
|
CLIENT_BASE_URL=http://localhost:4200
|
|
|
|
# Set to Website Platform
|
|
PLATFORM_WEBSITE_URL=https://gauzy.co
|
|
PLATFORM_WEBSITE_DOWNLOAD_URL=https://gauzy.co/downloads
|
|
|
|
# DB_ORM: typeorm | mikro-orm
|
|
DB_ORM=typeorm
|
|
|
|
# DB_TYPE: sqlite | postgres | better-sqlite3 | mysql
|
|
DB_TYPE=better-sqlite3
|
|
|
|
# DB Connection Parameters
|
|
# DB_HOST=localhost
|
|
## DB Port. The default for PostgreSQL - 5432, for MySQL - 3306
|
|
# DB_PORT=5432
|
|
# DB_NAME=gauzy
|
|
## DB Username. The default for PostgreSQL is 'postgres', for MySQL it's 'root'
|
|
# DB_USER=postgres
|
|
# DB_PASS=root
|
|
# DB_LOGGING=all
|
|
# DB_POOL_SIZE=40
|
|
# DB_POOL_SIZE_KNEX=10
|
|
# DB_CONNECTION_TIMEOUT=5000
|
|
# DB_IDLE_TIMEOUT=10000
|
|
# DB_SLOW_QUERY_LOGGING_TIMEOUT=10000
|
|
# DB_SSL_MODE=false
|
|
## If you want to use SSL and set DB_SSL_MODE=true, set the following environment variable
|
|
## with base64 encoded SSL certificate for DB
|
|
# DB_CA_CERT=
|
|
# Configuration for Worker Queue and Scheduler
|
|
WORKER_QUEUE_ENABLED=false
|
|
WORKER_SCHEDULER_ENABLED=false
|
|
WORKER_DEFAULT_QUEUE=gauzy_worker_default_queue
|
|
WORKER_TIMEZONE=UTC
|
|
|
|
# Redis Connection Parameters
|
|
REDIS_ENABLED=false
|
|
REDIS_HOST=
|
|
REDIS_PASSWORD=
|
|
REDIS_PORT=
|
|
REDIS_USER=
|
|
REDIS_TLS=false
|
|
# redis[s]://[[username][:password]@][host][:port][/db-number]
|
|
REDIS_URL=redis://localhost:6379
|
|
|
|
# ============================================================================
|
|
# SECURITY: Authentication & session secrets
|
|
# Set each of these to a strong, UNIQUE, random value before deploying, e.g.:
|
|
# openssl rand -hex 64
|
|
# The API refuses to start in production (NODE_ENV=production and DEMO != true)
|
|
# while any of these is empty or left at a well-known default value.
|
|
# Outside production (and outside DEMO=true) an empty value makes the API use a
|
|
# random secret generated for that process only: sign-ins, emailed links and
|
|
# sessions then stop working on every restart, and every OTHER process that must
|
|
# accept the same tokens (API replicas, `yarn seed`, which signs seeded invite and
|
|
# estimate links) cannot verify them. Set explicit values shared by all of them.
|
|
# ============================================================================
|
|
EXPRESS_SESSION_SECRET=
|
|
|
|
# JWT Configuration
|
|
JWT_SECRET=
|
|
JWT_TOKEN_EXPIRATION_TIME=86400
|
|
|
|
# JWT Refresh Token Configuration
|
|
JWT_REFRESH_TOKEN_SECRET=
|
|
JWT_REFRESH_TOKEN_EXPIRATION_TIME=86400
|
|
|
|
# Email Verification Config
|
|
JWT_VERIFICATION_TOKEN_SECRET=
|
|
JWT_VERIFICATION_TOKEN_EXPIRATION_TIME=86400
|
|
|
|
# Password Less Authentication Configuration
|
|
MAGIC_CODE_EXPIRATION_TIME=600
|
|
|
|
# Join Request Organization Team Configuration
|
|
TEAM_JOIN_REQUEST_EXPIRATION_TIME=86400
|
|
|
|
# Rate Limiting
|
|
THROTTLE_ENABLED=true
|
|
THROTTLE_TTL=60000 # 1 minute
|
|
THROTTLE_LIMIT=60000
|
|
|
|
# ============================================================================
|
|
# SECURITY: Seeded account credentials
|
|
# The FIRST boot against an empty database creates three accounts from these
|
|
# variables. Their shipped defaults (admin@ever.co / admin, local.admin@ever.co
|
|
# / admin, employee@ever.co / 12345678) are published in this repository, so a
|
|
# deployment that leaves the passwords unset is exposed to publicly known
|
|
# passwords. The API therefore REFUSES TO SEED in production (NODE_ENV=production
|
|
# or a production build, DEMO != true, non-Electron) while any of the three
|
|
# *_PASSWORD variables is unset or left at its default; the *_EMAIL variables
|
|
# are optional. The `yarn seed:ever` / `yarn seed:all` seeds are refused there
|
|
# outright, because they also create fixture accounts with a hard-coded password.
|
|
# The check runs only when a seed runs: an existing database (users present) is
|
|
# never seeded again, so it is never refused. Demo deployments (DEMO=true) and
|
|
# the desktop Gauzy Server are exempt.
|
|
# ============================================================================
|
|
DEMO_SUPER_ADMIN_EMAIL=
|
|
DEMO_SUPER_ADMIN_PASSWORD=
|
|
DEMO_ADMIN_EMAIL=
|
|
DEMO_ADMIN_PASSWORD=
|
|
DEMO_EMPLOYEE_EMAIL=
|
|
DEMO_EMPLOYEE_PASSWORD=
|
|
# Emergency escape hatch; seeds the well-known defaults anyway. Strongly discouraged.
|
|
# ALLOW_INSECURE_SEED_CREDENTIALS=false
|
|
|
|
# ============================================================================
|
|
# SECURITY: Proxy trust & brute-force controls
|
|
# TRUST_PROXY is the Express `trust proxy` setting and decides what `req.ip`
|
|
# resolves to - which is what the rate limiter counts against. Use the NUMBER OF
|
|
# PROXY HOPS in front of the API (1 for a single nginx/ingress, 2 for
|
|
# Cloudflare Tunnel -> ingress -> api), or a comma-separated list of trusted proxy
|
|
# CIDRs. `true` trusts the whole X-Forwarded-For chain and lets any client pick
|
|
# its own rate-limit bucket - do not use it. A hop count trusts whoever connects
|
|
# to the API socket to write one X-Forwarded-For entry, so if the API port is
|
|
# reachable WITHOUT going through your proxy (e.g. docker-compose publishing
|
|
# 3000), use your proxy CIDRs instead.
|
|
TRUST_PROXY=1
|
|
# Set to true when every request reaches the API through Cloudflare (including
|
|
# a Cloudflare Tunnel) and nothing else can reach the origin; the
|
|
# CF-Connecting-IP header is client-writable otherwise. Behind Cloudflare with
|
|
# this left false, every client shares the proxy's address and therefore ONE
|
|
# rate-limit bucket.
|
|
THROTTLE_TRUST_CF_CONNECTING_IP=false
|
|
# Per-ACCOUNT brute-force control, independent of the client IP. Once this many
|
|
# consecutive failed sign-ins for one account have come from at least TWO
|
|
# different client addresses, that account is blocked for AUTH_LOCKOUT_SECONDS
|
|
# (HTTP 429 with Retry-After). Failures from a single address never block the
|
|
# account - the per-address throttle covers that - so one client cannot lock a
|
|
# known email out. At most this many checks per account may run concurrently.
|
|
# Trade-off: an attacker with several addresses can still block a known account
|
|
# for AUTH_LOCKOUT_SECONDS at a time. Set AUTH_MAX_FAILED_ATTEMPTS=0 to switch
|
|
# the per-account control off and rely on the per-address throttle alone.
|
|
AUTH_MAX_FAILED_ATTEMPTS=10
|
|
AUTH_LOCKOUT_SECONDS=900
|
|
|
|
# CORS Allowed Origins (comma-separated list of trusted origins)
|
|
# If not set, all origins (*) are allowed. In production, set this to your trusted domains.
|
|
# ALLOWED_ORIGINS=https://app.gauzy.co,https://gauzy.co
|
|
|
|
# Twitter OAuth Configuration
|
|
TWITTER_CLIENT_ID=XXXXXXX
|
|
TWITTER_CLIENT_SECRET=XXXXXXX
|
|
TWITTER_CALLBACK_URL=http://localhost:3000/api/auth/twitter/callback
|
|
|
|
# Google OAuth Configuration
|
|
GOOGLE_CLIENT_ID=XXXXXXX
|
|
GOOGLE_CLIENT_SECRET=XXXXXXX
|
|
GOOGLE_CALLBACK_URL=http://localhost:3000/api/auth/google/callback
|
|
|
|
# Facebook OAuth Configuration
|
|
FACEBOOK_CLIENT_ID=XXXXXXX
|
|
FACEBOOK_CLIENT_SECRET=XXXXXXX
|
|
FACEBOOK_CALLBACK_URL=http://localhost:3000/api/auth/facebook/callback
|
|
FACEBOOK_GRAPH_VERSION=v3.0
|
|
|
|
# Github OAuth App Integration
|
|
GAUZY_GITHUB_OAUTH_CLIENT_ID=XXXXXXX
|
|
GAUZY_GITHUB_OAUTH_CLIENT_SECRET=XXXXXXX
|
|
GAUZY_GITHUB_OAUTH_CALLBACK_URL="http://localhost:3000/api/auth/github/callback"
|
|
|
|
# Social sign-in by provider access token (POST /api/auth/signin.email.social, /api/auth/signup.link.account).
|
|
# Tokens are only accepted when issued to an allowed OAuth client. Gauzy's own apps above (GOOGLE_CLIENT_ID,
|
|
# GAUZY_GITHUB_OAUTH_CLIENT_ID/SECRET, FACEBOOK_CLIENT_ID/SECRET) are always allowed; list OTHER first-party
|
|
# clients (e.g. Ever Teams) here. Google: comma-separated client ids. GitHub/Facebook: comma-separated
|
|
# clientId:clientSecret pairs (the secret is needed to introspect the token). A provider with no client rejects all tokens.
|
|
GAUZY_SOCIAL_AUTH_GOOGLE_CLIENT_IDS=
|
|
GAUZY_SOCIAL_AUTH_GITHUB_APPS=
|
|
GAUZY_SOCIAL_AUTH_FACEBOOK_APPS=
|
|
|
|
# LinkedIn OAuth Configuration
|
|
LINKEDIN_CLIENT_ID=XXXXXXX
|
|
LINKEDIN_CLIENT_SECRET=XXXXXXX
|
|
LINKEDIN_CALLBACK_URL=http://localhost:3000/api/auth/linkedin/callback
|
|
|
|
# Microsoft OAuth Configuration
|
|
MICROSOFT_GRAPH_API_URL=https://graph.microsoft.com/v1.0
|
|
MICROSOFT_AUTHORIZATION_URL=https://login.microsoftonline.com/common/oauth2/v2.0/authorize
|
|
MICROSOFT_TOKEN_URL=https://login.microsoftonline.com/common/oauth2/v2.0/token
|
|
MICROSOFT_CLIENT_ID=XXXXXXX
|
|
MICROSOFT_CLIENT_SECRET=XXXXXXX
|
|
MICROSOFT_CALLBACK_URL=http://localhost:3000/api/auth/microsoft/callback
|
|
|
|
# Github Apps Integration
|
|
GAUZY_GITHUB_CLIENT_ID=XXXXXXX
|
|
GAUZY_GITHUB_CLIENT_SECRET=XXXXXXX
|
|
|
|
# Github App Install Integration
|
|
GAUZY_GITHUB_APP_NAME=
|
|
GAUZY_GITHUB_APP_ID=XXXXXXX
|
|
GAUZY_GITHUB_APP_PRIVATE_KEY=
|
|
|
|
# Github Webhook Configuration
|
|
# GAUZY_GITHUB_WEBHOOK_SECRET is REQUIRED whenever the GitHub App integration is enabled: the
|
|
# receiver at POST /api/integration/github/webhook verifies GitHub's `x-hub-signature-256` HMAC and
|
|
# rejects every delivery (403) when the secret is unset or does not match. Set it to the exact
|
|
# "Webhook secret" configured on the GitHub App (Settings -> Developer settings -> GitHub Apps).
|
|
GAUZY_GITHUB_WEBHOOK_URL=http://localhost:3000/api/auth/github/webhook
|
|
GAUZY_GITHUB_WEBHOOK_SECRET=XXXXXXX
|
|
|
|
# Github Redirect URL
|
|
GAUZY_GITHUB_REDIRECT_URL=http://localhost:3000/api/integration/github/callback
|
|
GAUZY_GITHUB_POST_INSTALL_URL="http://localhost:4200/#/pages/integrations/github/setup/installation"
|
|
GAUZY_GITHUB_API_VERSION="2022-11-28"
|
|
|
|
# Zapier Apps Integration
|
|
GAUZY_ZAPIER_CLIENT_ID=XXXXXXXXX
|
|
GAUZY_ZAPIER_CLIENT_SECRET=XXXXXXX
|
|
GAUZY_ZAPIER_REDIRECT_URL=http://localhost:3000/api/integration/zapier/oauth/callback
|
|
GAUZY_ZAPIER_POST_INSTALL_URL="http://localhost:4200/#/pages/integrations/zapier"
|
|
# Comma-separated list of domains allowed for OAuth redirects (security feature)
|
|
GAUZY_ZAPIER_ALLOWED_DOMAINS=gauzy.co,*.gauzy.co,ever.co,*.ever.co,zapier.com,*.zapier.com,localhost
|
|
# Maximum number of OAuth authorization codes to store in memory
|
|
GAUZY_ZAPIER_MAX_AUTH_CODES=1000
|
|
# Number of server instances (affects auth code cleanup behavior)
|
|
GAUZY_ZAPIER_INSTANCE_COUNT=1
|
|
|
|
FIVERR_CLIENT_ID=XXXXXXX
|
|
FIVERR_CLIENT_SECRET=XXXXXXX
|
|
|
|
AUTH0_CLIENT_ID=XXXXXXX
|
|
AUTH0_CLIENT_SECRET=XXXXXXX
|
|
AUTH0_DOMAIN=XXXXXXX
|
|
|
|
# Keycloak OAuth
|
|
KEYCLOAK_CLIENT_ID=XXXXXXX
|
|
KEYCLOAK_CLIENT_SECRET=XXXXXXX
|
|
KEYCLOAK_REALM=
|
|
KEYCLOAK_COOKIE_KEY=XXXXXXX
|
|
KEYCLOAK_AUTH_SERVER_URL=https://keycloak.example.com/auth
|
|
KEYCLOAK_CALLBACK_URL=http://localhost:3000/api/auth/keycloak/callback
|
|
|
|
INTEGRATED_HUBSTAFF_USER_PASS=hubstaffPassword
|
|
|
|
# Upwork Integration Config
|
|
UPWORK_API_KEY=XXXXXXX
|
|
UPWORK_API_SECRET=XXXXXXX
|
|
UPWORK_REDIRECT_URL="http://localhost:3000/api/integrations/upwork/callback"
|
|
UPWORK_POST_INSTALL_URL="http://localhost:4200/#/pages/integrations/upwork"
|
|
|
|
# Hubstaff Integration Configuration
|
|
HUBSTAFF_CLIENT_ID=XXXXXXX
|
|
HUBSTAFF_CLIENT_SECRET=XXXXXXX
|
|
HUBSTAFF_REDIRECT_URL="http://localhost:3000/api/integration/hubstaff/callback"
|
|
HUBSTAFF_POST_INSTALL_URL="http://localhost:4200/#/pages/integrations/hubstaff"
|
|
|
|
# File System: LOCAL | S3 | WASABI | CLOUDINARY
|
|
FILE_PROVIDER=LOCAL
|
|
|
|
# AWS Config
|
|
AWS_ACCESS_KEY_ID=
|
|
AWS_SECRET_ACCESS_KEY=
|
|
AWS_REGION=us-east-1
|
|
AWS_S3_BUCKET=gauzy
|
|
|
|
# WASABI Config (optional)
|
|
WASABI_ACCESS_KEY_ID=
|
|
WASABI_SECRET_ACCESS_KEY=
|
|
WASABI_REGION=us-east-1
|
|
WASABI_SERVICE_URL=https://s3.wasabisys.com
|
|
WASABI_S3_BUCKET=gauzy
|
|
WASABI_S3_FORCE_PATH_STYLE=true
|
|
|
|
# DIGITALOCEAN Spaces Config (optional)
|
|
DIGITALOCEAN_ACCESS_KEY_ID=
|
|
DIGITALOCEAN_SECRET_ACCESS_KEY=
|
|
DIGITALOCEAN_REGION=us-east-1
|
|
DIGITALOCEAN_SERVICE_URL=
|
|
DIGITALOCEAN_CDN_URL=
|
|
DIGITALOCEAN_S3_BUCKET=gauzy
|
|
DIGITALOCEAN_S3_FORCE_PATH_STYLE=false
|
|
|
|
# Cloudinary Config (optional)
|
|
CLOUDINARY_CLOUD_NAME=
|
|
CLOUDINARY_API_KEY=
|
|
CLOUDINARY_API_SECRET=
|
|
CLOUDINARY_API_SECURE=true
|
|
CLOUDINARY_CDN_URL=https://res.cloudinary.com
|
|
|
|
# Gauzy AI Endpoints (optional, do not set unless you subscribed to Gauzy AI)
|
|
GAUZY_AI_GRAPHQL_ENDPOINT=http://localhost:3005/graphql
|
|
GAUZY_AI_REST_ENDPOINT=http://localhost:3005/api
|
|
|
|
# Gauzy AI Key/Secret pair authentication
|
|
GAUZY_AI_API_KEY=
|
|
GAUZY_AI_API_SECRET=
|
|
|
|
# Gauzy Cloud
|
|
GAUZY_CLOUD_ENDPOINT=https://api.gauzy.co
|
|
GAUZY_CLOUD_APP=https://app.gauzy.co
|
|
|
|
# SMTP Mail Config
|
|
MAIL_FROM_ADDRESS=gauzy@ever.co
|
|
MAIL_HOST=smtp.gmail.com
|
|
MAIL_PORT=465
|
|
MAIL_USERNAME=
|
|
MAIL_PASSWORD=
|
|
|
|
# Sentry Client Key
|
|
SENTRY_DSN=
|
|
SENTRY_HTTP_TRACING_ENABLED=
|
|
SENTRY_POSTGRES_TRACKING_ENABLED=
|
|
SENTRY_PROFILING_ENABLED=
|
|
SENTRY_TRACES_SAMPLE_RATE=
|
|
SENTRY_PROFILE_SAMPLE_RATE=
|
|
|
|
# PostHog Configuration
|
|
POSTHOG_KEY=
|
|
POSTHOG_HOST=
|
|
POSTHOG_ENABLED=
|
|
POSTHOG_FLUSH_INTERVAL=
|
|
|
|
# Default Currency
|
|
DEFAULT_CURRENCY=USD
|
|
|
|
# Default Country
|
|
DEFAULT_COUNTRY=US
|
|
|
|
# Google Maps API Key
|
|
GOOGLE_MAPS_API_KEY=
|
|
|
|
# Chatwoot SDK Token
|
|
CHATWOOT_SDK_TOKEN=
|
|
|
|
# Restrict Access to Google Place Autocomplete
|
|
GOOGLE_PLACE_AUTOCOMPLETE=false
|
|
|
|
# Nebular CHAT API key for a map message type (which is required by Google Maps)
|
|
CHAT_MESSAGE_GOOGLE_MAP=
|
|
|
|
# Default Latitude and Longitude
|
|
DEFAULT_LATITUDE=
|
|
DEFAULT_LONGITUDE=
|
|
|
|
# Keymetrics settings (optional)
|
|
WEB_CONCURRENCY=1
|
|
WEB_MEMORY=4096
|
|
|
|
# Unleash Configuration for Features management (optional)
|
|
|
|
UNLEASH_APP_NAME=Gauzy
|
|
UNLEASH_API_URL=
|
|
UNLEASH_INSTANCE_ID=
|
|
UNLEASH_REFRESH_INTERVAL=15000
|
|
UNLEASH_METRICS_INTERVAL=60000
|
|
UNLEASH_API_KEY=
|
|
|
|
# Defines feature flags and settings related to user authentication methods.
|
|
FEATURE_EMAIL_PASSWORD_LOGIN=true
|
|
FEATURE_MAGIC_LOGIN=true
|
|
FEATURE_GITHUB_LOGIN=true
|
|
FEATURE_FACEBOOK_LOGIN=true
|
|
FEATURE_GOOGLE_LOGIN=true
|
|
FEATURE_TWITTER_LOGIN=true
|
|
FEATURE_MICROSOFT_LOGIN=true
|
|
FEATURE_LINKEDIN_LOGIN=true
|
|
|
|
# Features Toggles
|
|
|
|
FEATURE_DASHBOARD=true
|
|
FEATURE_TIME_TRACKING=true
|
|
|
|
FEATURE_ESTIMATE=true
|
|
FEATURE_ESTIMATE_RECEIVED=true
|
|
FEATURE_INVOICE=true
|
|
FEATURE_INVOICE_RECURRING=true
|
|
FEATURE_INVOICE_RECEIVED=true
|
|
FEATURE_INCOME=true
|
|
FEATURE_EXPENSE=true
|
|
FEATURE_PAYMENT=true
|
|
|
|
FEATURE_PROPOSAL=true
|
|
FEATURE_PROPOSAL_TEMPLATE=true
|
|
|
|
FEATURE_PIPELINE=true
|
|
FEATURE_PIPELINE_DEAL=true
|
|
|
|
FEATURE_DASHBOARD_TASK=true
|
|
FEATURE_TEAM_TASK=true
|
|
FEATURE_MY_TASK=true
|
|
|
|
FEATURE_JOB=true
|
|
|
|
FEATURE_EMPLOYEES=true
|
|
FEATURE_EMPLOYEE_TIME_ACTIVITY=true
|
|
FEATURE_EMPLOYEE_TIMESHEETS=true
|
|
FEATURE_EMPLOYEE_APPOINTMENT=true
|
|
FEATURE_EMPLOYEE_APPROVAL=true
|
|
FEATURE_EMPLOYEE_APPROVAL_POLICY=true
|
|
FEATURE_EMPLOYEE_LEVEL=true
|
|
FEATURE_EMPLOYEE_POSITION=true
|
|
FEATURE_EMPLOYEE_TIMEOFF=true
|
|
FEATURE_EMPLOYEE_RECURRING_EXPENSE=true
|
|
FEATURE_EMPLOYEE_CANDIDATE=true
|
|
FEATURE_MANAGE_INTERVIEW=true
|
|
FEATURE_MANAGE_INVITE=true
|
|
|
|
FEATURE_ORGANIZATION=true
|
|
FEATURE_ORGANIZATION_EQUIPMENT=true
|
|
FEATURE_ORGANIZATION_INVENTORY=true
|
|
FEATURE_ORGANIZATION_TAG=true
|
|
FEATURE_ORGANIZATION_VENDOR=true
|
|
FEATURE_ORGANIZATION_PROJECT=true
|
|
FEATURE_ORGANIZATION_DEPARTMENT=true
|
|
FEATURE_ORGANIZATION_TEAM=true
|
|
FEATURE_ORGANIZATION_DOCUMENT=true
|
|
FEATURE_ORGANIZATION_EMPLOYMENT_TYPE=true
|
|
FEATURE_ORGANIZATION_RECURRING_EXPENSE=true
|
|
FEATURE_ORGANIZATION_HELP_CENTER=true
|
|
|
|
FEATURE_CONTACT=true
|
|
|
|
FEATURE_GOAL=true
|
|
FEATURE_GOAL_REPORT=true
|
|
FEATURE_GOAL_SETTING=true
|
|
|
|
FEATURE_REPORT=true
|
|
|
|
FEATURE_USER=true
|
|
FEATURE_ORGANIZATIONS=true
|
|
FEATURE_APP_INTEGRATION=true
|
|
|
|
FEATURE_SETTING=true
|
|
FEATURE_EMAIL_HISTORY=true
|
|
FEATURE_EMAIL_TEMPLATE=true
|
|
FEATURE_IMPORT_EXPORT=true
|
|
FEATURE_FILE_STORAGE=true
|
|
FEATURE_PAYMENT_GATEWAY=true
|
|
FEATURE_SMS_GATEWAY=true
|
|
FEATURE_SMTP=true
|
|
FEATURE_ROLES_PERMISSION=true
|
|
|
|
# Email Verification
|
|
FEATURE_EMAIL_VERIFICATION=false
|
|
|
|
# Set the environment variable to enable/disable the global stats endpoint
|
|
FEATURE_OPEN_STATS=false
|
|
|
|
# Mac Code Sign
|
|
# Required for signing the macOS app with your Developer ID certificate
|
|
#
|
|
# CSC_LINK: Base64 encoded .p12 certificate file
|
|
# Generate with: base64 -i /path/to/certificate.p12 | tr -d '\n'
|
|
# The certificate must include both the Developer ID Application certificate
|
|
# and its private key (exported from Keychain Access as .p12)
|
|
CSC_LINK=
|
|
|
|
# CSC_KEY_PASSWORD: Password for the .p12 file
|
|
# Set to empty string if no password was used when exporting the .p12
|
|
CSC_KEY_PASSWORD=
|
|
|
|
# Notarize MacOS
|
|
# Required for notarization to avoid Gatekeeper warnings on macOS 10.15+
|
|
#
|
|
# APPLE_API_KEY: Base64 encoded .p8 key file from App Store Connect
|
|
APPLE_API_KEY=
|
|
|
|
# APPLE_API_KEY_ID: The Key ID from App Store Connect (e.g., AB12CD34EF)
|
|
APPLE_API_KEY_ID=
|
|
|
|
# APPLE_API_ISSUER: The Issuer ID from App Store Connect
|
|
APPLE_API_ISSUER=
|
|
|
|
# GitHub App Integration
|
|
GITHUB_INTEGRATION_APP_ID=
|
|
GITHUB_INTEGRATION_CLIENT_ID=
|
|
GITHUB_INTEGRATION_CLIENT_SECRET=
|
|
GITHUB_INTEGRATION_PRIVATE_KEY=
|
|
# NOTE: nothing reads GITHUB_INTEGRATION_WEBHOOK_SECRET. The GitHub App webhook receiver reads
|
|
# GAUZY_GITHUB_WEBHOOK_SECRET (above) — setting this one instead leaves the receiver unconfigured,
|
|
# which now rejects every delivery.
|
|
GITHUB_INTEGRATION_WEBHOOK_SECRET=
|
|
|
|
# HubStaff Integration
|
|
HUBSTAFF_CLIENT_ID=
|
|
HUBSTAFF_CLIENT_SECRET=
|
|
HUBSTAFF_PERSONAL_ACCESS_TOKEN=
|
|
|
|
# Jitsu Browser Configuration
|
|
JITSU_BROWSER_URL=
|
|
JITSU_BROWSER_WRITE_KEY=
|
|
|
|
# Jitsu Server Configuration
|
|
JITSU_SERVER_URL=
|
|
JITSU_SERVER_WRITE_KEY=
|
|
JITSU_SERVER_DEBUG=
|
|
JITSU_SERVER_ECHO_EVENTS=
|
|
|
|
# Tracing Configuration
|
|
OTEL_ENABLED=false
|
|
OTEL_PROVIDER=zipkin
|
|
OTEL_SERVICE_NAME=
|
|
OTEL_EXPORTER_OTLP_PROTOCOL=
|
|
OTEL_EXPORTER_OTLP_HEADERS=
|
|
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=
|
|
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=
|
|
OTEL_EXPORTER_OTLP_ENDPOINT=
|
|
ASPECTO_API_KEY=
|
|
HONEYCOMB_API_KEY=
|
|
HONEYCOMB_ENABLE_LOCAL_VISUALIZATIONS=
|
|
|
|
# Platform Logo resource URL (SVG is Recommended)
|
|
PLATFORM_LOGO='assets/images/logos/logo_Gauzy.svg'
|
|
|
|
# Desktop App 512x512 icon
|
|
GAUZY_DESKTOP_LOGO_512X512='assets/icons/icon_512x512.png'
|
|
|
|
# Platform Privacy URL
|
|
PLATFORM_PRIVACY_URL='https://gauzy.co/privacy'
|
|
|
|
# Platform terms of Services URL
|
|
PLATFORM_TOS_URL='https://gauzy.co/tos'
|
|
|
|
# Platform no internet logo
|
|
NO_INTERNET_LOGO='assets/images/logos/logo_Gauzy.svg'
|
|
|
|
# Company Information
|
|
COMPANY_NAME='Ever Co. LTD'
|
|
COMPANY_LINK='https://ever.co'
|
|
COMPANY_SITE_NAME='Gauzy'
|
|
COMPANY_SITE_LINK='https://gauzy.co'
|
|
COMPANY_GITHUB_LINK='https://github.com/ever-co'
|
|
COMPANY_GITLAB_LINK='https://gitlab.com/ever-co'
|
|
COMPANY_FACEBOOK_LINK='https://www.facebook.com/gauzyplatform'
|
|
COMPANY_TWITTER_LINK='https://twitter.com/gauzyplatform'
|
|
COMPANY_IN_LINK='https://www.linkedin.com/company/everhq'
|
|
|
|
# Desktop download links
|
|
DESKTOP_APP_DOWNLOAD_LINK_APPLE='https://gauzy.co/downloads#desktop/apple'
|
|
DESKTOP_APP_DOWNLOAD_LINK_WINDOWS='https://gauzy.co/downloads#desktop/windows'
|
|
DESKTOP_APP_DOWNLOAD_LINK_LINUX='https://gauzy.co/downloads#desktop/linux'
|
|
MOBILE_APP_DOWNLOAD_LINK='https://gauzy.co/downloads#mobile'
|
|
EXTENSION_DOWNLOAD_LINK='https://gauzy.co/downloads#extensions'
|
|
|
|
# Desktop Timer Application Configuration
|
|
PROJECT_REPO='https://github.com/ever-co/ever-gauzy.git'
|
|
|
|
DESKTOP_TIMER_APP_NAME='gauzy-desktop-timer'
|
|
DESKTOP_TIMER_APP_DESCRIPTION='Gauzy Desktop Timer'
|
|
DESKTOP_TIMER_APP_ID='com.ever.gauzydesktoptimer'
|
|
DESKTOP_TIMER_APP_REPO_NAME='ever-gauzy-desktop-timer'
|
|
DESKTOP_TIMER_APP_REPO_OWNER='ever-co'
|
|
DESKTOP_TIMER_APP_WELCOME_TITLE=
|
|
DESKTOP_TIMER_APP_WELCOME_CONTENT=
|
|
DESKTOP_TIMER_APP_PROTOCOL='gauzy-timer'
|
|
|
|
# Desktop Application Configuration
|
|
DESKTOP_APP_NAME='gauzy-desktop'
|
|
DESKTOP_APP_DESCRIPTION='Gauzy Desktop'
|
|
DESKTOP_APP_ID='com.ever.gauzydesktop'
|
|
DESKTOP_APP_REPO_NAME='ever-gauzy-desktop'
|
|
DESKTOP_APP_REPO_OWNER='ever-co'
|
|
DESKTOP_APP_WELCOME_TITLE=
|
|
DESKTOP_APP_WELCOME_CONTENT=
|
|
DESKTOP_APP_PROTOCOL='gauzy-desktop'
|
|
|
|
# Desktop Server Application Configuration
|
|
DESKTOP_SERVER_APP_NAME='gauzy-server'
|
|
DESKTOP_SERVER_APP_DESCRIPTION='Gauzy Server'
|
|
DESKTOP_SERVER_APP_ID='com.ever.gauzyserver'
|
|
DESKTOP_SERVER_APP_REPO_NAME='ever-gauzy-server'
|
|
DESKTOP_SERVER_APP_REPO_OWNER='ever-co'
|
|
DESKTOP_SERVER_APP_WELCOME_TITLE=
|
|
DESKTOP_SERVER_APP_WELCOME_CONTENT=
|
|
DESKTOP_SERVER_APP_PROTOCOL='gauzy-server'
|
|
|
|
# Desktop API Server Application Configuration
|
|
DESKTOP_API_SERVER_APP_NAME='gauzy-api-server'
|
|
DESKTOP_API_SERVER_APP_DESCRIPTION='Gauzy API Server'
|
|
DESKTOP_API_SERVER_APP_ID='com.ever.gauzyapiserver'
|
|
DESKTOP_API_SERVER_APP_REPO_NAME='ever-gauzy-api-server'
|
|
DESKTOP_API_SERVER_APP_REPO_OWNER='ever-co'
|
|
DESKTOP_API_SERVER_APP_WELCOME_TITLE=
|
|
DESKTOP_API_SERVER_APP_WELCOME_CONTENT=
|
|
DESKTOP_API_SERVER_APP_PROTOCOL='gauzy-api-server'
|
|
|
|
#AGENT
|
|
AGENT_APP_PROTOCOL='gauzy-agent'
|
|
|
|
REGISTER_URL='https://app.gauzy.co/#/auth/register'
|
|
FORGOT_PASSWORD_URL='https://app.gauzy.co/#/auth/request-password'
|
|
|
|
# I18N Translation Files URL
|
|
I18N_FILES_URL=
|
|
|
|
# MCP Server Configuration
|
|
API_TIMEOUT=30000
|
|
GAUZY_AUTH_EMAIL=your-email@example.com
|
|
GAUZY_AUTH_PASSWORD=your-secure-password
|
|
GAUZY_AUTO_LOGIN=false
|
|
GAUZY_MCP_DEBUG=false
|
|
MCP_APP_ID=co.gauzy.mcp-server
|
|
MCP_APP_NAME="Gauzy MCP Server"
|
|
NODE_ENV=development
|
|
|
|
# MCP Transport Configuration
|
|
# Options: stdio | http | websocket
|
|
MCP_SERVER_MODE="stdio" # Internal server runtime mode
|
|
MCP_TRANSPORT="stdio" # Exposed transport (used by TransportFactory)
|
|
|
|
# HTTP Transport Settings
|
|
MCP_AUTH_BASE_URL=http://localhost:3003
|
|
MCP_AUTH_PORT=3003
|
|
MCP_CORS_CREDENTIALS=true
|
|
MCP_CORS_ORIGIN=http://localhost:3000,http://localhost:4200,http://127.0.0.1:3000,http://127.0.0.1:4200
|
|
MCP_HTTP_HOST=localhost
|
|
MCP_HTTP_PORT=3001
|
|
|
|
# Session Management
|
|
MCP_AUTH_SESSION_SECRET=your-secure-session-secret
|
|
MCP_SESSION_COOKIE_NAME=mcp-session-id
|
|
MCP_SESSION_ENABLED=true
|
|
MCP_SESSION_TTL=1800000
|
|
MCP_TRUSTED_PROXIES=
|
|
|
|
# WebSocket Transport Settings
|
|
MCP_WS_ALLOWED_ORIGINS="*" # DEV ONLY; set specific origins in production
|
|
MCP_WS_CERT_PATH=path/to/your/certs/cert.pem
|
|
MCP_WS_COMPRESSION=true
|
|
MCP_WS_HOST=localhost
|
|
MCP_WS_KEY_PATH=path/to/your/certs/key.pem
|
|
# Default 1MB is safer; increase only if you must handle large messages
|
|
MCP_WS_MAX_PAYLOAD=1048576
|
|
MCP_WS_PATH="/sse" # Realtime endpoint path (WS server behind /sse by default)
|
|
MCP_WS_PER_MESSAGE_DEFLATE=true
|
|
MCP_WS_PORT=3002
|
|
MCP_WS_SESSION_COOKIE_NAME=mcp-ws-session-id
|
|
MCP_WS_SESSION_ENABLED=true
|
|
MCP_WS_TLS=false
|
|
MCP_WS_TRUSTED_PROXIES=
|
|
|
|
# OAuth 2.0 Authorization Configuration (Development)
|
|
MCP_AUTH_ENABLED=false
|
|
MCP_AUTH_RESOURCE_URI=http://localhost:3001/sse
|
|
MCP_AUTH_REQUIRED_SCOPES=mcp.read,mcp.write
|
|
|
|
# JWT validation for development using RS256 with JWKS
|
|
MCP_AUTH_JWT_ALGORITHMS=RS256
|
|
MCP_AUTH_JWT_AUDIENCE=http://localhost:3001/sse
|
|
MCP_AUTH_JWT_ISSUER=http://localhost:3003
|
|
MCP_AUTH_JWT_JWKS_URI=http://localhost:3003/.well-known/jwks.json
|
|
# If you switch to HS*, use MCP_AUTH_JWT_SECRET and set ALGORITHMS=HS256
|
|
|
|
# Cache settings
|
|
# 5 minutes
|
|
MCP_AUTH_TOKEN_CACHE_TTL=300
|
|
# 1 hour
|
|
MCP_AUTH_METADATA_CACHE_TTL=3600
|
|
|
|
# Authorization server configuration (mock for development)
|
|
MCP_AUTH_SERVERS='[{"issuer":"http://localhost:3003","authorizationEndpoint":"http://localhost:3003/oauth2/authorize","tokenEndpoint":"http://localhost:3003/oauth2/token","grantTypesSupported":["authorization_code","client_credentials","refresh_token"],"responseTypesSupported":["code"],"scopesSupported":["mcp.read","mcp.write","mcp.admin"],"codeChallengeMethodsSupported":["S256"]}]'
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# AI CHAT (embedded AI agent) — @gauzy/plugin-ai-chat
|
|
# -----------------------------------------------------------------------------
|
|
# Master switch (chat is also hidden automatically when no provider is configured)
|
|
GAUZY_AI_CHAT_ENABLED=true
|
|
|
|
# SSRF egress guard for BYOK provider endpoints.
|
|
# A tenant admin can store a custom provider base URL and the server then fetches it (model
|
|
# catalogue, dictation, chat completions, document embeddings). By default any loopback, private
|
|
# (RFC 1918), link-local or cloud-metadata target is REFUSED, both when the URL is saved and when it
|
|
# is used, and redirects are not followed.
|
|
# Set this to `true` ONLY on single-tenant / self-hosted installs (and desktop local-server builds)
|
|
# whose users enter a LocalAI, Speaches, vLLM, Ollama or whisper.cpp address on localhost or a LAN in
|
|
# the AI settings page. Leave it unset on shared/multi-tenant hosting: there it would let any tenant
|
|
# reach the operator's internal network. It governs everything a TENANT credential leads to: the base
|
|
# URL a tenant entered AND the built-in local default a key-less row for Speaches/LocalAI/whisper.cpp
|
|
# falls back to (GHSA-w3mx-m5cr-3gxp). Only an operator's own `*_BASE_URL` value is trusted without it,
|
|
# so a zero-config local provider needs either this flag or that variable.
|
|
# GAUZY_AI_CHAT_ALLOW_PRIVATE_BASE_URLS=false
|
|
|
|
# Default provider/model when the tenant has not chosen one in Settings (BYOK).
|
|
# Providers are contributed by @gauzy/plugin-ai-provider-* plugins:
|
|
# anthropic | openai | openrouter | vercel-gateway | gauzy-ai | gemini | grok | groq | mistral |
|
|
# localai | openai-compatible (chat) — plus the voice-only ones listed under AI VOICE below.
|
|
GAUZY_AI_CHAT_DEFAULT_PROVIDER=anthropic
|
|
GAUZY_AI_CHAT_DEFAULT_MODEL=claude-sonnet-5
|
|
|
|
# BYOK credentials entered in Settings are encrypted at rest with this base64
|
|
# 32-byte key (shared with the core EncryptionService). Generate one with:
|
|
# node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
|
|
# ENCRYPTION_KEY=
|
|
|
|
# Server-wide provider API keys (tenant BYOK credentials from Settings take precedence)
|
|
ANTHROPIC_API_KEY=
|
|
OPENAI_API_KEY=
|
|
OPENROUTER_API_KEY=
|
|
AI_GATEWAY_API_KEY=
|
|
|
|
# Optional custom base URLs (proxies / self-hosted compatible endpoints)
|
|
# ANTHROPIC_BASE_URL=
|
|
# OPENAI_BASE_URL=
|
|
# OPENROUTER_BASE_URL=
|
|
# AI_GATEWAY_BASE_URL=
|
|
|
|
# Free tier: set this to make the AI agent work with NO per-tenant setup.
|
|
#
|
|
# It is resolved LAST — after a tenant's own key from Settings, and after OPENROUTER_API_KEY above —
|
|
# and it is the only source restricted to OpenRouter's free (":free") models. That separation is the
|
|
# point: if you set OPENROUTER_API_KEY you are bringing your own account and keep full access, while
|
|
# this variable is for a shared key that should never be spent on paid models.
|
|
#
|
|
# Rate limits on free models are low and shared across everyone using the key. When a user is rate
|
|
# limited the chat tells them to connect their own OpenRouter account or configure another provider.
|
|
# OPENROUTER_PLATFORM_API_KEY=
|
|
#
|
|
# Optional: pin the free model list instead of fetching it from OpenRouter. Free slugs are retired
|
|
# without notice, so this lets you correct drift with a restart rather than a release. Comma
|
|
# separated; leave unset to use the live catalogue (cached, with a pinned fallback).
|
|
# OPENROUTER_FREE_MODELS=deepseek/deepseek-r1:free,meta-llama/llama-3.3-70b-instruct:free
|
|
|
|
# Optional: attach the Gauzy MCP server's tools to the chat agent.
|
|
# Only point this at an MCP server that honors the per-request bearer token
|
|
# (see packages/plugins/ai-chat README — security note).
|
|
# Google Gemini + xAI Grok provider keys (BYOK per tenant also supported in Settings -> AI)
|
|
# GEMINI_API_KEY=
|
|
# XAI_API_KEY=
|
|
|
|
# Groq + Mistral: OpenAI-compatible chat AND speech-to-text (Whisper / Voxtral) — usable as the
|
|
# tenant's voice (dictation) provider as well as for chat.
|
|
# GROQ_API_KEY=
|
|
# GROQ_BASE_URL=
|
|
# MISTRAL_API_KEY=
|
|
# MISTRAL_BASE_URL=
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# AI VOICE (dictation / speech-to-text) — voice providers for the AI chat
|
|
# -----------------------------------------------------------------------------
|
|
# Dictation uses the tenant's chosen voice provider (Settings -> AI Providers -> "Use as default
|
|
# voice provider"), and otherwise the first speech-capable provider that has credentials, in this
|
|
# order: openai (50), groq (80), mistral (90), speaches (100), localai (101), whisper-cpp (102),
|
|
# openai-compatible (103), deepgram (110), elevenlabs (120). Any provider below that is configured
|
|
# — even one that cannot chat — makes dictation work.
|
|
#
|
|
# Cloud speech-to-text only providers (no chat models):
|
|
# DEEPGRAM_API_KEY=
|
|
# DEEPGRAM_BASE_URL=
|
|
# ELEVENLABS_API_KEY=
|
|
# ELEVENLABS_BASE_URL=
|
|
#
|
|
# LOCAL / self-hosted speech (no API key needed — setting the base URL is the whole credential;
|
|
# a tenant can also enter the URL in Settings -> AI Providers instead):
|
|
# Speaches (faster-whisper-server): docker run -p 8000:8000 ghcr.io/speaches-ai/speaches:latest-cpu
|
|
# SPEACHES_BASE_URL=http://localhost:8000/v1
|
|
# SPEACHES_API_KEY=
|
|
# LocalAI (chat + whisper): docker run -p 8080:8080 localai/localai:latest
|
|
# LOCALAI_BASE_URL=http://localhost:8080/v1
|
|
# LOCALAI_API_KEY=
|
|
# whisper.cpp whisper-server: ./build/bin/whisper-server -m models/ggml-base.en.bin --convert
|
|
# WHISPER_CPP_BASE_URL=http://localhost:8080
|
|
# WHISPER_CPP_API_KEY=
|
|
# Any OpenAI-compatible endpoint (vLLM, LM Studio, Ollama /v1, LiteLLM …) for chat and/or STT:
|
|
# OPENAI_COMPATIBLE_BASE_URL=http://localhost:11434/v1
|
|
# OPENAI_COMPATIBLE_API_KEY=
|
|
|
|
# GAUZY_AI_CHAT_MCP_URL=
|
|
|
|
# Optional: base URL the chat agent's tools use to call this API itself
|
|
# (defaults to API_BASE_URL). Useful in k8s to short-circuit via localhost.
|
|
# GAUZY_AI_CHAT_SELF_API_URL=
|
|
|
|
# --------------------------------------------------------------------------------------------------
|
|
# Documents (@gauzy/plugin-docs)
|
|
# --------------------------------------------------------------------------------------------------
|
|
# The Documents hub works out of the box with no configuration: files upload, extract text, and become
|
|
# searchable. Everything below is optional tuning. AI features are OFF by default — turn them on only
|
|
# after an AI provider is configured (see the AI chat section above; Documents reuses those providers
|
|
# and honors per-tenant BYOK keys).
|
|
|
|
# Master switch for AI classification, summaries and embeddings in Documents. ON by default.
|
|
# It is safe to leave on with no AI provider configured: the AI stages are additionally gated on a
|
|
# provider having credentials, so with none registered the pipeline skips them and uploads still
|
|
# extract text and stay fully searchable (lexical search only). Set to false to keep the AI stages
|
|
# off even where a provider IS configured.
|
|
# GAUZY_DOCS_AI_ENABLED=true
|
|
|
|
# Model overrides. Classification falls back to the AI chat default model when unset.
|
|
# GAUZY_DOCS_CLASSIFY_MODEL=
|
|
# GAUZY_DOCS_EMBEDDING_MODEL=text-embedding-3-small
|
|
# GAUZY_DOCS_EMBEDDING_DIMS=1536
|
|
# GAUZY_DOCS_EMBED_BATCH_SIZE=64
|
|
# GAUZY_DOCS_CLASSIFY_SAMPLE_CHARS=12000
|
|
|
|
# Text recognition (OCR) for scanned PDFs and image uploads, using the same AI provider (and the same
|
|
# per-tenant BYOK keys) as classification — no separate OCR service is involved. Off by default because
|
|
# it costs one model call per page. ON by default, but doubly gated — it needs GAUZY_DOCS_AI_ENABLED
|
|
# *and* a provider with credentials, so a deployment with no AI configured never transcribes anything.
|
|
# While unavailable, a PDF with no text layer and an image upload both fail with a clear "OCR is not
|
|
# available" message and land in the review queue.
|
|
# OCR'd documents are always flagged for review: a transcription can drop or garble text silently.
|
|
# GAUZY_DOCS_OCR_ENABLED=true
|
|
# GAUZY_DOCS_OCR_MAX_PAGES=20
|
|
|
|
# Vector store used for semantic retrieval. 'pgvector' requires PostgreSQL with the vector extension;
|
|
# on MySQL/SQLite (or when the extension is missing) retrieval degrades to lexical search automatically.
|
|
# GAUZY_DOCS_VECTOR_STORE=pgvector
|
|
# GAUZY_DOCS_RETRIEVAL_TOPK_MAX=12
|
|
|
|
# Chunking (how extracted text is split before embedding).
|
|
# GAUZY_DOCS_CHUNK_TOKENS=512
|
|
# GAUZY_DOCS_CHUNK_OVERLAP_TOKENS=64
|
|
|
|
# Re-index every document automatically when the embedding model or dimensions change. Off by default
|
|
# because a fleet-wide re-embed costs money — trigger it deliberately from Documents settings instead.
|
|
# GAUZY_DOCS_AUTO_REINDEX_ON_MODEL_CHANGE=false
|
|
|
|
# Upload limits and processing.
|
|
# GAUZY_DOCS_MAX_FILE_SIZE=52428800
|
|
# GAUZY_DOCS_MAX_EXTRACTED_CHARS=2000000
|
|
# GAUZY_DOCS_MAX_BINARY_BYTES=10485760
|
|
# GAUZY_DOCS_QUEUE_CONCURRENCY=2
|
|
# GAUZY_DOCS_STUCK_THRESHOLD_MINUTES=1440
|
|
|
|
# Per-organization storage quota in bytes. 0 or unset means unlimited (an organization-level setting
|
|
# in Documents settings overrides this).
|
|
# GAUZY_DOCS_ORG_QUOTA_BYTES=0
|
|
|
|
# Minutes between automatic version snapshots while a page is being edited.
|
|
# GAUZY_DOCS_VERSION_DEBOUNCE_MINUTES=10
|
|
|
|
# Structured logging of knowledge searches (query length, result counts, latency — never query text).
|
|
# GAUZY_DOCS_RETRIEVAL_LOG_ENABLED=true
|
|
|
|
# Inbound email capture: documents emailed to a per-organization address land in Documents for review.
|
|
# ON by default, but the route accepts NOTHING until a delivery can prove itself. Captured documents
|
|
# are never added to AI knowledge automatically — they always go through review first.
|
|
#
|
|
# Two kinds of capture address:
|
|
# PLATFORM docs-<128-bit hex>@$GAUZY_DOCS_INBOUND_DOMAIN — minted automatically for every
|
|
# organization on first read. Requires GAUZY_DOCS_INBOUND_DOMAIN; without it there
|
|
# is no address to mint and none is invented.
|
|
# CUSTOM_DOMAIN <mailbox>@<the tenant's own domain>, registered through the Documents settings UI.
|
|
# Inert until the tenant publishes _gauzy-docs.<domain> IN TXT with the value the UI
|
|
# shows — a chosen mailbox name is guessable, so ownership must be proven.
|
|
#
|
|
# A delivery is authenticated by EITHER of two proofs:
|
|
# 1. The deployment-wide HMAC below. Signature is hex(HMAC_SHA256(secret, "<timestamp>.<rawBody>"))
|
|
# in x-gauzy-docs-signature with x-gauzy-docs-timestamp. Fails closed when unset, and enforces a
|
|
# timestamp tolerance, a constant-time compare and single-use replay consumption.
|
|
# 2. A per-address relay secret in x-gauzy-docs-address-secret, issued once when a custom-domain
|
|
# address is created or rotated. Prefer this for tenant-owned domains: the deployment-wide
|
|
# secret can post as ANY tenant, a per-address secret only as one.
|
|
# GAUZY_DOCS_INBOUND_EMAIL_ENABLED=true
|
|
# GAUZY_DOCS_INBOUND_DOMAIN=
|
|
# GAUZY_DOCS_INBOUND_WEBHOOK_SECRET=
|
|
# GAUZY_DOCS_INBOUND_MAX_MESSAGE_BYTES=26214400
|
|
|
|
# BullMQ-backed dispatch for the Documents pipeline. Defaults to ON exactly where a BullMQ root is
|
|
# registered — i.e. REDIS_ENABLED=true and SCHEDULER_QUEUE_ENABLED not set to false. Every process
|
|
# that loads plugins (API, `yarn seed`, worker) registers that root under the same condition, so
|
|
# the default cannot register a @Processor without a connection. Set it explicitly to force either
|
|
# direction; with it off the pipeline runs in-process, which stays a fully supported mode.
|
|
# GAUZY_DOCS_QUEUE_ENABLED=false
|
|
|
|
# Whether THIS process also runs the docs-processing consumer, or only enqueues. Defaults to true
|
|
# wherever the queue is on. Set false on the API when a dedicated worker deployment exists, so the
|
|
# extraction / OCR / embedding work happens only there.
|
|
# GAUZY_DOCS_QUEUE_WORKER_ENABLED=false
|
|
|
|
# Fleet-wide kill switch for the BullMQ root itself (API + worker + seeder). `false` removes the
|
|
# root everywhere and puts every queue-backed pipeline back on its in-process fallback.
|
|
# SCHEDULER_QUEUE_ENABLED=false
|
|
|
|
# BILLING (Stripe) — hosted deployments only.
|
|
#
|
|
# Leave everything here unset for self-hosting: registration behaves exactly as it always has, no
|
|
# Stripe call is ever made, no subscription is required, and the billing UI is absent. The presence
|
|
# of STRIPE_SECRET_KEY is the only switch that turns any of it on.
|
|
#
|
|
# Server-side only — never expose these to the browser.
|
|
#
|
|
# Per-environment expectation:
|
|
# demo no key at all. DEMO=true also disables billing outright even if a key is present,
|
|
# so a demo deployment cannot reach Stripe by accident.
|
|
# staging a sk_test_ key. Test mode; no real card is ever charged.
|
|
# prod a sk_live_ key AND STRIPE_LIVE_MODE=true. A live key without that second opt-in is
|
|
# refused, so copying the production secret bundle onto staging cannot start taking
|
|
# real payments.
|
|
STRIPE_SECRET_KEY=
|
|
# Required alongside a sk_live_ key. Leave unset everywhere except production.
|
|
STRIPE_LIVE_MODE=
|
|
# Signing secret for the Stripe webhook endpoint (POST /api/billing/webhook).
|
|
STRIPE_WEBHOOK_SECRET=
|
|
# Where an unsubscribed visitor is sent to pick a plan. Shared by every Ever product.
|
|
EVER_CHECKOUT_URL=https://ever.co/checkout
|