EmDash 使用通行密钥认证作为其主要登录方式。通行密钥具有抗网络钓鱼能力,无需密码,并可通过浏览器或密码管理器在设备间工作。
除了通行密钥之外,您还可以添加可插拔的登录提供商 — GitHub、Google 和 Atmosphere(AT 协议)开箱即用,且相同的提供商接口对第三方包开放。任何配置的提供商都可用于创建第一个管理员账户、登录或链接到现有用户。
对于 Cloudflare 部署,Cloudflare Access 也可作为接管整个登录流程的独占认证方法。
工作原理
通行密钥使用 WebAuthn,这是一个创建公钥凭据的 Web 标准,凭据存储在您的设备上或通过密码管理器同步。当您登录时,您的设备证明拥有凭据,而无需通过网络发送密码。
通行密钥认证的优势:
- 无需记住或泄露密码
- 抗网络钓鱼 — 凭据绑定到您网站的域名
- 跨设备同步 — 可与 iCloud 钥匙串、Google 密码管理器、1Password 等配合使用
- 快速登录 — 使用生物识别或 PIN 一键登录
首次用户设置
首次访问管理面板时,设置向导将引导您创建管理员账户。
-
导航到
http://localhost:4321/_emdash/admin -
您将被重定向到设置向导。输入:
- 网站标题 — 您网站的名称
- 标语 — 简短描述
- 管理员邮箱 — 您的邮箱地址
-
点击创建网站以注册您的通行密钥
-
您的浏览器将提示您创建通行密钥:
- macOS:Touch ID、设备密码或安全密钥
- Windows:Windows Hello 或安全密钥
- 移动设备:Face ID、指纹或 PIN
-
注册通行密钥后,您将登录并重定向到管理仪表板。
登录
设置完成后,返回管理面板将触发通行密钥认证:
-
访问
/_emdash/admin -
如果未登录,您将看到登录页面
-
点击登录进行认证
-
您的浏览器会提示您提供通行密钥(生物识别、PIN 或安全密钥)
-
验证后,您将被重定向到管理仪表板
魔法链接备用方案
如果您无法使用通行密钥(例如,设备丢失),魔法链接提供了替代方案。这需要配置邮箱。
-
在登录页面上,点击使用邮箱登录
-
输入您的邮箱地址
-
检查收件箱中的登录链接
-
点击链接进行认证(有效期 15 分钟)
登录提供商
除了通行密钥之外,EmDash 还支持可插拔的登录提供商,它们会出现在登录页面和设置向导中。GitHub、Google 和 Atmosphere 提供商开箱即用;第三方包可以使用相同的接口注册自己的提供商。
提供商是累加的 — 启用提供商时通行密钥继续工作,用户可以将提供商链接到现有的仅通行密钥账户。第一个用户也可以通过任何配置的提供商创建,因此如果您愿意,全新安装可以完全跳过通行密钥。
配置提供商
将提供商传递给 EmDash 集成上的 authProviders 数组。以下示例启用 GitHub、Google 和 Atmosphere:
import { defineConfig } from "astro/config";
import emdash from "emdash/astro";
import { github } from "emdash/auth/providers/github";
import { google } from "emdash/auth/providers/google";
import { atproto } from "@emdash-cms/auth-atproto";
export default defineConfig({
integrations: [
emdash({
authProviders: [github(), google(), atproto()],
}),
],
});
对于登录页面,顺序很重要:提供商按您列出的顺序渲染,紧凑的纯按钮提供商首先显示,需要自定义表单的提供商(如要求提供用户名的 Atmosphere)随后显示。
GitHub
以下示例启用 GitHub 提供商:
import { github } from "emdash/auth/providers/github";
emdash({ authProviders: [github()] });
通过环境变量设置凭据。EmDash 首先检查带前缀的名称,然后回退到不带前缀的名称:
| 变量 | 用途 |
|---|---|
EMDASH_OAUTH_GITHUB_CLIENT_ID / GITHUB_CLIENT_ID | OAuth 应用客户端 ID |
EMDASH_OAUTH_GITHUB_CLIENT_SECRET / GITHUB_CLIENT_SECRET | OAuth 应用密钥 |
将您的 GitHub OAuth 应用的回调 URL 配置为 https://your-site.example.com/_emdash/api/auth/oauth/github/callback。
以下示例启用 Google 提供商:
import { google } from "emdash/auth/providers/google";
emdash({ authProviders: [google()] });
通过环境变量设置凭据。EmDash 首先检查带前缀的名称,然后回退到不带前缀的名称:
| 变量 | 用途 |
|---|---|
EMDASH_OAUTH_GOOGLE_CLIENT_ID / GOOGLE_CLIENT_ID | OAuth 应用客户端 ID |
EMDASH_OAUTH_GOOGLE_CLIENT_SECRET / GOOGLE_CLIENT_SECRET | OAuth 应用密钥 |
将您的 Google OAuth 客户端的重定向 URI 配置为 https://your-site.example.com/_emdash/api/auth/oauth/google/callback。
Atmosphere(AT 协议)
对于贡献者已经拥有 Atmosphere 账户的网站 — Bluesky 和更广泛的 AT 协议网络背后的用户拥有的身份 — 安装 Atmosphere 提供商:
pnpm add @emdash-cms/auth-atproto
以下示例启用带有用户名白名单的 Atmosphere 提供商:
import { atproto } from "@emdash-cms/auth-atproto";
emdash({
authProviders: [
atproto({
allowedHandles: ["*.example.com"],
}),
],
});
无需客户端密钥或环境变量。有关用户名/DID 白名单、角色映射以及 AT 协议 OAuth 配置文件所需的本地开发设置,请参阅 Atmosphere 登录指南。
构建您自己的提供商
提供商只是一个 AuthProviderDescriptor — 一个 id、一个人类可读的标签,以及管理端 React 组件、路由处理程序、公共路由前缀和存储集合的任意组合。该形状从 emdash 导出:
import type { AuthProviderDescriptor } from "emdash";
export function myProvider(): AuthProviderDescriptor {
return {
id: "my-provider",
label: "My Provider",
adminEntry: "my-provider/admin", // exports LoginButton / LoginForm / SetupStep
routes: [
{ pattern: "/_emdash/api/auth/my-provider/login", entrypoint: "my-provider/routes/login.ts" },
{ pattern: "/_emdash/api/auth/my-provider/callback", entrypoint: "my-provider/routes/callback.ts" },
],
publicRoutes: ["/_emdash/api/auth/my-provider/"],
storage: {
sessions: {},
},
};
}
Atmosphere 包(@emdash-cms/auth-atproto)是需要自定义登录表单、OAuth 路由处理程序和持久存储的提供商的最完整的实际参考。
用户角色
EmDash 使用基于角色的访问控制,共有五个级别:
| 角色 | 级别 | 描述 |
|---|---|---|
| Subscriber | 10 | 阅读已发布内容(无草稿访问权限) |
| Contributor | 20 | 创建内容(需要批准才能发布) |
| Author | 30 | 创建/编辑/发布自己的内容 |
| Editor | 40 | 管理所有内容 |
| Admin | 50 | 完全访问权限,包括设置 |
每个角色都继承所有较低级别的权限。第一个用户始终创建为管理员。
订阅者和草稿内容
订阅者拥有 content:read 权限,因此可以向已认证的读者提供仅限会员的已发布内容。他们无法看到草稿、已安排项目、已删除项目、修订版本或预览 URL — 这些由 content:read_drafts 保护,授予给贡献者及以上角色。列表和获取端点对订阅者透明地过滤为 status=published;仅编辑者视图(/compare、/revisions、/trash、/preview-url)直接拒绝订阅者请求。
邀请用户
管理员可以通过管理面板邀请新用户:
-
转到设置 > 用户
-
点击邀请用户
-
输入用户的邮箱并选择角色
-
点击发送邀请
-
用户会收到带有邀请链接的邮件
-
他们点击链接并注册通行密钥
邀请有效期为 7 天。管理员可以从用户页面重新发送或撤销邀请。
管理通行密钥
用户可以从账户设置中管理他们的通行密钥:
- 添加通行密钥 — 为备份或其他设备注册额外的通行密钥
- 删除通行密钥 — 删除您不再使用的通行密钥
- 重命名通行密钥 — 为通行密钥提供描述性名称
每个用户最多可以注册 10 个通行密钥。
允许群组无需邀请即可登录
要允许群组无需邀请每个用户即可登录,请配置带有白名单的登录提供商。Atmosphere 提供商接受 allowedHandles 和 allowedDIDs(参见 Atmosphere 登录);Cloudflare Access 适配器通过 autoProvision 和 roleMapping 从您的身份提供商配置用户。任何配置的提供商也可以创建初始管理员账户。
会话
会话使用安全的 HttpOnly、SameSite=Lax cookie,并具有滑动过期时间,持续 30 天 — 过期时间在活动时重置。
安全说明
- 通行密钥存储为公钥 — 私钥永远不会离开您的设备
- 挑战验证可防止重放攻击
- 速率限制可防止暴力破解(5 次尝试/分钟/IP)
- 会话是 HttpOnly、Secure、SameSite=Lax 以确保 cookie 安全
- 魔法链接令牌采用 SHA-256 哈希 — 原始令牌永远不会存储
故障排除
”未注册通行密钥”
如果您在登录时看到此错误,您的通行密钥可能已从密码管理器中删除。请管理员向您发送魔法链接或新邀请。
“通行密钥认证失败”
这通常意味着通行密钥是为不同的域创建的。通行密钥与域绑定 — 为 localhost:4321 创建的通行密钥在 example.com 上无法使用。为每个域注册新的通行密钥。
“会话已过期”
会话默认持续 30 天,具有滑动过期时间。如果您意外注销,请清除 cookie 并重新登录。
丢失所有通行密钥
如果您失去了对所有已注册通行密钥的访问权限:
- 请另一位管理员向您发送魔法链接(需要邮箱配置)
- 使用魔法链接登录
- 在账户设置中注册新的通行密钥
如果您是唯一的管理员且未配置邮箱,则需要通过数据库重置网站的认证。
Cloudflare Access
部署到 Cloudflare 时,您可以使用 Cloudflare Access 作为您的认证提供商,而不是通行密钥。Access 使用您现有的身份提供商在边缘处理认证。
为什么使用 Cloudflare Access
- 单点登录 — 用户使用您公司的 IdP 进行认证
- 集中访问控制 — 在 Cloudflare 仪表板中管理谁可以访问管理
- 无需管理通行密钥 — 无需注册或管理通行密钥
- 基于群组的角色 — 自动将 IdP 群组映射到 EmDash 角色
设置
- 为您的 EmDash 网站创建 Cloudflare Access 应用程序
- 从应用程序设置中记下应用程序受众(AUD)标签
- 配置 EmDash 以使用 Access:
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
import emdash from "emdash/astro";
import { d1, access } from "@emdash-cms/cloudflare";
export default defineConfig({
output: "server",
adapter: cloudflare(),
integrations: [
emdash({
database: d1({ binding: "DB" }),
auth: access({
teamDomain: "myteam.cloudflareaccess.com",
audience: "abc123def456...", // From Access app settings
}),
}),
],
});
配置选项
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
teamDomain | string | 必需 | 您的 Access 团队域(例如 myteam.cloudflareaccess.com) |
audience | string | 必需 | Access 设置中的应用程序受众(AUD)标签 |
autoProvision | boolean | true | 在首次 Access 登录时创建 EmDash 用户 |
defaultRole | number | 30 | 不匹配任何群组的用户的角色(30 = Author) |
syncRoles | boolean | false | 根据 IdP 群组在每次登录时更新角色 |
roleMapping | object | — | 将 IdP 群组名称映射到角色级别 |
audienceEnvVar | string | "CF_ACCESS_AUDIENCE" | 受众标签的环境变量名称(硬编码的替代方案) |
角色映射
将您的 IdP 群组映射到 EmDash 角色:
emdash({
auth: access({
teamDomain: "myteam.cloudflareaccess.com",
audience: "abc123...",
roleMapping: {
Admins: 50, // Admin
"Content Editors": 40, // Editor
Writers: 30, // Author
},
defaultRole: 20, // 不属于任何群组的用户为 Contributor
}),
});
如果用户属于多个群组,则第一个匹配的群组获胜。访问网站的第一个用户始终成为管理员,无论群组如何。
角色同步行为
默认情况下(syncRoles: false),用户的角色在首次登录时设置,之后不会更改。这允许管理员在 EmDash 中手动调整角色。
如果您希望 IdP 群组具有权威性,请设置 syncRoles: true — 用户的角色将根据其当前群组在每次登录时更新。
工作原理
- 用户访问
/_emdash/admin - Cloudflare Access 拦截并重定向到您的 IdP
- 用户进行认证(SSO、MFA 等)
- Access 在请求中设置签名的 JWT
- EmDash 验证 JWT 并创建/认证用户
已禁用的功能
启用 Access 后,以下功能不可用:
- 登录页面(
/_emdash/admin/login) - 通行密钥注册和管理
- OAuth 登录
- 魔法链接登录
- 自助注册
- 用户邀请
用户管理完全通过您的 Cloudflare Access 策略完成。
故障排除
”无 Access JWT”
请求在没有 Access JWT 的情况下到达 EmDash。这意味着:
- Access 未配置为保护您的应用程序
- Access 策略与管理路由不匹配
验证您的 Access 应用程序覆盖 /_emdash/admin/*。
“JWT 受众不匹配”
您配置中的 audience 与 JWT 不匹配。仔细检查 Access 应用程序设置中的应用程序受众标签。
“用户未授权”
用户通过 Access 认证,但 autoProvision 为 false,并且他们在 EmDash 中不存在。要么:
- 设置
autoProvision: true,要么 - 在他们登录之前手动创建用户