---
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。是标准流程。支持两种邮件发送通道，按部署环境二选一：

### Option A：Resend（公网/云端部署推荐）

1. 在 [Resend](https://resend.com/) 建账号、验证你的域名
2. 创建 API key
3. 设环境变量：

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

4. 重启 server

### Option B：SMTP relay（内网/自部署）

适合内网无法访问 `api.resend.com`，或者已经有内部邮件中继（Microsoft Exchange、Postfix、自部署 SendGrid 等）的场景。同时设置时 `SMTP_HOST` 优先级高于 `RESEND_API_KEY`：只要 `SMTP_HOST` 非空，server 一律走 SMTP 路径，即便 `RESEND_API_KEY` 也配着，验证码和邀请邮件也不会经过 Resend 外发出内网。

SMTP 路径覆盖大多数本地邮件服务器（特别是 Microsoft Exchange 的 receive connector）暴露的三种 relay 模式：

| 模式 | 端口 | 认证 | TLS |
|---|---|---|---|
| 匿名内部 relay | `25` | 无 —— 按 IP / 子网信任 | 链路上无 TLS（仅限内网段） |
| 认证提交（submission） | `587` | `SMTP_USERNAME` + `SMTP_PASSWORD` | STARTTLS，自动升级 |
| 隐式 TLS（SMTPS） | `465` | 可选（`SMTP_USERNAME` + `SMTP_PASSWORD`） | 连上即 TLS 握手 —— 端口 `465` 自动启用；非标准端口设 `SMTP_TLS=implicit` 强制开启 |

**匿名 Exchange relay，端口 25** —— 经典的 "internal SMTP relay" / Exchange 匿名 receive connector，按可信子网放行，不要求凭据：

```bash
SMTP_HOST=exchange.internal.example.com
SMTP_PORT=25
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_TLS_INSECURE=false
RESEND_FROM_EMAIL=noreply@yourdomain.com  # 同时作为 SMTP From: 头
```

**认证提交，端口 587** —— 需要 service account 的 relay；服务端 advertise STARTTLS 时会自动升级：

```bash
SMTP_HOST=smtp.internal.example.com
SMTP_PORT=587
SMTP_USERNAME=multica
SMTP_PASSWORD=...
SMTP_TLS_INSECURE=false        # 仅在私有 CA / 自签证书时改成 true
RESEND_FROM_EMAIL=noreply@yourdomain.com
```

**隐式 TLS（SMTPS），端口 465** —— 适用于只提供 SMTPS、不广告 STARTTLS 的服务商（例如阿里云 / 腾讯企业邮箱）。端口 `465` 会自动启用隐式 TLS；非标准 SMTPS 端口设置 `SMTP_TLS=implicit`（别名：`smtps`、`ssl`）即可强制开启：

```bash
SMTP_HOST=smtp.qiye.aliyun.com
SMTP_PORT=465                  # 465 自动启用隐式 TLS
SMTP_USERNAME=multica@yourdomain.com
SMTP_PASSWORD=...
SMTP_TLS=implicit              # 465 上可省略；在非标准 SMTPS 端口上必填
RESEND_FROM_EMAIL=noreply@yourdomain.com
```

**严格公网 relay（例如 Google Workspace `smtp-relay.gmail.com`）**还要求一个合法的 EHLO 名称。它们会拒绝来自公网 IP 的默认 `localhost` 问候，relay 随即断开连接——这不会在问候阶段报错，而是在后续某条命令上表现为一个不知所云的 `EOF`（`smtp auth: EOF`）。把 `SMTP_EHLO_NAME` 设成 relay 期望的 FQDN；它默认取机器主机名，而在容器内这通常不是合法的 FQDN：

```bash
SMTP_HOST=smtp-relay.gmail.com
SMTP_PORT=587
SMTP_EHLO_NAME=mail.yourdomain.com   # relay 接受的 FQDN；默认取（非 FQDN 的）容器主机名
RESEND_FROM_EMAIL=noreply@yourdomain.com
```

启动时 server 会打印当前选择的 provider 和协商出的 TLS 模式，比如 `EmailService: SMTP relay exchange.internal.example.com:25 (starttls) from=noreply@example.com` 或 `… smtp.qiye.aliyun.com:465 (implicit-tls) from=…`（或 `Resend API` / `DEV mode`），密码不会出现在日志里。重启后没看到 SMTP 这行，说明 `SMTP_HOST` 没进到进程，确认下容器环境（`docker compose -f docker-compose.selfhost.yml exec backend env | grep SMTP`）。

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

## 固定本地测试验证码

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

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

没配任何邮件后端（Resend 和 SMTP 都没设）的本地开发，应使用 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`、注册被拒的常见排查
