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()