---
title: 登录与注册配置
description: 配 Email 验证码登录、Google OAuth、注册白名单和本地测试验证码。
---

import { Callout } from "fumadocs-ui/components/callout";
import { Mermaid } from "@/components/mermaid";

Multica 支持两种登录方式：**Email + 验证码**（默认）和 **Google OAuth**（可选）。登录成功后 server 签发一个 30 天有效期的 JWT cookie。这一页讲怎么配、怎么限制谁能注册、以及本地测试验证码怎么安全使用。

上面用到的环境变量的清单见 [环境变量](/environment-variables)；token 怎么用、生命周期细节见 [认证与令牌](/auth-tokens)。

## Email + 验证码登录怎么工作

用户在登录页输邮箱 → server 发 6 位验证码 → 用户填回 → server 验证 → 签发 JWT cookie。是标准流程。需要 [Resend](https://resend.com/) 作为邮件发送服务：

1. 在 Resend 建账号、验证你的域名
2. 创建 API key
3. 设环境变量：

    ```bash
    RESEND_API_KEY=re_xxxxxxxxxxxxxxxx
    RESEND_FROM_EMAIL=noreply@yourdomain.com  # 必须是 Resend 已验证的域名
    ```

4. 重启 server

**不配 `RESEND_API_KEY` 的后果**：server 不报错，但**所有本该发出去的邮件只打到 server 的 stdout**。本地开发方便（你从日志抄验证码），生产环境等于黑洞。

## 固定本地测试验证码

<Callout type="warning">
**不要在公网可访问实例上启用固定验证码。**

旧版「非 production 默认接受 `888888`」的行为已经移除。除非你显式配置，否则输入 `888888` 会和普通错误验证码一样被拒绝。

不配 Resend 的本地开发，应使用 server 日志里打印的随机验证码。如果你需要确定性的本地/私有自动化测试，可以把 `MULTICA_DEV_VERIFICATION_CODE` 设成一个 6 位数字，比如 `888888`，并保持 `APP_ENV` 为非 production：

```bash
APP_ENV=development
MULTICA_DEV_VERIFICATION_CODE=888888
```

`APP_ENV=production` 时这个快捷码会被忽略。
</Callout>

生产部署应保持 `MULTICA_DEV_VERIFICATION_CODE` 为空，并设置 `APP_ENV=production`。如果你用 `make selfhost` / `docker-compose.selfhost.yml` 自部署，`APP_ENV` 默认就是 `production`。

## 怎么配 Google OAuth

可选。不配就只有 Email + 验证码登录；配了后登录页会多出「用 Google 登录」按钮。

1. 去 [Google Cloud Console](https://console.cloud.google.com/) 创建一个 OAuth 2.0 client
2. **授权的回调 URI**（Authorized redirect URIs）填你的 Multica 前端地址加 `/auth/callback`，例如：

    ```text
    https://multica.yourdomain.com/auth/callback
    ```

3. 拿到 client ID 和 client secret 后设三个环境变量：

    ```bash
    GOOGLE_CLIENT_ID=xxxxx.apps.googleusercontent.com
    GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxx
    GOOGLE_REDIRECT_URI=https://multica.yourdomain.com/auth/callback
    ```

4. 重启 server。

**热生效**：前端通过 `/api/config` 运行时读这些配置——改完只要重启 server，前端不用重建镜像、不用重新部署。

<Callout type="warning">
**回调 URI 在 Google Console 和 `GOOGLE_REDIRECT_URI` 两处必须完全一致**，包括协议（`http` vs `https`）、尾部斜杠、端口。不一致 Google 会拒绝整个 OAuth 流程，用户看到的错误是 `redirect_uri_mismatch`。
</Callout>

## 怎么限制谁能注册

三层环境变量按优先级组合：

<Mermaid chart={`
graph TD
    Start[新用户首次登录] --> A{email 在<br/>ALLOWED_EMAILS 里?}
    A -- 是 --> Allow[允许注册]
    A -- 否 --> B{domain 在<br/>ALLOWED_EMAIL_DOMAINS 里?}
    B -- 是 --> Allow
    B -- 否 --> C{任一白名单<br/>非空?}
    C -- 是 --> Block[拒绝]
    C -- 否 --> D{ALLOW_SIGNUP<br/>= true?}
    D -- 是 --> Allow
    D -- 否 --> Block
`} />

**已经登录过的老用户永远可以再次登录**——signup 白名单只对**首次注册**生效，不拦截老用户。

- **`ALLOWED_EMAILS`**（最高优先级）—— 显式邮箱白名单，逗号分隔。**非空时只有列表里的邮箱能注册**。
- **`ALLOWED_EMAIL_DOMAINS`**—— 域名白名单，逗号分隔（例如 `company.io,partner.com`）。
- **`ALLOW_SIGNUP`** —— 总开关，默认 `true`。设 `false` 完全关闭注册。

<Callout type="warning">
**三层白名单是 AND 语义，不是 OR。** 很多人第一直觉是「设 `ALLOWED_EMAIL_DOMAINS=company.io` + `ALLOW_SIGNUP=true` 就是允许 company.io 和其他所有人」——**不是**。任何一层白名单只要设了非空值，**不匹配的邮箱直接拒**，`ALLOW_SIGNUP=true` 挡不住。

要真的「允许所有人」，所有三个环境变量都留空（或 `ALLOW_SIGNUP=true`）。
</Callout>

**典型配法**：

| 需求 | 配置 |
|---|---|
| 公司内网，只允许 `company.io` 员工 | `ALLOWED_EMAIL_DOMAINS=company.io` |
| 公司内网 + 几个外部合作者 | `ALLOWED_EMAIL_DOMAINS=company.io` + 合作者个人邮箱加到 `ALLOWED_EMAILS` |
| 完全关闭自助注册，只能邀请 | `ALLOW_SIGNUP=false` |
| 开放注册（不推荐生产用）| 三个都留空 |

## 关了注册还能邀请人进来吗

**只对已经有 Multica 账号的人能**。接受邀请那一步不检查 signup 白名单——如果对方已经注册过（比如在别的工作区），他们点链接登录就能直接接受。

**但还没注册过的人，邀请救不了他们**。他们接受邀请前必须先登录，登录的第一步（发验证码）会过 signup 白名单检查。如果你 `ALLOW_SIGNUP=false`、或他们的邮箱不在 `ALLOWED_EMAILS` / `ALLOWED_EMAIL_DOMAINS` 里，他们**没法完成注册**，也就没法接受邀请。

要邀请一个还没注册的外部协作者：临时把他们的邮箱加到 `ALLOWED_EMAILS`，等他们注册 + 接受邀请之后再把这条移掉。

邀请的创建和使用见 [成员与权限](/members-roles)。

## 下一步

- [环境变量](/environment-variables) —— 这一页用到的环境变量完整定义
- [认证与令牌](/auth-tokens) —— JWT / PAT / Daemon Token 的分类和使用
- [故障排查](/troubleshooting) —— 验证码收不到、OAuth 报 `redirect_uri_mismatch`、注册被拒的常见排查
