# Roundcube 一键登录

MailHub 的“一键登录 Webmail”使用两层短期凭据，不会向浏览器、URL 或 Roundcube 日志暴露邮箱原密码：

1. MailHub 在已登录的同源页面为指定邮箱签发 60 秒、仅可使用一次的启动票据。
2. 浏览器通过隐藏表单把 `mailhub_ticket` **POST** 到 `WEBMAIL_SSO_URL`，票据不会进入查询字符串或 Referer。
3. `mailhub_sso` 插件在服务器端用共享密钥调用 MailHub 的 `/internal/webmail-sso/exchange`，取得邮箱地址和 `mhw_` 临时凭据。
4. Roundcube 使用该凭据连接固定的 IMAP/SMTP 服务；成功后固定进入 `INBOX`。退出或销毁 Roundcube 会话时，插件会尽力调用 `/internal/webmail-sso/revoke`，凭据自身到期仍是最终边界。

同一用户每分钟最多签发 20 个启动票据，同一邮箱最多保留 5 个有效 Webmail 临时会话；超过上限时会淘汰最久未使用的会话。过期或撤销记录会机会式清理，避免 SQLite 表无界增长。

## MailHub 配置

在 `.env` 中设置：

```dotenv
WEBMAIL_SSO_URL=https://mail.us.ss5.xyz/
WEBMAIL_SSO_SECRET_FILE=./data/secrets/webmail_sso_secret
WEBMAIL_SSO_READER_GID=33
WEBMAIL_SSO_TICKET_TTL_SECONDS=60
WEBMAIL_SSO_CREDENTIAL_TTL_SECONDS=43200
```

`WEBMAIL_SSO_URL` 留空时功能关闭，便于未配置 Roundcube 的部署继续运行；一旦设置 URL，Secret 文件必须存在且有效，否则应用会快速失败。Compose 固定从 `/data/secrets/webmail_sso_secret` 读取（宿主机对应 `./data/secrets/webmail_sso_secret`），功能关闭时不会因为旧部署缺少该文件而阻止容器升级。`WEBMAIL_SSO_URL` 必须是固定 HTTPS 起始地址。MailHub 会把它规范化成绝对 origin（例如 `https://mail.us.ss5.xyz`）并写入票据；Roundcube 的 `mailhub_sso_audience` 必须完全一致。

Secret 只接受 64–512 个十六进制字符。首次部署前可直接运行 `npm run prepare:dovecot`；也可以手工生成：

```bash
install -d -m 0700 data/secrets
openssl rand -hex 32 > data/secrets/webmail_sso_secret
chown 1000:33 data/secrets data/secrets/webmail_sso_secret
chmod 0750 data/secrets
chmod 0440 data/secrets/webmail_sso_secret
```

同机 Roundcube 应把 `WEBMAIL_SSO_READER_GID` 设置为其 **PHP 工作进程的实际主组 GID**，Debian/Apache 镜像中的 `www-data` 通常为 `33`。Secret 保持 MailHub 的 uid `1000` 所有、对该组只读（目录 `0750`、文件 `0440`），不要改成全局可读。仅配置 Compose `group_add` 并不可靠，因为 Apache 降权后的 worker 可能主动丢弃补充组。MailHub 的准备脚本会从 `.env` 读取该纯数字 GID 并在后续部署中持续维护权限。若 Roundcube 在另一台机器，复制相同内容到仅由其 PHP 运行用户可读的独立文件即可。

该 Secret 不能复用 Session、Dovecot、Token 或其他业务密钥，也不能提交到 Git。

## Roundcube 1.6/1.7 安装

Roundcube PHP 运行环境需要启用 cURL 扩展。

1. 将 [`docker/roundcube/plugins/mailhub_sso`](../docker/roundcube/plugins/mailhub_sso) 复制到 Roundcube 的 `plugins/mailhub_sso`。
2. 将 `plugins/mailhub_sso/config.inc.php.dist` 复制为 `plugins/mailhub_sso/config.inc.php`，按实际网络修改内部地址；不要把 Secret 内容写进 PHP 配置。
3. 把 `mailhub_sso` 加入 Roundcube `$config['plugins']`。可参考 [`docker/roundcube/config.inc.php.example`](../docker/roundcube/config.inc.php.example)。
4. 将与 MailHub 完全相同的 Secret 只读挂载到 Roundcube 的 `/run/secrets/webmail_sso_secret`；同机容器把 `WEBMAIL_SSO_READER_GID` 设置为 PHP worker 的实际主组 GID，确保 worker 能穿过 `0750` 目录并读取 `0440` 文件。
5. 保持外部 Webmail 全程 HTTPS，并把 `mailhub_sso_audience` 设置为精确 origin，不带路径和结尾斜杠。

若 HTTPS 终止在 Roundcube 前方的反向代理，将 Roundcube 的 `use_https` 设为 `true`；不要同时启用与它互斥的 `force_https`。

生产示例：

```php
$config['plugins'][] = 'mailhub_sso';
$config['imap_host'] = 'ssl://in.ss5.xyz:993';
$config['smtp_host'] = 'ssl://in.ss5.xyz:465';
$config['smtp_user'] = '%u';
$config['smtp_pass'] = '%p';

$config['mailhub_sso_internal_base_url'] = 'http://app:3000';
$config['mailhub_sso_audience'] = 'https://mail.us.ss5.xyz';
$config['mailhub_sso_secret_file'] = '/run/secrets/webmail_sso_secret';
$config['mailhub_sso_imap_host'] = 'ssl://in.ss5.xyz:993';
```

## 网络拓扑

本仓库的 Compose **不运行 Roundcube**。可按现有部署选择以下方式：

- Roundcube 容器与 MailHub 同机：让 Roundcube 加入 `mailhub` Compose 网络，使用 `http://app:3000`，并把宿主机同一个 Secret 文件以只读方式挂入两个容器。
- Roundcube 运行在宿主机：使用仅回环监听的 `http://127.0.0.1:3025`。
- Roundcube 在另一台机器：通过 WireGuard、SSH 隧道或等价私网访问。不得把 `/internal/webmail-sso/*` 无保护地公开到互联网。

Roundcube 使用独立 Compose 时，可将现有 MailHub 网络声明为 external；实际网络名以 `docker network ls` 为准：

```yaml
services:
  roundcube:
    extra_hosts:
      - "in.ss5.xyz:host-gateway"
    volumes:
      - /absolute/path/to/mailhub/data/secrets/webmail_sso_secret:/run/secrets/webmail_sso_secret:ro
    networks:
      - mailhub

networks:
  mailhub:
    external: true
    name: <mailhub-compose-project>_mailhub
```

同机部署并通过宿主机公开的 `993/465` 端口连接时，应使用上述 `extra_hosts` 将邮件主机名映射到 Docker host gateway。这样既避免容器绕公网 IP 回环连接被拒绝，也继续使用 `in.ss5.xyz` 做 TLS 主机名校验；不要直接把 IMAP/SMTP 地址替换成裸网关 IP。

同时在对外反向代理中拒绝公网访问 `/internal/webmail-sso/`；Bearer Secret 是内网接口的第二层校验，不应代替网络隔离。

内部接口只接受 `Authorization: Bearer <shared-secret>` 和 JSON：

```text
POST /internal/webmail-sso/exchange  { ticket, audience }
POST /internal/webmail-sso/revoke    { credential, audience }
```

插件不跟随 HTTP 重定向，不接受 GET 票据，不允许浏览器指定 IMAP 主机，也不记录内部响应、票据、临时凭据或共享密钥。仅有收信权限的临时凭据可以进入 Roundcube，但 SMTP 认证应由 MailHub 拒绝；有发信权限时 Roundcube 才能发信。

被分配用户通过 Webmail 登录时，Dovecot 使用固定 ACL 组将 IMAP 权限限制为列出、读取和更新 `\Seen`；不能删除/移动邮件、EXPUNGE，也不能创建、删除或重命名文件夹。邮箱所有者保持完整 IMAP 权限。撤销收信权限、停用用户或临时凭据到期后，新的 IMAP 认证会立即失败；已经建立的 IMAP TCP 连接会在连接关闭后生效，Roundcube 的典型按请求连接使该窗口通常很短，但它不是逐条 IMAP 命令的强制踢线。
