--- 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**。本地开发方便(你从日志抄验证码),生产环境等于黑洞。 ## 固定本地测试验证码 **不要在公网可访问实例上启用固定验证码。** 旧版「非 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` 时这个快捷码会被忽略。 生产部署应保持 `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,前端不用重建镜像、不用重新部署。 **回调 URI 在 Google Console 和 `GOOGLE_REDIRECT_URI` 两处必须完全一致**,包括协议(`http` vs `https`)、尾部斜杠、端口。不一致 Google 会拒绝整个 OAuth 流程,用户看到的错误是 `redirect_uri_mismatch`。 ## 怎么限制谁能注册 三层环境变量按优先级组合: A{email 在
ALLOWED_EMAILS 里?} A -- 是 --> Allow[允许注册] A -- 否 --> B{domain 在
ALLOWED_EMAIL_DOMAINS 里?} B -- 是 --> Allow B -- 否 --> C{任一白名单
非空?} C -- 是 --> Block[拒绝] C -- 否 --> D{ALLOW_SIGNUP
= true?} D -- 是 --> Allow D -- 否 --> Block `} /> **已经登录过的老用户永远可以再次登录**——signup 白名单只对**首次注册**生效,不拦截老用户。 - **`ALLOWED_EMAILS`**(最高优先级)—— 显式邮箱白名单,逗号分隔。**非空时只有列表里的邮箱能注册**。 - **`ALLOWED_EMAIL_DOMAINS`**—— 域名白名单,逗号分隔(例如 `company.io,partner.com`)。 - **`ALLOW_SIGNUP`** —— 总开关,默认 `true`。设 `false` 完全关闭注册。 **三层白名单是 AND 语义,不是 OR。** 很多人第一直觉是「设 `ALLOWED_EMAIL_DOMAINS=company.io` + `ALLOW_SIGNUP=true` 就是允许 company.io 和其他所有人」——**不是**。任何一层白名单只要设了非空值,**不匹配的邮箱直接拒**,`ALLOW_SIGNUP=true` 挡不住。 要真的「允许所有人」,所有三个环境变量都留空(或 `ALLOW_SIGNUP=true`)。 **典型配法**: | 需求 | 配置 | |---|---| | 公司内网,只允许 `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`、注册被拒的常见排查