packages/api/.dev.vars.example and the executable callbacks/webhook routes in packages/api/src/worker.ts.
Account authentication
Factorize owns authentication in PostgreSQL. Create a native account at/auth/signup with an email address and a password of 12–200 characters. Emails are case-insensitive. Verify the email before signing in at /auth/login; email plus password are accepted. Linear is connected after sign-in from Settings → Integrations, only for Linear-backed jobs.
Postmark sends one-hour, single-use verification and password-reset links. /auth/verify/request resends verification; /auth/password-reset handles recovery. Links never appear in application responses or logs. Passwords use salted PBKDF2-SHA-256. Change password in the account menu requires the current password. Password reset, password change and sign-out invalidate existing sessions via membership session versions, including delegated API credentials.
Before deploying, configure POSTMARK_SERVER_TOKEN as a production GitHub environment secret. The deployment copies it to a Worker secret; the verified sender and transactional message stream are non-secret Worker vars in packages/api/wrangler.jsonc. For a manual Postmark preflight, set the following environment variables along with APP_ORIGIN:
APP_ORIGIN must be the canonical HTTPS origin, without a trailing slash. Run npm run auth:validate-email --workspace=factorize manually when needed: it checks the server token/stream with Postmark and sends one preflight email from/to the configured sender, rejecting unverified senders or sending failures. It prints no credentials. Missing configuration fails the manual check. The deploy workflow does not run this check or send a preflight email. Runtime signup and recovery also fail closed if configuration or delivery fails; users can resend verification after a delivery failure. Local mail testing needs an HTTPS origin and a separate Postmark test server/sender; automated tests mock Postmark and never send mail.
Existing account migration
Apply0008_native_accounts.sql and 0009_remove_auth_usernames.sql before deploying this code. They reject ambiguous legacy emails that differ only in case (reconcile those identities before retrying migration), preserve tenant IDs, memberships, jobs and encrypted Linear connections, and backfill explicit Linear organization-to-workspace mappings. Existing Linear-only users choose Forgot password or previously signed in with Linear?, receive a reset email at their existing address, and set a password. Email possession is required; signup cannot replace an existing account’s credentials. Existing native users keep their passwords and may sign in by email; unverified accounts must verify first. A reset never promotes a member to owner. An account with multiple existing owner memberships currently opens the oldest workspace; no new workspace-switching UI is introduced.
Linear OAuth requires an authenticated owner, binds state to that user/workspace, stores the connection there, and never creates a Factorize session. A Linear organization already bound to another workspace cannot be claimed. Existing webhook routes resolve the explicit mapping. Do not delete that binding to work around an account-recovery problem.
Authentication verification
npm run check runs unit/contract tests and Postmark preflight tests. CI also runs the real PostgreSQL authentication suite against a disposable service. To run it locally, set AUTH_TEST_DATABASE_URL to an isolated PostgreSQL server whose user can create/drop test databases, then run npm test --workspace=factorize -- --run test/native-auth.postgres.test.ts. The suite creates and removes its own database and applies every migration, including a legacy connection fixture before the auth migration.
Configure Linear
Use authorization-code OAuth and enable Webhooks on the OAuth application. Configure:
After changing the webhook settings, re-authorize existing workspaces so Linear creates a fresh workspace subscription.
Configure ClickUp
Create a ClickUp OAuth application with the callback URLhttps://your-domain.example/auth/clickup/callback, then store its client ID and secret in CLICKUP_CLIENT_ID and CLICKUP_CLIENT_SECRET. Connecting ClickUp from Settings registers the signed task webhook automatically. ClickUp triggers can filter a list by status, tag, assignee, or creator; all configured rules must match.
Configuration reference
Never commit
.dev.vars or production secret values. The included .dev.vars.example is a safe template.