diff --git a/.env.example b/.env.example index 32bd6bd94..1662417c4 100644 --- a/.env.example +++ b/.env.example @@ -11,16 +11,15 @@ DATABASE_URL=postgres://multica:multica@localhost:5432/multica?sslmode=disable # DATABASE_MIN_CONNS=5 # Server -# APP_ENV gates dev-only auth shortcuts (primarily the 888888 master code). -# - Docker self-host: docker-compose.selfhost.yml already pins APP_ENV to -# "production" by default, so 888888 is DISABLED — a public instance can't -# be logged into with any email + 888888. -# - Local dev (make dev): leave APP_ENV unset so 888888 works out of the box. -# - Docker self-host on a private network you fully control, or evaluation -# without Resend: set APP_ENV=development to re-enable 888888. Do NOT -# enable on a publicly reachable instance. +# APP_ENV gates production safety checks. Docker self-host pins APP_ENV to +# "production" by default. Local dev can leave it unset. # See SELF_HOSTING.md for the full login setup. APP_ENV= +# Optional local/testing shortcut. Empty by default, so there is no fixed +# verification code. Without RESEND_API_KEY, generated codes print to stdout. +# If you need deterministic local automation, set a 6-digit value such as +# 888888 and keep APP_ENV non-production. This is ignored when APP_ENV=production. +MULTICA_DEV_VERIFICATION_CODE= PORT=8080 # Prometheus metrics are disabled by default. When enabled, bind to loopback # unless you protect the listener with private networking, allowlists, or @@ -50,8 +49,7 @@ MULTICA_BACKEND_IMAGE=ghcr.io/multica-ai/multica-backend MULTICA_WEB_IMAGE=ghcr.io/multica-ai/multica-web # Email (Resend) -# For local/dev use, leave RESEND_API_KEY empty — codes print to stdout, and -# master code 888888 works (only when APP_ENV != "production"; see above). +# For local/dev use, leave RESEND_API_KEY empty — generated codes print to stdout. # For production, set your Resend API key and change RESEND_FROM_EMAIL to a domain verified in your Resend account. RESEND_API_KEY= RESEND_FROM_EMAIL=noreply@multica.ai diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b15178db9..753dd47f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -373,7 +373,8 @@ done #### 2. Create a test user and token (automated auth) -In non-production environments the verification code is fixed at `888888`: +For deterministic local automation, set `MULTICA_DEV_VERIFICATION_CODE=888888` +in your env file before starting the backend: ```bash curl -s -X POST "$SERVER/auth/send-code" \ @@ -476,7 +477,9 @@ This automatically: 3. Starts and manages its own daemon instance 4. Connects to the local backend -Login in the Desktop UI with `dev@localhost` and code `888888`. +Login in the Desktop UI with `dev@localhost` and the generated code from the +backend logs. If you set `MULTICA_DEV_VERIFICATION_CODE=888888` before starting +the backend, you can use `888888` instead. If the backend runs on a non-default port (worktree), create `apps/desktop/.env.development.local`: diff --git a/Makefile b/Makefile index 6d64b17c5..ea2c58f03 100644 --- a/Makefile +++ b/Makefile @@ -91,7 +91,7 @@ selfhost: ## Create .env if needed, then pull and start the official self-hosted echo " $${MULTICA_WEB_IMAGE:-ghcr.io/multica-ai/multica-web}:$${MULTICA_IMAGE_TAG:-latest}"; \ echo ""; \ echo "Log in: configure RESEND_API_KEY in .env for email codes,"; \ - echo " or set APP_ENV=development in .env (private networks only) to enable code 888888."; \ + echo " or read the generated code from backend logs when Resend is unset."; \ echo ""; \ echo "Next — install the CLI and connect your machine:"; \ echo " brew install multica-ai/tap/multica"; \ @@ -130,7 +130,7 @@ selfhost-build: ## Build backend/web from the current checkout and start the sel echo " Backend: http://localhost:$${PORT:-8080}"; \ echo ""; \ echo "Log in: configure RESEND_API_KEY in .env for email codes,"; \ - echo " or set APP_ENV=development in .env (private networks only) to enable code 888888."; \ + echo " or read the generated code from backend logs when Resend is unset."; \ echo ""; \ echo "Built images locally via docker-compose.selfhost.build.yml."; \ echo "Local tags: multica-backend:dev and multica-web:dev."; \ diff --git a/SELF_HOSTING.md b/SELF_HOSTING.md index 653bcd0a2..40dc5cff1 100644 --- a/SELF_HOSTING.md +++ b/SELF_HOSTING.md @@ -26,7 +26,7 @@ multica setup self-host This installs the `multica` CLI, checks out the latest self-host assets, pulls the official Multica images from GHCR, and configures everything for localhost. -Open http://localhost:3000. To log in, configure `RESEND_API_KEY` in `.env` for email-based codes (recommended), or set `APP_ENV=development` in `.env` to enable the dev master code **`888888`**. See [Step 2 — Log In](#step-2--log-in) for details. +Open http://localhost:3000. To log in, configure `RESEND_API_KEY` in `.env` for email-based codes (recommended), or leave Resend unset and copy the generated code from the backend logs. See [Step 2 — Log In](#step-2--log-in) for details. > **Prerequisites:** Docker and Docker Compose must be installed. The script checks for this and provides install links if missing. > @@ -67,15 +67,15 @@ Once ready: ### Step 2 — Log In -Open http://localhost:3000 in your browser. The Docker self-host stack defaults to `APP_ENV=production` (set in `docker-compose.selfhost.yml`), so the dev master code is **disabled by default** for safety on public deployments. Pick one of the following to log in: +Open http://localhost:3000 in your browser. The Docker self-host stack defaults to `APP_ENV=production` (set in `docker-compose.selfhost.yml`), and there is no fixed verification code by default. Pick one of the following to log in: - **Recommended (production):** configure `RESEND_API_KEY` in `.env`, then restart the backend. Real verification codes will be sent to the email address you enter. See [Advanced Configuration → Email](SELF_HOSTING_ADVANCED.md#email-required-for-authentication). -- **Evaluation / private network:** set `APP_ENV=development` in `.env` and restart the backend. Verification code **`888888`** will then work for any email address. -- **Without configuring either:** the verification code is generated server-side and printed to the backend container logs (look for `[DEV] Verification code for ...:`). Useful for one-off testing on a single machine. +- **Without email configured:** the verification code is generated server-side and printed to the backend container logs (look for `[DEV] Verification code for ...:`). Useful for one-off testing on a single machine. +- **Deterministic local/private testing:** set `APP_ENV=development` and `MULTICA_DEV_VERIFICATION_CODE=888888` in `.env`, then restart the backend. This fixed code is ignored when `APP_ENV=production`. Changes to `ALLOW_SIGNUP` and `GOOGLE_CLIENT_ID` also take effect after restarting the backend / compose stack. The web UI reads both from `/api/config` at runtime, so no web rebuild is needed. -> **Warning:** do **not** set `APP_ENV=development` on a publicly reachable instance — anyone who knows an email address can then log in with `888888`. +> **Warning:** do **not** set `MULTICA_DEV_VERIFICATION_CODE` on a publicly reachable instance — anyone who knows an email address can then log in with that fixed code. ### Step 3 — Install CLI & Start Daemon diff --git a/SELF_HOSTING_ADVANCED.md b/SELF_HOSTING_ADVANCED.md index 59625be5a..eb3ceae0a 100644 --- a/SELF_HOSTING_ADVANCED.md +++ b/SELF_HOSTING_ADVANCED.md @@ -32,7 +32,7 @@ Multica uses email-based magic link authentication via [Resend](https://resend.c | `RESEND_API_KEY` | Your Resend API key | | `RESEND_FROM_EMAIL` | Sender email address (default: `noreply@multica.ai`) | -> **Note:** The dev master verification code `888888` is gated by `APP_ENV != "production"`. The Docker self-host stack defaults to `APP_ENV=production` (so `888888` is disabled), which protects publicly reachable instances. For local development without email configured, set `APP_ENV=development` in your `.env` to enable `888888` — never do this on a public instance. +> **Note:** If Resend is not configured, generated verification codes are printed to backend logs. A fixed local testing code is disabled by default; to opt in on a private test instance, set `APP_ENV=development` and `MULTICA_DEV_VERIFICATION_CODE` to a 6-digit value. It is ignored when `APP_ENV=production`. ### Google OAuth (Optional) diff --git a/SELF_HOSTING_AI.md b/SELF_HOSTING_AI.md index 2c534c415..ea0cbcbdb 100644 --- a/SELF_HOSTING_AI.md +++ b/SELF_HOSTING_AI.md @@ -37,7 +37,7 @@ multica setup self-host The `multica setup self-host` command will: 1. Configure CLI to connect to localhost:8080 / localhost:3000 -2. Open a browser for login — use verification code `888888` with any email +2. Open a browser for login — use the emailed code, or the generated code printed in backend logs when Resend is unset 3. Discover workspaces automatically 4. Start the daemon in the background diff --git a/apps/docs/content/docs/auth-setup.mdx b/apps/docs/content/docs/auth-setup.mdx index 78aaaf6ac..7d8fb2908 100644 --- a/apps/docs/content/docs/auth-setup.mdx +++ b/apps/docs/content/docs/auth-setup.mdx @@ -1,6 +1,6 @@ --- title: Sign-in and signup configuration -description: Configure email + verification code sign-in, Google OAuth, and signup allowlists. Avoid the 888888 trap. +description: Configure email + verification code sign-in, Google OAuth, signup allowlists, and local test codes. --- import { Callout } from "fumadocs-ui/components/callout"; @@ -27,17 +27,24 @@ The user enters an email on the sign-in page → the server sends a 6-digit code **What happens if you don't set `RESEND_API_KEY`**: the server doesn't error, but **every email that should have been sent is written to the server's stdout only**. Handy for local development (copy the code from the logs); in production it's a black hole. -## The 888888 trap +## Fixed local testing codes -**If `APP_ENV` is not set to `production`, anyone can sign in to any account with the code `888888`.** +**Do not enable a fixed verification code on a publicly reachable instance.** -Multica has a development-only master code, `888888` — a backdoor so local development doesn't depend on Resend. The rule is straightforward: when `APP_ENV != "production"`, **any email** plus `888888` passes verification. +The old behavior where non-production instances accepted `888888` by default has been removed. Unless you explicitly configure it, typing `888888` is treated like any other wrong code. -**Production deployments must set `APP_ENV=production`**. If you deploy via `make selfhost` / `docker-compose.selfhost.yml`, this value is already set to `production` by default; but if you deploy from source yourself, write your own Docker config, or redefine environment variables in Kubernetes — you must add `APP_ENV=production` yourself. +Local development without Resend should use the generated code printed in server logs. If you need deterministic local/private automation, set `MULTICA_DEV_VERIFICATION_CODE` to a 6-digit value such as `888888`, and keep `APP_ENV` non-production: + +```bash +APP_ENV=development +MULTICA_DEV_VERIFICATION_CODE=888888 +``` + +This shortcut is ignored when `APP_ENV=production`. -To check whether your deployment has this trap: open the sign-in page, enter **any email** to request a code, then enter `888888`. If you get in, your `APP_ENV` is not set to `production`, and **the entire instance is wide open**. +Production deployments should leave `MULTICA_DEV_VERIFICATION_CODE` empty and set `APP_ENV=production`. If you deploy via `make selfhost` / `docker-compose.selfhost.yml`, `APP_ENV` defaults to `production`. ## Google OAuth configuration diff --git a/apps/docs/content/docs/auth-setup.zh.mdx b/apps/docs/content/docs/auth-setup.zh.mdx index aabe40d82..7ce811536 100644 --- a/apps/docs/content/docs/auth-setup.zh.mdx +++ b/apps/docs/content/docs/auth-setup.zh.mdx @@ -1,12 +1,12 @@ --- title: 登录与注册配置 -description: 配 Email 验证码登录 + Google OAuth + 注册白名单。避开最坑的 888888 陷阱。 +description: 配 Email 验证码登录、Google OAuth、注册白名单和本地测试验证码。 --- import { Callout } from "fumadocs-ui/components/callout"; import { Mermaid } from "@/components/mermaid"; -Multica 支持两种登录方式:**Email + 验证码**(默认)和 **Google OAuth**(可选)。登录成功后 server 签发一个 30 天有效期的 JWT cookie。这一页讲怎么配、怎么限制谁能注册、以及自部署最容易踩的一个陷阱。 +Multica 支持两种登录方式:**Email + 验证码**(默认)和 **Google OAuth**(可选)。登录成功后 server 签发一个 30 天有效期的 JWT cookie。这一页讲怎么配、怎么限制谁能注册、以及本地测试验证码怎么安全使用。 上面用到的环境变量的清单见 [环境变量](/environment-variables);token 怎么用、生命周期细节见 [认证与令牌](/auth-tokens)。 @@ -27,17 +27,24 @@ Multica 支持两种登录方式:**Email + 验证码**(默认)和 **Google **不配 `RESEND_API_KEY` 的后果**:server 不报错,但**所有本该发出去的邮件只打到 server 的 stdout**。本地开发方便(你从日志抄验证码),生产环境等于黑洞。 -## 888888 陷阱 +## 固定本地测试验证码 -**`APP_ENV` 不设为 `production`,任何人都能用验证码 `888888` 登录任何账号。** +**不要在公网可访问实例上启用固定验证码。** -Multica 有一个开发用的主验证码(master code)`888888`——为了本地开发不依赖 Resend 而设的后门。判定逻辑很简单:`APP_ENV != "production"` 时,**任何邮箱**输 `888888` 都能通过。 +旧版「非 production 默认接受 `888888`」的行为已经移除。除非你显式配置,否则输入 `888888` 会和普通错误验证码一样被拒绝。 -**生产部署必须设 `APP_ENV=production`**。如果你用 `make selfhost` / `docker-compose.selfhost.yml` 自部署,这个值已经默认设为 `production`;但如果你自己从源码部署、自己写 Docker 配置、或者在 Kubernetes 里重新定义环境变量——一定要自己把 `APP_ENV=production` 加上。 +不配 Resend 的本地开发,应使用 server 日志里打印的随机验证码。如果你需要确定性的本地/私有自动化测试,可以把 `MULTICA_DEV_VERIFICATION_CODE` 设成一个 6 位数字,比如 `888888`,并保持 `APP_ENV` 为非 production: + +```bash +APP_ENV=development +MULTICA_DEV_VERIFICATION_CODE=888888 +``` + +`APP_ENV=production` 时这个快捷码会被忽略。 -检查你的部署是否有这个陷阱:打开登录页,输入**任意邮箱**请求验证码,再在验证码栏输 `888888`。如果能登进去 = 你的 `APP_ENV` 没设成 `production`,**整个实例处于完全开放状态**。 +生产部署应保持 `MULTICA_DEV_VERIFICATION_CODE` 为空,并设置 `APP_ENV=production`。如果你用 `make selfhost` / `docker-compose.selfhost.yml` 自部署,`APP_ENV` 默认就是 `production`。 ## 怎么配 Google OAuth diff --git a/apps/docs/content/docs/auth-tokens.mdx b/apps/docs/content/docs/auth-tokens.mdx index 3472f9f3a..27aea8704 100644 --- a/apps/docs/content/docs/auth-tokens.mdx +++ b/apps/docs/content/docs/auth-tokens.mdx @@ -38,7 +38,7 @@ In day-to-day use you'll only touch the first two directly. The **[daemon](/daem 2. Enter the code; the server issues a JWT cookie (browser) or exchanges it for a PAT (CLI). -**Self-hosting operators, take note**: if `APP_ENV` is not set to `production`, the verification code is always `888888` — anyone can sign in as anyone. See [Self-host auth configuration](/auth-setup). +**Self-hosting operators, take note**: keep `MULTICA_DEV_VERIFICATION_CODE` empty on public deployments. If you enable a fixed local test code, anyone who can request a code can sign in with that value while `APP_ENV` is non-production. See [Self-host auth configuration](/auth-setup). ### Google OAuth diff --git a/apps/docs/content/docs/auth-tokens.zh.mdx b/apps/docs/content/docs/auth-tokens.zh.mdx index 0c04e01ba..e907bfb23 100644 --- a/apps/docs/content/docs/auth-tokens.zh.mdx +++ b/apps/docs/content/docs/auth-tokens.zh.mdx @@ -38,7 +38,7 @@ Multica 有三种令牌,对应三种使用场景:浏览器 Web UI、命令 2. 输入验证码,server 签发 JWT cookie(浏览器)或交换出 PAT(CLI) -**自部署运维注意**:如果环境变量 `APP_ENV` 不是 `production`,验证码恒为 `888888`——任何人能登录任何账号。详见 [自部署的认证配置](/auth-setup)。 +**自部署运维注意**:公网部署时保持 `MULTICA_DEV_VERIFICATION_CODE` 为空。如果启用固定本地测试验证码,在 `APP_ENV` 非 production 时,任何能请求验证码的人都能用该固定值登录。详见 [自部署的认证配置](/auth-setup)。 ### Google OAuth diff --git a/apps/docs/content/docs/environment-variables.mdx b/apps/docs/content/docs/environment-variables.mdx index 81e03c6a8..88b4cb60d 100644 --- a/apps/docs/content/docs/environment-variables.mdx +++ b/apps/docs/content/docs/environment-variables.mdx @@ -7,20 +7,21 @@ import { Callout } from "fumadocs-ui/components/callout"; A self-hosted Multica [server](/self-host-quickstart) reads its configuration from environment variables at startup — database, sign-in, email, storage, signup allowlists all live here. This page groups every variable by purpose: each section spells out **what happens if you leave it unset** and **which ones you must set in production**. For how to actually configure the auth-related ones, see [Sign-in and signup configuration](/auth-setup). -## The five required at startup +## Core server variables -These are the five you must think about before deploying — some have defaults that let the server start, but in production you should set all of them explicitly. +These are the core variables you must think about before deploying — some have defaults that let the server start, but in production you should set the required ones explicitly. | Variable | Default | Required in production? | |---|---|---| | `DATABASE_URL` | `postgres://multica:multica@localhost:5432/multica?sslmode=disable` | **Yes** | | `PORT` | `8080` | No (unless you change the port) | | `JWT_SECRET` | `multica-dev-secret-change-in-production` | **Yes** (the default is unsafe) | -| `APP_ENV` | empty | **Yes** (must be `production` — see the next section for the trap) | +| `APP_ENV` | empty | **Yes** (must be `production`) | | `FRONTEND_ORIGIN` | empty | **Yes** (self-host must set its own domain) | +| `MULTICA_DEV_VERIFICATION_CODE` | empty | No (must stay empty in production) | -**If `APP_ENV` is not set to `production`, anyone can sign in to any account using the code `888888`.** Multica has a development-only master code, `888888` — when `APP_ENV != "production"`, **any email** plus `888888` passes verification. The behavior is intentional for local development (no Resend dependency); **in production, failing to set `production` is equivalent to disabling auth entirely**. See [Sign-in and signup configuration → The 888888 trap](/auth-setup#the-888888-trap). +**Keep `MULTICA_DEV_VERIFICATION_CODE` empty in production.** A fixed local test code is disabled by default, but if you opt in with `MULTICA_DEV_VERIFICATION_CODE=888888`, anyone who can request a code can sign in with that fixed value while `APP_ENV` is non-production. The shortcut is ignored when `APP_ENV=production`. ### Database connection pool diff --git a/apps/docs/content/docs/environment-variables.zh.mdx b/apps/docs/content/docs/environment-variables.zh.mdx index a2388bc74..abde447ed 100644 --- a/apps/docs/content/docs/environment-variables.zh.mdx +++ b/apps/docs/content/docs/environment-variables.zh.mdx @@ -7,20 +7,21 @@ import { Callout } from "fumadocs-ui/components/callout"; Multica 的 [自部署](/self-host-quickstart) 服务器启动时从环境变量读取配置——数据库、登录、邮件、存储、注册白名单都在这里配。这一页按用途分组给完整清单:每组说清楚**不设会怎样**、**生产必须设哪几个**。Auth 相关那几个怎么真正配见 [登录与注册配置](/auth-setup)。 -## 启动必填的五个 +## 核心 server 环境变量 -这五个是你部署前必须考虑的——有些有默认值能让 server 启动,但生产环境里你应该全部显式配。 +这些是你部署前必须考虑的核心变量——有些有默认值能让 server 启动,但生产环境里你应该显式配置必填项。 | 环境变量 | 默认值 | 生产必须设? | |---|---|---| | `DATABASE_URL` | `postgres://multica:multica@localhost:5432/multica?sslmode=disable` | **是** | | `PORT` | `8080` | 否(除非换端口)| | `JWT_SECRET` | `multica-dev-secret-change-in-production` | **是**(默认值不安全)| -| `APP_ENV` | 空 | **是**(必须 `production`——见下一节陷阱)| +| `APP_ENV` | 空 | **是**(必须 `production`)| | `FRONTEND_ORIGIN` | 空 | **是**(self-host 要填你自己的域名)| +| `MULTICA_DEV_VERIFICATION_CODE` | 空 | 否(生产必须保持为空)| -**`APP_ENV` 不设为 `production`,任何人都能用 `888888` 登录任何账号。** Multica 有一个开发用的主验证码(master code)`888888`——`APP_ENV != "production"` 时**任何邮箱**输 `888888` 都能通过。本地开发时故意留空方便调试;**生产环境一旦不设 `production`,等于 auth 完全失效**。详见 [登录与注册配置 → 888888 陷阱](/auth-setup#888888-陷阱)。 +**生产环境保持 `MULTICA_DEV_VERIFICATION_CODE` 为空。** 固定本地测试验证码默认关闭;如果你设置 `MULTICA_DEV_VERIFICATION_CODE=888888`,在 `APP_ENV` 非 production 时,任何能请求验证码的人都能用这个固定值登录。`APP_ENV=production` 时该快捷码会被忽略。 ### 数据库连接池 diff --git a/apps/docs/content/docs/getting-started/self-hosting.zh.mdx b/apps/docs/content/docs/getting-started/self-hosting.zh.mdx index 96700ba00..acf33b996 100644 --- a/apps/docs/content/docs/getting-started/self-hosting.zh.mdx +++ b/apps/docs/content/docs/getting-started/self-hosting.zh.mdx @@ -31,7 +31,7 @@ curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/ins multica setup self-host ``` -This installs the CLI, checks out the latest self-host assets, pulls the official Multica images from GHCR, and configures everything for localhost. Then open http://localhost:3000 and pick a login method: configure `RESEND_API_KEY` in `.env` for email-based codes (recommended), or set `APP_ENV=development` in `.env` to enable the dev master code **`888888`**. See [Step 2 — Log In](#step-2--log-in) for details. +This installs the CLI, checks out the latest self-host assets, pulls the official Multica images from GHCR, and configures everything for localhost. Then open http://localhost:3000 and pick a login method: configure `RESEND_API_KEY` in `.env` for email-based codes (recommended), or leave Resend unset and copy the generated code from backend logs. See [Step 2 — Log In](#step-2--log-in) for details. If the self-host server is already running and you only need the CLI on a macOS/Linux machine, install it with Homebrew: `brew install multica-ai/tap/multica`. @@ -68,16 +68,16 @@ If you prefer running the Docker Compose steps manually: `cp .env.example .env`, ### Step 2 — Log In -Open http://localhost:3000. The Docker self-host stack defaults to `APP_ENV=production` (set in `docker-compose.selfhost.yml`), so the dev master code is **disabled by default** for safety on public deployments. Pick one of the following to log in: +Open http://localhost:3000. The Docker self-host stack defaults to `APP_ENV=production` (set in `docker-compose.selfhost.yml`), and there is no fixed verification code by default. Pick one of the following to log in: - **Recommended (production):** configure `RESEND_API_KEY` in `.env`, then restart the backend. Real verification codes will be sent to the email address you enter. See [Configuration](#configuration) below. -- **Evaluation / private network:** set `APP_ENV=development` in `.env` and restart the backend. Verification code **`888888`** will then work for any email address. -- **Without configuring either:** the verification code is generated server-side and printed to the backend container logs (look for `[DEV] Verification code for ...:`). Useful for one-off testing on a single machine. +- **Without email configured:** the verification code is generated server-side and printed to the backend container logs (look for `[DEV] Verification code for ...:`). Useful for one-off testing on a single machine. +- **Deterministic local/private testing:** set `APP_ENV=development` and `MULTICA_DEV_VERIFICATION_CODE=888888` in `.env`, then restart the backend. This fixed code is ignored when `APP_ENV=production`. Changes to `ALLOW_SIGNUP` and `GOOGLE_CLIENT_ID` also take effect after restarting the backend / compose stack. The web UI reads both from `/api/config` at runtime, so no web rebuild is needed. -**Warning:** do **not** set `APP_ENV=development` on a publicly reachable instance — anyone who knows an email address can then log in with `888888`. +**Warning:** do **not** set `MULTICA_DEV_VERIFICATION_CODE` on a publicly reachable instance — anyone who knows an email address can then log in with that fixed code. ### Step 3 — Install CLI & Start Daemon diff --git a/apps/docs/content/docs/self-host-quickstart.mdx b/apps/docs/content/docs/self-host-quickstart.mdx index e5be0cf43..451818f14 100644 --- a/apps/docs/content/docs/self-host-quickstart.mdx +++ b/apps/docs/content/docs/self-host-quickstart.mdx @@ -45,19 +45,19 @@ Once it's up: - **Frontend**: [http://localhost:3000](http://localhost:3000) - **Backend**: [http://localhost:8080](http://localhost:8080) -## 2. Important: set `APP_ENV` to `production` +## 2. Important: keep production safety on -**`docker-compose.selfhost.yml` sets `APP_ENV` to `production` by default** — this prevents the development "master code `888888`" from being enabled on an instance you've exposed to the public internet. +**`docker-compose.selfhost.yml` sets `APP_ENV` to `production` by default** and leaves `MULTICA_DEV_VERIFICATION_CODE` empty, so there is no fixed code on public instances. -**But if your `.env` leaves `APP_ENV` empty or sets it to another value**, `888888` is enabled — **anyone can log in as any email by typing `888888` as the verification code**. See [Auth setup → The 888888 trap](/auth-setup#the-888888-trap). +Only set `MULTICA_DEV_VERIFICATION_CODE` for local or private test automation. If a fixed code is enabled while `APP_ENV` is non-production, anyone who can request a code can sign in with that fixed value. See [Auth setup → Fixed local testing codes](/auth-setup#fixed-local-testing-codes). -Before any public deployment, make sure `.env` has `APP_ENV=production`. +Before any public deployment, make sure `.env` has `APP_ENV=production` and `MULTICA_DEV_VERIFICATION_CODE` is empty. ## 3. Configure the email service (optional but recommended) -Without email configured, your users can't receive verification codes — **unless `APP_ENV != production`, in which case `888888` works** (see the warning above). +Without email configured, your users can't receive verification codes by email; the server prints generated codes to stdout instead. To actually send verification emails: @@ -80,6 +80,7 @@ Open [http://localhost:3000](http://localhost:3000): - Enter your email - Grab the verification code from the Resend email (or, if you haven't configured Resend, from the server container stdout — look for the `[DEV] Verification code` line) +- Do not use `888888` unless you explicitly set `MULTICA_DEV_VERIFICATION_CODE=888888` on a non-production private instance - Log in and create your first workspace ## 5. Point the CLI at your own server diff --git a/apps/docs/content/docs/self-host-quickstart.zh.mdx b/apps/docs/content/docs/self-host-quickstart.zh.mdx index b333fd8eb..3d854ed8b 100644 --- a/apps/docs/content/docs/self-host-quickstart.zh.mdx +++ b/apps/docs/content/docs/self-host-quickstart.zh.mdx @@ -44,19 +44,19 @@ make selfhost - **前端**:[http://localhost:3000](http://localhost:3000) - **后端**:[http://localhost:8080](http://localhost:8080) -## 2. 重要:改 `APP_ENV` 成 `production` +## 2. 重要:保持生产安全配置 -**`docker-compose.selfhost.yml` 默认把 `APP_ENV` 设成 `production`**——这防止开发用的"万能验证码 `888888`"在你公网暴露的实例上启用。 +**`docker-compose.selfhost.yml` 默认把 `APP_ENV` 设成 `production`**,并让 `MULTICA_DEV_VERIFICATION_CODE` 为空,所以公网实例默认没有固定验证码。 -**但如果你的 `.env` 里把 `APP_ENV` 留空或改成其他值**,`888888` 会被启用——**任何人输入任何邮箱 + `888888` 都能登录**。详见 [登录与注册配置 → 888888 陷阱](/auth-setup#888888-陷阱)。 +只在本地或私有测试自动化里设置 `MULTICA_DEV_VERIFICATION_CODE`。如果在 `APP_ENV` 非 production 时启用了固定验证码,任何能请求验证码的人都能用这个固定值登录。详见 [登录与注册配置 → 固定本地测试验证码](/auth-setup#固定本地测试验证码)。 -公网部署前一定检查 `.env` 里 `APP_ENV=production`。 +公网部署前一定检查 `.env` 里 `APP_ENV=production`,且 `MULTICA_DEV_VERIFICATION_CODE` 为空。 ## 3. 配置邮件服务(可选但推荐) -如果不配邮件,你的用户无法收到验证码——**但如果 `APP_ENV != production` 你可以用 `888888` 登录**(见上方警告)。 +如果不配邮件,用户无法通过邮件收到验证码;server 会把生成的验证码打印到 stdout。 要真的发验证码邮件: @@ -79,6 +79,7 @@ make selfhost - 输入你的邮箱 - 从 Resend 邮件里拿验证码(或者前面没配 Resend 的话从 server 容器的 stdout 里抄 `[DEV] Verification code` 这行) +- 不要直接使用 `888888`;只有在非 production 私有实例上显式设置 `MULTICA_DEV_VERIFICATION_CODE=888888` 后它才会生效 - 登录后创建第一个工作区 ## 5. 连接命令行工具到你自己的 server diff --git a/apps/docs/content/docs/troubleshooting.mdx b/apps/docs/content/docs/troubleshooting.mdx index 2b0a0a9be..1c8864e3c 100644 --- a/apps/docs/content/docs/troubleshooting.mdx +++ b/apps/docs/content/docs/troubleshooting.mdx @@ -108,28 +108,29 @@ On the server side (self-host), grep for `"no_tasks"` / `"no_capacity"` to see t - Domain not verified → run the DNS verification flow in the Resend console (add SPF / DKIM records) - In an emergency (internal testing) → copy the code printed under `[DEV]` from the server logs -## Verification code `888888` doesn't work +## Fixed local test code doesn't work -**Symptom**: on a self-hosted instance, you try to sign in with the development-only master code `888888` and it's rejected with `invalid or expired code`. +**Symptom**: on a self-hosted instance, you try to sign in with a fixed local test code such as `888888` and it's rejected with `invalid or expired code`. **Likely causes** (mutually exclusive): -1. **`APP_ENV=production`** — this is the **correct** production configuration; `888888` is **disabled** when `APP_ENV=production`. Intentional design, not a bug -2. **You received a real code via Resend** — if Resend is configured, the server sent an actual email; `888888` is only a dev fallback +1. **`MULTICA_DEV_VERIFICATION_CODE` is empty** — fixed codes are disabled by default +2. **`APP_ENV=production`** — this is the **correct** production configuration; fixed local test codes are ignored in production +3. **The configured code is not 6 digits** — the shortcut only accepts a 6-digit value **How to diagnose**: ```bash -cat .env | grep APP_ENV # inspect current config -docker exec env | grep APP_ENV # docker deployment +cat .env | grep -E 'APP_ENV|MULTICA_DEV_VERIFICATION_CODE' +docker exec env | grep -E 'APP_ENV|MULTICA_DEV_VERIFICATION_CODE' ``` Check your inbox (including spam) for the real verification code. **How to fix**: -- In production, you shouldn't be using `888888` at all — configure Resend and use real codes -- **For local development or internal testing**, if you need `888888`, ensure `APP_ENV` is unset or not `production` — but **never** run a public instance this way (see [Sign-in and signup configuration → The 888888 trap](/auth-setup#the-888888-trap)) +- In production, leave `MULTICA_DEV_VERIFICATION_CODE` empty — configure Resend and use real codes +- For local development or internal testing, either copy the generated code from server logs or set `APP_ENV=development` plus `MULTICA_DEV_VERIFICATION_CODE=888888` — never enable a fixed code on a public instance (see [Sign-in and signup configuration → Fixed local testing codes](/auth-setup#fixed-local-testing-codes)) ## Port conflicts diff --git a/apps/docs/content/docs/troubleshooting.zh.mdx b/apps/docs/content/docs/troubleshooting.zh.mdx index 5b3390e34..ef73228dd 100644 --- a/apps/docs/content/docs/troubleshooting.zh.mdx +++ b/apps/docs/content/docs/troubleshooting.zh.mdx @@ -108,28 +108,29 @@ multica issue show # 看 task 历史 - 域名没验证 → Resend console 里走 DNS 验证流程(加 SPF / DKIM 记录) - 紧急情况下(如内部测试)→ 从 server 日志里抄 `[DEV]` 打印出的验证码 -## 验证码是 `888888` 但登不进去 +## 固定本地测试验证码登不进去 -**症状**:自部署实例,想用开发用的主验证码 `888888` 登录,但被拒 `invalid or expired code`。 +**症状**:自部署实例,想用 `888888` 这类固定本地测试验证码登录,但被拒 `invalid or expired code`。 -**可能原因**(这俩互斥): +**可能原因**(互斥): -1. **`APP_ENV=production`** —— 这正是你**应该**的生产配置;`888888` 在 `APP_ENV=production` 时**被禁用**。这是刻意设计,不是 bug -2. **你在 Resend 收到了真实验证码** —— 如果 Resend 已配,server 实际发了真邮件,`888888` 只作为 dev fallback +1. **`MULTICA_DEV_VERIFICATION_CODE` 为空** —— 固定验证码默认关闭 +2. **`APP_ENV=production`** —— 这是正确的生产配置;固定本地测试验证码在 production 中会被忽略 +3. **配置的验证码不是 6 位数字** —— 这个快捷码只接受 6 位数字 **怎么查**: ```bash -cat .env | grep APP_ENV # 看当前配置 -docker exec env | grep APP_ENV # docker 部署 +cat .env | grep -E 'APP_ENV|MULTICA_DEV_VERIFICATION_CODE' +docker exec env | grep -E 'APP_ENV|MULTICA_DEV_VERIFICATION_CODE' ``` 检查邮箱(含 spam)看有没有收到真实验证码。 **怎么修**: -- 生产环境你本来就不该用 `888888`—— 配好 Resend 用真实验证码 -- **本地开发或内网测试**若需要 `888888`,确保 `APP_ENV` 未设或不是 `production`——但**绝对不要**这样跑公网实例(详见 [登录与注册配置 → 888888 陷阱](/auth-setup#888888-陷阱)) +- 生产环境保持 `MULTICA_DEV_VERIFICATION_CODE` 为空,配好 Resend 后使用真实验证码 +- 本地开发或内网测试可以从 server 日志抄生成的验证码;如果需要 `888888`,设置 `APP_ENV=development` 和 `MULTICA_DEV_VERIFICATION_CODE=888888`。不要在公网实例启用固定验证码(详见 [登录与注册配置 → 固定本地测试验证码](/auth-setup#固定本地测试验证码)) ## 端口冲突 diff --git a/docker-compose.selfhost.yml b/docker-compose.selfhost.yml index 4fee5c822..acb0df47d 100644 --- a/docker-compose.selfhost.yml +++ b/docker-compose.selfhost.yml @@ -56,6 +56,7 @@ services: CLOUDFRONT_PRIVATE_KEY: ${CLOUDFRONT_PRIVATE_KEY:-} COOKIE_DOMAIN: ${COOKIE_DOMAIN:-} APP_ENV: ${APP_ENV:-production} + MULTICA_DEV_VERIFICATION_CODE: ${MULTICA_DEV_VERIFICATION_CODE:-} MULTICA_APP_URL: ${MULTICA_APP_URL:-http://localhost:3000} ALLOW_SIGNUP: ${ALLOW_SIGNUP:-true} ALLOWED_EMAILS: ${ALLOWED_EMAILS:-} diff --git a/docs/docs-outline.md b/docs/docs-outline.md index 61b727fbd..ded80ff7f 100644 --- a/docs/docs-outline.md +++ b/docs/docs-outline.md @@ -147,7 +147,7 @@ multica issue assign --agent **关键约定**: -- **Callout**:`...`。warning 用于陷阱(如 888888),info 用于补充说明,tip 用于最佳实践 +- **Callout**:`...`。warning 用于陷阱(如固定测试验证码),info 用于补充说明,tip 用于最佳实践 - **代码块**:shell 命令用 \`\`\`bash;配置用 \`\`\`yaml / \`\`\`env;JSON 用 \`\`\`json - **Cross-link**:用 markdown 链接 `[显示文字](/docs/page-slug)`,不要写成 "详见 Tasks 章节" - **表格**:有 3 行以上对照才用表格,不要 1-2 行也用 @@ -723,11 +723,11 @@ multica issue assign --agent > **合并说明**:原 7.3 Auth Setup + 7.10 Signup Controls 合并。 -- **Source files**: `server/internal/handler/auth.go`(APP_ENV 判断 + checkSignupAllowed), `.env.example`(auth 相关注释) +- **Source files**: `server/internal/handler/auth.go`(固定测试验证码 + checkSignupAllowed), `.env.example`(auth 相关注释) - **目标读者**: self-host 运维 - **叙事位置**: self-host 的 auth 配置。 - **写什么**(1500-2000 字): - - **🚨 超醒目 warning block**:`APP_ENV=production` 必须设置,否则 verification code 恒为 `888888`(任何人登录任何账号) + - **🚨 超醒目 warning block**:生产环境必须保持 `MULTICA_DEV_VERIFICATION_CODE` 为空;固定测试验证码只用于非 production 私有测试 - Email + verification code 登录流程(依赖 Resend) - Google OAuth 配置步骤(创建 OAuth client → redirect URI → 填 env) - **Signup 白名单三层优先级决策树**: @@ -737,9 +737,9 @@ multica issue assign --agent - 典型场景:开放给公司域 / 限定几个邮箱 / 完全关闭 signup - 和邀请的关系(signup 关了也能通过邀请加人) - **不写**: JWT 实现、token 类型(§8.2 讲) -- **写前要验证**: APP_ENV 判断条件;OAuth 流程最新;Signup 优先级 +- **写前要验证**: 固定测试验证码的 env 条件;OAuth 流程最新;Signup 优先级 - **⚠️ 动笔前必读**: - - ⚠️⚠️ **888888 陷阱必须最醒目**(红色 warning block),这是 self-host 最大坑 + - ⚠️⚠️ **固定测试验证码风险必须最醒目**(红色 warning block),这是 self-host 最大坑 - OAuth 给外部步骤截图,别假设读者懂 GCP Console - 决策树建议用 Mermaid 图 - **Owner**: – @@ -754,7 +754,7 @@ multica issue assign --agent - 任务一直 queued(runtime offline / max_concurrent 满 / agent 配错) - WebSocket 连不上(cookie / CORS / proxy) - Email 没收到(Resend 未配置 → 看 stderr) - - 验证码收到是 888888 但不工作(APP_ENV 检查) + - 固定测试验证码不工作(APP_ENV / MULTICA_DEV_VERIFICATION_CODE 检查) - Port 冲突 - 日志位置:daemon / server / browser console - **不写**: 深度 bug report(去 GitHub issue) diff --git a/docs/docs-rewrite-plan.md b/docs/docs-rewrite-plan.md index d1dd6ed9a..ecc07ab1a 100644 --- a/docs/docs-rewrite-plan.md +++ b/docs/docs-rewrite-plan.md @@ -118,7 +118,7 @@ Multica = **人 + AI agent 在同一个看板上协作的任务管理平台**。 | Overview | 决策树(哪种部署模式适合你) | | Docker Compose deployment | `make selfhost` vs `make selfhost-build` | | Environment variables reference | 完整 env 表 | -| Authentication setup | **🚨 `APP_ENV != "production"` 会让 verification code 固定为 `888888`** —— 生产必须设置 `APP_ENV=production`;Google OAuth 配置;signup 白名单 | +| Authentication setup | **🚨 固定测试验证码必须显式设置 `MULTICA_DEV_VERIFICATION_CODE`,生产保持为空**;Google OAuth 配置;signup 白名单 | | Storage | S3 / CloudFront / 本地磁盘 | | Email | Resend 配置;**没配会落到 stderr** | | Upgrading | 版本升级 + migration 策略 | @@ -145,7 +145,7 @@ Installation / Authentication / Setup / Daemon / Workspace / Issue / Comment / A | 5 | Webhook autopilot trigger 字段建了但没接路由——第一版不文档化 | Autopilots | | 6 | custom_env merge 是覆盖而非合并——不能用 custom_env"取消设置"系统 env | Agents | | 7 | 旧 assignee 取消分配后不会被取消订阅 | Subscriptions | -| 8 | `APP_ENV != "production"` 时 verification code 恒为 `888888` | Self-Hosting → Auth | +| 8 | 固定本地测试验证码默认关闭;`MULTICA_DEV_VERIFICATION_CODE` 仅用于非 production 私有测试 | Self-Hosting → Auth | | 9 | Signup 白名单优先级:ALLOWED_EMAILS > ALLOWED_EMAIL_DOMAINS > ALLOW_SIGNUP | Self-Hosting → Auth | | 10 | One daemon ↔ many runtimes;one runtime ↔ ONE provider;同 daemon_id 重启复用旧 runtime 行 | Runtimes / Daemon | | 11 | Inbox 10 种类型,mention dedup 只在单 event 内生效 | Inbox | @@ -159,7 +159,7 @@ Installation / Authentication / Setup / Daemon / Workspace / Issue / Comment / A |---|---| | Mermaid diagram | 架构图 / task 生命周期 / trigger 流向 / autopilot 调度链 | | Tabs | Cloud / Self-Host / Desktop 并列;CLI / UI 并列 | -| Callouts(内置)| Tip / Warning / Note — **警告类密集用在 Agents 的 custom_env 和 Self-Host 的 888888** | +| Callouts(内置)| Tip / Warning / Note — **警告类密集用在 Agents 的 custom_env 和 Self-Host 的固定测试验证码** | | Code Tabs | API 调用多语言(Shell / Node / Go) | | Video / GIF | "Create your first agent"、"Follow an agent working" | | DeploymentPicker(定制)| 交互式决策树:回答 3 个问题 → 推荐部署路径 | diff --git a/scripts/init-worktree-env.sh b/scripts/init-worktree-env.sh index b02ebe0f3..095e21a38 100644 --- a/scripts/init-worktree-env.sh +++ b/scripts/init-worktree-env.sh @@ -32,6 +32,7 @@ DATABASE_URL=postgres://multica:multica@localhost:${postgres_port}/${postgres_db PORT=${backend_port} JWT_SECRET=change-me-in-production +MULTICA_DEV_VERIFICATION_CODE= MULTICA_SERVER_URL=ws://localhost:${backend_port}/ws MULTICA_APP_URL=${frontend_origin} diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 5233b9166..c4cab7257 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -433,7 +433,7 @@ function Start-LocalInstall { Write-Host " multica setup self-host " -NoNewline; Write-Host "# Configure + authenticate + start daemon" -ForegroundColor DarkGray Write-Host "" Write-Host " Login: configure RESEND_API_KEY in .env for email codes," - Write-Host " or set APP_ENV=development in .env to enable the dev master code 888888." + Write-Host " or read the generated code from backend logs when Resend is unset." Write-Host "" Write-Host " To stop all services:" Write-Host ' $env:MULTICA_MODE="stop"; irm https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.ps1 | iex' diff --git a/scripts/install.sh b/scripts/install.sh index 4f1c085d4..ebb0d82ca 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -393,7 +393,7 @@ run_with_server() { printf " ${CYAN}multica setup self-host${RESET} # Configure + authenticate + start daemon\n" printf "\n" printf " ${BOLD}Login:${RESET} configure ${CYAN}RESEND_API_KEY${RESET} in .env for email codes,\n" - printf " or set ${CYAN}APP_ENV=development${RESET} in .env to enable the dev master code ${BOLD}888888${RESET}.\n" + printf " or read the generated code from backend logs when Resend is unset.\n" printf "\n" printf " ${BOLD}To stop all services:${RESET}\n" printf " curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bash -s -- --stop\n" diff --git a/server/cmd/server/main.go b/server/cmd/server/main.go index 201a5f7bd..acbb39d73 100644 --- a/server/cmd/server/main.go +++ b/server/cmd/server/main.go @@ -124,6 +124,13 @@ func main() { if os.Getenv("RESEND_API_KEY") == "" { slog.Warn("RESEND_API_KEY is not set — email verification codes will be printed to the log instead of emailed.") } + if os.Getenv("MULTICA_DEV_VERIFICATION_CODE") != "" { + if strings.EqualFold(strings.TrimSpace(os.Getenv("APP_ENV")), "production") { + slog.Warn("MULTICA_DEV_VERIFICATION_CODE is set but ignored because APP_ENV=production.") + } else { + slog.Warn("MULTICA_DEV_VERIFICATION_CODE is enabled. Use it only for local development or private test instances.") + } + } port := os.Getenv("PORT") if port == "" { diff --git a/server/internal/handler/auth.go b/server/internal/handler/auth.go index 4345b46fe..d2847ccb3 100644 --- a/server/internal/handler/auth.go +++ b/server/internal/handler/auth.go @@ -2,11 +2,11 @@ package handler import ( "context" - "errors" "crypto/rand" "crypto/subtle" "encoding/binary" "encoding/json" + "errors" "fmt" "io" "log/slog" @@ -36,6 +36,8 @@ func (e SignupError) Error() string { var ErrSignupProhibited = SignupError{Message: "user registration is disabled on this self-hosted instance"} var ErrEmailNotAllowed = SignupError{Message: "email address or domain not allowed on this instance"} +const devVerificationCodeEnv = "MULTICA_DEV_VERIFICATION_CODE" + type UserResponse struct { ID string `json:"id"` Name string `json:"name"` @@ -92,6 +94,35 @@ func generateCode() (string, error) { return fmt.Sprintf("%06d", n), nil } +func isDevVerificationCode(code string) bool { + if isProductionEnv() { + return false + } + + devCode := strings.TrimSpace(os.Getenv(devVerificationCodeEnv)) + if !isSixDigitCode(devCode) { + return false + } + + return subtle.ConstantTimeCompare([]byte(code), []byte(devCode)) == 1 +} + +func isProductionEnv() bool { + return strings.EqualFold(strings.TrimSpace(os.Getenv("APP_ENV")), "production") +} + +func isSixDigitCode(code string) bool { + if len(code) != 6 { + return false + } + for _, ch := range code { + if ch < '0' || ch > '9' { + return false + } + } + return true +} + func (h *Handler) issueJWT(user db.User) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "sub": uuidToString(user.ID), @@ -311,8 +342,8 @@ func (h *Handler) VerifyCode(w http.ResponseWriter, r *http.Request) { return } - isMasterCode := code == "888888" && os.Getenv("APP_ENV") != "production" - if !isMasterCode && subtle.ConstantTimeCompare([]byte(code), []byte(dbCode.Code)) != 1 { + isDevCode := isDevVerificationCode(code) + if !isDevCode && subtle.ConstantTimeCompare([]byte(code), []byte(dbCode.Code)) != 1 { _ = h.Queries.IncrementVerificationCodeAttempts(r.Context(), dbCode.ID) writeError(w, http.StatusBadRequest, "invalid or expired code") return diff --git a/server/internal/handler/handler_test.go b/server/internal/handler/handler_test.go index 04b70e380..57d37f75b 100644 --- a/server/internal/handler/handler_test.go +++ b/server/internal/handler/handler_test.go @@ -1543,7 +1543,94 @@ func TestVerifyCode(t *testing.T) { } } +func createVerificationCodeForTest(t *testing.T, email, code string) { + t.Helper() + + _, err := testPool.Exec(context.Background(), ` + INSERT INTO verification_code (email, code, expires_at) + VALUES ($1, $2, now() + interval '10 minutes') + `, email, code) + if err != nil { + t.Fatalf("create verification code: %v", err) + } +} + +func TestVerifyCodeRejectsDevCodeUnlessExplicitlyConfigured(t *testing.T) { + t.Setenv(devVerificationCodeEnv, "") + t.Setenv("APP_ENV", "") + + const email = "dev-code-disabled-test@multica.ai" + ctx := context.Background() + + t.Cleanup(func() { + testPool.Exec(ctx, `DELETE FROM verification_code WHERE email = $1`, email) + }) + + createVerificationCodeForTest(t, email, "123456") + + w := httptest.NewRecorder() + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(map[string]string{"email": email, "code": "888888"}) + req := httptest.NewRequest("POST", "/auth/verify-code", &buf) + req.Header.Set("Content-Type", "application/json") + testHandler.VerifyCode(w, req) + if w.Code != http.StatusBadRequest { + t.Fatalf("VerifyCode (disabled dev code): expected 400, got %d: %s", w.Code, w.Body.String()) + } +} + +func TestVerifyCodeAcceptsConfiguredDevCodeOutsideProduction(t *testing.T) { + t.Setenv(devVerificationCodeEnv, "888888") + t.Setenv("APP_ENV", "development") + + const email = "dev-code-enabled-test@multica.ai" + ctx := context.Background() + + t.Cleanup(func() { + testPool.Exec(ctx, `DELETE FROM verification_code WHERE email = $1`, email) + testPool.Exec(ctx, `DELETE FROM "user" WHERE email = $1`, email) + }) + + createVerificationCodeForTest(t, email, "123456") + + w := httptest.NewRecorder() + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(map[string]string{"email": email, "code": "888888"}) + req := httptest.NewRequest("POST", "/auth/verify-code", &buf) + req.Header.Set("Content-Type", "application/json") + testHandler.VerifyCode(w, req) + if w.Code != http.StatusOK { + t.Fatalf("VerifyCode (enabled dev code): expected 200, got %d: %s", w.Code, w.Body.String()) + } +} + +func TestVerifyCodeRejectsConfiguredDevCodeInProduction(t *testing.T) { + t.Setenv(devVerificationCodeEnv, "888888") + t.Setenv("APP_ENV", "production") + + const email = "dev-code-production-test@multica.ai" + ctx := context.Background() + + t.Cleanup(func() { + testPool.Exec(ctx, `DELETE FROM verification_code WHERE email = $1`, email) + }) + + createVerificationCodeForTest(t, email, "123456") + + w := httptest.NewRecorder() + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(map[string]string{"email": email, "code": "888888"}) + req := httptest.NewRequest("POST", "/auth/verify-code", &buf) + req.Header.Set("Content-Type", "application/json") + testHandler.VerifyCode(w, req) + if w.Code != http.StatusBadRequest { + t.Fatalf("VerifyCode (production dev code): expected 400, got %d: %s", w.Code, w.Body.String()) + } +} + func TestVerifyCodeWrongCode(t *testing.T) { + t.Setenv(devVerificationCodeEnv, "") + const email = "wrong-code-test@multica.ai" ctx := context.Background() @@ -1572,6 +1659,8 @@ func TestVerifyCodeWrongCode(t *testing.T) { } func TestVerifyCodeBruteForceProtection(t *testing.T) { + t.Setenv(devVerificationCodeEnv, "") + const email = "bruteforce-test@multica.ai" ctx := context.Background()