eve-auth-and-secrets

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Eve Auth and Secrets

Eve身份认证与密钥管理

Use this workflow to log in to Eve and manage secrets for your app.
使用此工作流登录Eve并管理应用密钥。

When to Use

使用场景

  • Setting up a new project profile
  • Authentication failures
  • Adding or rotating secrets
  • Secret interpolation errors during deploys
  • Setting up identity providers or org invites
  • Adding SSO login to an Eve-deployed app
  • Setting up access groups and scoped data-plane authorization
  • Configuring group-aware RLS for environment databases
  • 设置新的项目配置文件
  • 身份验证失败时
  • 添加或轮换密钥
  • 部署期间出现密钥插值错误时
  • 设置身份提供商或组织邀请
  • 为Eve部署的应用添加SSO登录
  • 设置访问组和范围化数据平面授权
  • 为环境数据库配置支持组的RLS(行级安全)

Authentication

身份认证

bash
eve auth login
eve auth login --ttl 30                # custom token TTL (1-90 days)
eve auth status
bash
eve auth login
eve auth login --ttl 30                # 自定义令牌有效期(1-90天)
eve auth status

Challenge-Response Flow

挑战-响应流程

Eve uses challenge-response authentication. The default provider is
github_ssh
:
  1. Client sends SSH public key fingerprint
  2. Server returns a challenge (random bytes)
  3. Client signs the challenge with the private key
  4. Server verifies the signature and issues a JWT
Eve采用挑战-响应式身份认证,默认提供商为
github_ssh
  1. 客户端发送SSH公钥指纹
  2. 服务器返回挑战信息(随机字节)
  3. 客户端使用私钥对挑战信息签名
  4. 服务器验证签名并颁发JWT

Token Types

令牌类型

TypeIssued ViaUse Case
User Token
eve auth login
Interactive CLI sessions
Job TokenWorker auto-issuedAgent execution within jobs
Minted Token
eve auth mint
Bot/service accounts
JWT payloads include
sub
(user ID),
org_id
,
scope
, and
exp
. Verify tokens via the JWKS endpoint:
GET /auth/jwks
.
Role and org membership changes take effect immediately -- the server resolves permissions from live DB memberships, not stale JWT claims. When a request includes a
project_id
but no
org_id
, the permission guard derives the org context from the project's owning org.
类型颁发方式使用场景
用户令牌
eve auth login
交互式CLI会话
作业令牌工作节点自动颁发作业内的Agent执行
生成令牌
eve auth mint
机器人/服务账户
JWT载荷包含
sub
(用户ID)、
org_id
scope
exp
。可通过JWKS端点验证令牌:
GET /auth/jwks
角色和组织成员身份变更会立即生效——服务器从实时数据库成员关系解析权限,而非过期的JWT声明。当请求包含
project_id
但无
org_id
时,权限防护会从项目所属组织推导组织上下文。

Permissions

权限检查

Check what the current token can do:
bash
eve auth permissions
Register additional identities for multi-provider access:
bash
curl -X POST "$EVE_API_URL/auth/identities" -H "Authorization: Bearer $TOKEN" \
  -d '{"provider": "nostr", "external_id": "<pubkey>"}'
查看当前令牌的可用权限:
bash
eve auth permissions
注册额外身份以支持多提供商访问:
bash
curl -X POST "$EVE_API_URL/auth/identities" -H "Authorization: Bearer $TOKEN" \
  -d '{"provider": "nostr", "external_id": "<pubkey>"}'

Identity Providers

身份提供商

Eve supports pluggable identity providers. The auth guard tries Bearer JWT first, then provider-specific request auth.
ProviderAuth MethodUse Case
github_ssh
SSH challenge-responseDefault CLI login
nostr
NIP-98 request auth + challenge-responseNostr-native users
Eve支持可插拔的身份提供商。认证防护优先尝试Bearer JWT,再使用提供商特定的请求认证方式。
提供商认证方式使用场景
github_ssh
SSH挑战-响应默认CLI登录
nostr
NIP-98请求认证 + 挑战-响应Nostr原生用户

Nostr Authentication

Nostr身份认证

Two paths:
  • Challenge-response: Like SSH but signs with Nostr key. Use
    eve auth login --provider nostr
    .
  • NIP-98 request auth: Every API request signed with a Kind 27235 event. Stateless, no stored token.
两种路径:
  • 挑战-响应:与SSH类似,但使用Nostr密钥签名。使用
    eve auth login --provider nostr
  • NIP-98请求认证:每个API请求都使用Kind 27235事件签名。无状态,无需存储令牌。

Org Invites

组织邀请

Invite external users via the CLI or API:
bash
undefined
通过CLI或API邀请外部用户:
bash
undefined

Invite with SSH key registration (registers key so the user can log in immediately)

邀请并注册SSH密钥(注册密钥后用户可立即登录)

eve admin invite --email user@example.com --ssh-key ~/.ssh/id_ed25519.pub --org org_xxx
eve admin invite --email user@example.com --ssh-key ~/.ssh/id_ed25519.pub --org org_xxx

Invite with GitHub identity

邀请GitHub身份用户

eve admin invite --email user@example.com --github ghuser --org org_xxx
eve admin invite --email user@example.com --github ghuser --org org_xxx

Invite with web-based auth (Supabase)

使用基于Web的认证(Supabase)邀请

eve admin invite --email user@example.com --web --org org_xxx
eve admin invite --email user@example.com --web --org org_xxx

API: invite targeting a Nostr pubkey

API:邀请Nostr公钥用户

curl -X POST "$EVE_API_URL/auth/invites" -H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{"org_id": "org_xxx", "role": "member", "provider_hint": "nostr", "identity_hint": "<pubkey>"}'

If no auth method is specified (`--github`, `--ssh-key`, or `--web`), the CLI warns that the user will not be able to log in. The user can self-register later via `eve auth request-access --org "Org Name" --ssh-key ~/.ssh/id_ed25519.pub --wait`.

When the identity authenticates, Eve auto-provisions their account and org membership.

For app-driven onboarding, use the org-scoped invite API instead of the legacy admin invite flow:

```bash
curl -X POST "$EVE_API_URL/auth/invites" -H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{"org_id": "org_xxx", "role": "member", "provider_hint": "nostr", "identity_hint": "<pubkey>"}'

如果未指定认证方式(`--github`、`--ssh-key`或`--web`),CLI会警告用户无法登录。用户可稍后通过`eve auth request-access --org "Org Name" --ssh-key ~/.ssh/id_ed25519.pub --wait`自行注册。

当身份验证通过后,Eve会自动为用户创建账户并添加组织成员身份。

对于应用驱动的入职流程,使用组织范围的邀请API而非旧版管理员邀请流程:

```bash

Create an org-scoped Supabase invite with a return URL for the app

创建带有返回URL的组织范围Supabase邀请

curl -X POST "$EVE_API_URL/orgs/org_xxx/invites"
-H "Authorization: Bearer $USER_TOKEN"
-H "Content-Type: application/json"
-d '{ "email": "user@example.com", "role": "member", "redirect_to": "https://app.example.com/invite/complete", "app_context": { "project_id": "proj_123" } }'
curl -X POST "$EVE_API_URL/orgs/org_xxx/invites"
-H "Authorization: Bearer $USER_TOKEN"
-H "Content-Type: application/json"
-d '{ "email": "user@example.com", "role": "member", "redirect_to": "https://app.example.com/invite/complete", "app_context": { "project_id": "proj_123" } }'

Search existing org members for an assignee picker

搜索现有组织成员以选择被分配人

curl "$EVE_API_URL/orgs/org_xxx/members/search?q=ali"
-H "Authorization: Bearer $USER_TOKEN"

Use a user token with `orgs:invite` to create or list these invites and `orgs:members:read` for member lookup. Invite emails should land on GoTrue's `/verify` path, not the OAuth callback directly. If the invite is auto-applied during the SSO exchange, Eve returns `invite_redirect_to` so the SSO callback can land the user back in the target app even when the email provider strips nested redirect params. Current invite onboarding establishes the SSO session first, then sends the user through `/set-password` before redirecting to the app.
curl "$EVE_API_URL/orgs/org_xxx/members/search?q=ali"
-H "Authorization: Bearer $USER_TOKEN"

使用具备`orgs:invite`权限的用户令牌创建或列出这些邀请,使用`orgs:members:read`权限进行成员查询。邀请邮件应指向GoTrue的`/verify`路径,而非直接指向OAuth回调。如果在SSO交换期间自动应用邀请,Eve会返回`invite_redirect_to`,以便即使邮件提供商剥离嵌套重定向参数,SSO回调仍能将用户带回目标应用。当前邀请入职流程先建立SSO会话,然后引导用户进入`/set-password`页面,最后重定向到应用。

App-Branded Invite Emails

应用品牌化邀请邮件

Projects opt into app-branded invites with
x-eve.branding
in the manifest. The subject, body, and
From:
display name all carry the app's identity — other apps fall back to "Eve Horizon" defaults.
yaml
x-eve:
  branding:
    app_name: "ACME Portal"
    app_logo_url: "https://app.example.com/assets/logo.svg"   # https-only
    primary_color: "#1f6feb"
    email_from_name: "ACME Portal"
    reply_to_email: "support@example.com"
    support_email: "support@example.com"
    support_url: "https://example.com/help"
Run
eve project sync
after editing. Invites sent with
eve org invite <email> --org <org_id> --project <project_id>
use the project branding. The sender address remains the platform default in Phase 1; only the display name varies. The same branding template is shared with magic-link login emails — only the copy ("Accept invite" vs "Sign in") differs.
项目可通过清单中的
x-eve.branding
选择应用品牌化邀请。邮件主题、正文和
From:
显示名称都会带有应用标识——其他应用则默认使用"Eve Horizon"。
yaml
x-eve:
  branding:
    app_name: "ACME Portal"
    app_logo_url: "https://app.example.com/assets/logo.svg"   # 仅支持https
    primary_color: "#1f6feb"
    email_from_name: "ACME Portal"
    reply_to_email: "support@example.com"
    support_email: "support@example.com"
    support_url: "https://example.com/help"
编辑后运行
eve project sync
。使用
eve org invite <email> --org <org_id> --project <project_id>
发送的邀请会使用项目品牌。第一阶段中,发件地址仍为平台默认值;仅显示名称会有所不同。同一品牌模板也用于魔法链接登录邮件——仅文案("接受邀请" vs "登录")不同。

Token Minting (Admin)

令牌生成(管理员)

Mint tokens for bot/service users without SSH login:
bash
undefined
为无需SSH登录的机器人/服务用户生成令牌:
bash
undefined

Mint token for a bot user (creates user + membership if needed)

为机器人用户生成令牌(如需则创建用户和成员身份)

eve auth mint --email app-bot@example.com --org org_xxx
eve auth mint --email app-bot@example.com --org org_xxx

With custom TTL (1-90 days, default: server configured)

自定义有效期(1-90天,默认:服务器配置)

eve auth mint --email app-bot@example.com --org org_xxx --ttl 90
eve auth mint --email app-bot@example.com --org org_xxx --ttl 90

Scope to project with admin role

限定到项目并赋予管理员角色

eve auth mint --email app-bot@example.com --project proj_xxx --role admin

Print the current access token (useful for scripts):

```bash
eve auth token
eve auth mint --email app-bot@example.com --project proj_xxx --role admin

打印当前访问令牌(适用于脚本):

```bash
eve auth token

Self-Service Access Requests

自助式访问请求

Users without an invite can request access:
bash
eve auth request-access --org "My Company" --email you@example.com
eve auth request-access --org "My Company" --ssh-key ~/.ssh/id_ed25519.pub
eve auth request-access --status <request_id>
Admins approve or reject via:
bash
eve admin access-requests list
eve admin access-requests approve <request_id>
eve admin access-requests reject <request_id> --reason "..."
List responses use the canonical
{ "data": [...] }
envelope.
Approval is atomic (single DB transaction) and idempotent -- re-approving a completed request returns the existing record. If the fingerprint is already registered, Eve reuses that identity owner. If a legacy partial org matches the requested slug and name, Eve reuses it during approval. Failed attempts never leave partial state.
无邀请的用户可申请访问:
bash
eve auth request-access --org "My Company" --email you@example.com
eve auth request-access --org "My Company" --ssh-key ~/.ssh/id_ed25519.pub
eve auth request-access --status <request_id>
管理员通过以下命令批准或拒绝:
bash
eve admin access-requests list
eve admin access-requests approve <request_id>
eve admin access-requests reject <request_id> --reason "..."
列表响应使用标准的
{ "data": [...] }
格式。
批准操作是原子性的(单数据库事务)且幂等——重新批准已完成的请求会返回现有记录。如果指纹已注册,Eve会复用该身份所有者。如果旧版部分组织与请求的slug和名称匹配,Eve会在批准期间复用它。失败的尝试不会留下部分状态。

Credential Check

凭证检查

Verify local AI tool credentials:
bash
eve auth creds                # Show Claude + Codex cred status
eve auth creds --claude       # Only Claude
eve auth creds --codex        # Only Codex
Output includes token type (
setup-token
or
oauth
), preview, and expiry. Use this to confirm token health before syncing.
验证本地AI工具凭证:
bash
eve auth creds                # 显示Claude + Codex凭证状态
eve auth creds --claude       # 仅查看Claude
eve auth creds --codex        # 仅查看Codex
输出包含令牌类型(
setup-token
oauth
)、预览和过期时间。在同步前使用此命令确认令牌状态。

OAuth Token Sync

OAuth令牌同步

Sync local Claude/Codex OAuth tokens into Eve secrets so agents can use them. Scope precedence: project > org > user.
bash
eve auth sync                       # Sync to user-level (default)
eve auth sync --org org_xxx         # Sync to org-level (shared across org projects)
eve auth sync --project proj_xxx    # Sync to project-level (scoped to one project)
eve auth sync --dry-run             # Preview without syncing
This sets
CLAUDE_CODE_OAUTH_TOKEN
/
CLAUDE_OAUTH_REFRESH_TOKEN
(Claude) and
CODEX_AUTH_JSON_B64
(Codex/Code) at the requested scope.
将本地Claude/Codex OAuth令牌同步到Eve密钥中,以便Agent使用。优先级:项目 > 组织 > 用户。
bash
eve auth sync                       # 默认同步到用户级别
eve auth sync --org org_xxx         # 同步到组织级别(组织内所有项目共享)
eve auth sync --project proj_xxx    # 同步到项目级别(限定到单个项目)
eve auth sync --dry-run             # 预览同步内容,不执行实际操作
此操作会在指定范围内设置
CLAUDE_CODE_OAUTH_TOKEN
/
CLAUDE_OAUTH_REFRESH_TOKEN
(Claude)和
CODEX_AUTH_JSON_B64
(Codex/Code)。

Claude Token Types

Claude令牌类型

Token PrefixTypeLifetimeRecommendation
sk-ant-oat01-*
setup-token
(long-lived)
Long-livedPreferred for jobs and automation
Other
sk-ant-*
oauth
(short-lived)
~15 hoursUse for interactive dev; regenerate with
claude setup-token
eve auth sync
warns when syncing a short-lived OAuth token. Run
eve auth creds
to inspect token type before syncing.
令牌前缀类型生命周期推荐场景
sk-ant-oat01-*
setup-token
(长期有效)
长期有效作业和自动化优先选择
其他
sk-ant-*
oauth
(短期有效)
~15小时用于交互式开发;使用
claude setup-token
重新生成
同步短期OAuth令牌时,
eve auth sync
会发出警告。同步前运行
eve auth creds
检查令牌类型。

Automatic Codex/Code Token Write-Back

Codex/Code令牌自动回写

After each harness invocation, the worker checks if the Codex/Code CLI refreshed
auth.json
during the session. If the token changed, it is automatically written back to the originating secret scope (user/org/project) so the next job starts with a fresh token. This is transparent and non-fatal -- a write-back failure logs a warning but does not affect the job result.
For Codex/Code credentials, the sync picks the freshest token across
~/.codex/auth.json
and
~/.code/auth.json
by comparing
tokens.expires_at
.
每次 harness 调用后,工作节点会检查Codex/Code CLI是否在会话期间刷新了
auth.json
。如果令牌已更改,会自动写回原始密钥范围(用户/组织/项目),以便下一个作业使用新鲜令牌。此操作是透明且非致命的——回写失败会记录警告,但不影响作业结果。
对于Codex/Code凭证,同步会通过比较
tokens.expires_at
,从
~/.codex/auth.json
~/.code/auth.json
中选择最新的令牌。

Access Groups + Scoped Access

访问组 + 范围化访问

Groups are first-class authorization primitives that segment data-plane access (org filesystem, org docs, environment databases). Create groups, add members, and bind roles with scoped constraints:
bash
undefined
组是一等授权原语,用于划分数据平面访问权限(组织文件系统、组织文档、环境数据库)。创建组、添加成员并绑定带有范围约束的角色:
bash
undefined

Create a group

创建组

eve access groups create --org org_xxx --slug eng-team --name "Engineering"
eve access groups create --org org_xxx --slug eng-team --name "Engineering"

Add members

添加成员

eve access groups members add eng-team --org org_xxx --user user_abc eve access groups members add eng-team --org org_xxx --service-principal sp_xxx
eve access groups members add eng-team --org org_xxx --user user_abc eve access groups members add eng-team --org org_xxx --service-principal sp_xxx

Bind a role with scoped access

绑定带有范围化访问的角色

eve access bind --org org_xxx --group grp_xxx --role data-reader
--scope-json '{"orgfs":{"allow_prefixes":["/shared/"]},"envdb":{"schemas":["public"]}}'
eve access bind --org org_xxx --group grp_xxx --role data-reader
--scope-json '{"orgfs":{"allow_prefixes":["/shared/"]},"envdb":{"schemas":["public"]}}'

Check effective access

检查有效访问权限

eve access memberships --org org_xxx --user user_abc
undefined
eve access memberships --org org_xxx --user user_abc
undefined

Scope Types

范围类型

ResourceScope FieldsExample
Org Filesystem
orgfs.allow_prefixes
,
orgfs.read_only_prefixes
"/shared/"
,
"/reports/"
Org Documents
orgdocs.allow_prefixes
,
orgdocs.read_only_prefixes
"/pm/features/"
Environment DB
envdb.schemas
,
envdb.tables
"public"
,
"analytics_*"
资源范围字段示例
组织文件系统
orgfs.allow_prefixes
,
orgfs.read_only_prefixes
"/shared/"
,
"/reports/"
组织文档
orgdocs.allow_prefixes
,
orgdocs.read_only_prefixes
"/pm/features/"
环境数据库
envdb.schemas
,
envdb.tables
"public"
,
"analytics_*"

Group-Aware RLS

支持组的RLS

Scaffold RLS helper functions for group-based row-level security in environment databases:
bash
eve db rls init --with-groups
This creates SQL helpers (
app.current_user_id()
,
app.current_group_ids()
,
app.has_group()
) that read session context set by Eve's runtime. Use them in RLS policies:
sql
CREATE POLICY notes_group_read ON notes FOR SELECT
  USING (group_id = ANY(app.current_group_ids()));
为环境数据库中的基于组的行级安全搭建RLS辅助函数:
bash
eve db rls init --with-groups
这会创建SQL辅助函数(
app.current_user_id()
app.current_group_ids()
app.has_group()
),这些函数会读取Eve运行时设置的会话上下文。在RLS策略中使用它们:
sql
CREATE POLICY notes_group_read ON notes FOR SELECT
  USING (group_id = ANY(app.current_group_ids()));

Membership Introspection

成员身份自省

Inspect a principal's full effective access -- base org/project roles, group memberships, resolved bindings, and merged scopes:
bash
eve access memberships --org org_xxx --user user_abc
eve access memberships --org org_xxx --service-principal sp_xxx
The response includes
effective_scopes
(merged across all bindings),
effective_permissions
, and each binding's
matched_via
(direct or group).
查看主体的完整有效访问权限——基础组织/项目角色、组成员身份、已解析的绑定和合并后的范围:
bash
eve access memberships --org org_xxx --user user_abc
eve access memberships --org org_xxx --service-principal sp_xxx
响应包含
effective_scopes
(所有绑定合并后的范围)、
effective_permissions
以及每个绑定的
matched_via
(直接或通过组匹配)。

Resource-Specific Access Checks

特定资源的访问检查

Check and explain access against a specific data-plane resource:
bash
eve access can orgfs:read /shared/reports --org org_xxx
eve access explain orgfs:write /shared/reports --org org_xxx --user user_abc
The response includes
scope_required
,
scope_matched
, and per-grant
scope_reason
explaining why a binding did or did not match the requested resource path.
检查并解释对特定数据平面资源的访问权限:
bash
eve access can orgfs:read /shared/reports --org org_xxx
eve access explain orgfs:write /shared/reports --org org_xxx --user user_abc
响应包含
scope_required
scope_matched
以及每个授权的
scope_reason
,解释绑定为何匹配或不匹配请求的资源路径。

Policy-as-Code (v2)

策略即代码(v2)

Declare groups, roles, and scoped bindings in
.eve/access.yaml
. Use
version: 2
:
yaml
version: 2
access:
  groups:
    eng-team:
      name: Engineering Team
      description: Scoped access for engineering collaborators
      members:
        - type: user
          id: user_abc
  roles:
    app_editor:
      scope: org
      permissions:
        - orgdocs:read
        - orgdocs:write
        - orgfs:read
        - envdb:read
  bindings:
    - subject: { type: group, id: eng-team }
      roles: [app_editor]
      scope:
        orgdocs: { allow_prefixes: ["/groups/app/**"] }
        orgfs: { allow_prefixes: ["/groups/app/**"] }
        envdb: { schemas: ["app"] }
Validate, plan, and sync:
bash
eve access validate --file .eve/access.yaml
eve access plan --file .eve/access.yaml --org org_xxx
eve access sync --file .eve/access.yaml --org org_xxx
Sync is declarative: it creates, updates, and prunes groups, members, roles, and bindings to match the YAML. Invalid scope configurations fail fast before any mutations are applied. Binding subjects can be
user
,
service_principal
, or
group
.
.eve/access.yaml
中声明组、角色和范围化绑定,使用
version: 2
yaml
version: 2
access:
  groups:
    eng-team:
      name: Engineering Team
      description: Scoped access for engineering collaborators
      members:
        - type: user
          id: user_abc
  roles:
    app_editor:
      scope: org
      permissions:
        - orgdocs:read
        - orgdocs:write
        - orgfs:read
        - envdb:read
  bindings:
    - subject: { type: group, id: eng-team }
      roles: [app_editor]
      scope:
        orgdocs: { allow_prefixes: ["/groups/app/**"] }
        orgfs: { allow_prefixes: ["/groups/app/**"] }
        envdb: { schemas: ["app"] }
验证、规划并同步:
bash
eve access validate --file .eve/access.yaml
eve access plan --file .eve/access.yaml --org org_xxx
eve access sync --file .eve/access.yaml --org org_xxx
同步是声明式的:它会创建、更新和删除组、成员、角色和绑定,以匹配YAML配置。无效的范围配置会在任何变更应用前快速失败。绑定主体可以是
user
service_principal
group

Key Rotation

密钥轮换

Rotate the JWT signing key:
  1. Set
    EVE_AUTH_JWT_SECRET_NEW
    alongside the existing secret
  2. Server starts signing with the new key but accepts both during the grace period
  3. After grace period (
    EVE_AUTH_KEY_ROTATION_GRACE_HOURS
    ), remove the old secret
  4. Emergency rotation: set only the new key (immediately invalidates all existing tokens)
轮换JWT签名密钥:
  1. 在现有密钥旁设置
    EVE_AUTH_JWT_SECRET_NEW
  2. 服务器开始使用新密钥签名,但在宽限期内仍接受旧密钥
  3. 宽限期过后(
    EVE_AUTH_KEY_ROTATION_GRACE_HOURS
    ),移除旧密钥
  4. 紧急轮换:仅设置新密钥(立即使所有现有令牌失效)

App SSO Integration

应用SSO集成

Add Eve SSO login to any Eve-deployed app using two shared packages:
@eve-horizon/auth
(backend) and
@eve-horizon/auth-react
(frontend). The platform auto-injects
EVE_SSO_URL
,
EVE_ORG_ID
,
EVE_PROJECT_ID
, and
EVE_API_URL
into deployed services.
使用两个共享包为任何Eve部署的应用添加Eve SSO登录:
@eve-horizon/auth
(后端)和
@eve-horizon/auth-react
(前端)。平台会自动将
EVE_SSO_URL
EVE_ORG_ID
EVE_PROJECT_ID
EVE_API_URL
注入到部署的服务中。

Magic-Link Login Opt-In (Passwordless Apps)

魔法链接登录选择(无密码应用)

Apps can opt into passwordless browser login with
x-eve.auth.login_method: magic_link
. The SSO login page is branded for the project and shows email magic-link login instead of username/password.
yaml
x-eve:
  auth:
    login_method: magic_link              # or password_or_magic_link, password
    self_signup: false                    # unknown emails get generic success, no email
    invite_requires_password: false       # invite callback skips /set-password
Magic-link emails are sent by Eve API through
POST /auth/magic-link
(not GoTrue directly) so the platform can enforce project policy, share the
x-eve.branding
template with invite emails, and avoid account enumeration. Projects without
x-eve.auth
keep legacy SSO behavior. Create new users with
eve org invite <email> --org <org_id> --project <project_id>
.
应用可通过
x-eve.auth.login_method: magic_link
选择无密码浏览器登录。SSO登录页面会使用项目品牌,并显示邮箱魔法链接登录而非用户名/密码。
yaml
x-eve:
  auth:
    login_method: magic_link              # 或 password_or_magic_link, password
    self_signup: false                    # 未知邮箱会返回通用成功,不发送邮件
    invite_requires_password: false       # 邀请回调跳过/set-password页面
魔法链接邮件由Eve API通过
POST /auth/magic-link
发送(而非直接通过GoTrue),以便平台执行项目策略,与邀请邮件共享
x-eve.branding
模板,并避免账户枚举。未设置
x-eve.auth
的项目保留旧版SSO行为。使用
eve org invite <email> --org <org_id> --project <project_id>
创建新用户。

Magic-Link Confirmation Interstitial (Security)

魔法链接确认中间页(安全措施)

Eve-rendered magic-link and invite emails embed a wrap URL (
https://sso/m/mlw_<id>
), not the raw GoTrue verify URL. Email-security scanners (Defender SafeLinks, Mimecast, Proofpoint, Barracuda) follow every URL in mail and would otherwise consume single-use OTPs before the human clicks. The wrap renders a branded "Confirm sign-in / Accept invite" page; only the POST from the button reveals the GoTrue URL and 302-redirects. Treat this as a platform guarantee — no app-side work required.
Eve渲染的魔法链接和邀请邮件嵌入包装URL(
https://sso/m/mlw_<id>
),而非原始GoTrue验证URL。邮件安全扫描器(Defender SafeLinks、Mimecast、Proofpoint、Barracuda)会跟踪邮件中的每个URL,否则会在用户点击前消耗一次性OTP。包装页面会显示品牌化的"确认登录/接受邀请"页面;只有按钮的POST请求会暴露GoTrue URL并通过302重定向。这是平台提供的保障——无需应用端做任何工作。

Domain-Based Signup (Path C Auto-Attach)

基于域名的注册(Path C自动附加)

Pre-approve email domains so anyone with a matching address can sign in via magic link without a per-user invite. On first successful login the platform attaches them as
member
of the rule's
target_org
. One project can route different domains to different orgs.
yaml
x-eve:
  auth:
    login_method: magic_link
    invite_requires_password: false
    org_access:
      mode: allowlist
      allowed_orgs: [org_Acme, org_Partner, org_Retailer]
      domain_signup:
        enabled: true
        domains:
          - { domain: example.com,  target_org: org_Acme, role: member }
          - { domain: partner.example,    target_org: org_Partner }
          - { domain: retailer.example, target_org: org_Retailer }
Rules are walked in declaration order — first match wins, so declare more-specific patterns first. Each rule's
target_org
must appear in
allowed_orgs
. Declaring free-email providers (
free-mail.example
) is allowed but produces a manifest coherence warning. Explicit pending invites take priority over domain signup (Path B beats Path C). Removing a rule stops new signups but does not retroactively remove existing memberships — drop those with
eve org members remove
.
Audit via the event spine:
auth.domain_signup.invite_created
and
auth.domain_signup.member_attached
carry
org_id
,
matched_rule
, and
matched_domain
.
预先批准邮箱域名,使任何匹配地址的用户都可通过魔法链接登录,无需逐个用户邀请。首次登录成功后,平台会将他们作为规则
target_org
member
附加。一个项目可将不同域名路由到不同组织。
yaml
x-eve:
  auth:
    login_method: magic_link
    invite_requires_password: false
    org_access:
      mode: allowlist
      allowed_orgs: [org_Acme, org_Partner, org_Retailer]
      domain_signup:
        enabled: true
        domains:
          - { domain: example.com,  target_org: org_Acme, role: member }
          - { domain: partner.example,    target_org: org_Partner }
          - { domain: retailer.example, target_org: org_Retailer }
规则按声明顺序匹配——第一个匹配项生效,因此先声明更具体的模式。每个规则的
target_org
必须出现在
allowed_orgs
中。声明免费邮箱提供商(
free-mail.example
)是允许的,但会产生清单一致性警告。明确的待处理邀请优先于域名注册(Path B优于Path C)。移除规则会停止新注册,但不会追溯移除现有成员身份——使用
eve org members remove
删除。
通过事件 spine 审计:
auth.domain_signup.invite_created
auth.domain_signup.member_attached
包含
org_id
matched_rule
matched_domain

App Org Access and Admin Invites

应用组织访问和管理员邀请

Apps default to project-owner-org access. Use
mode: allowlist
to declare which customer orgs may use the app, and enable in-app admin invites that send branded magic-link onboarding:
yaml
x-eve:
  auth:
    org_access:
      mode: allowlist
      allowed_orgs: [org_customer123, customer-slug]
      invite:
        enabled: true
        admin_roles: [admin, owner]
        invited_role: member          # fixed; app invites cannot create admins
Endpoints:
GET /auth/app-access
returns the user's allowed orgs (plus which ones they can invite into);
POST /auth/app-invites
lets an org admin/owner invite a regular member with the project-branded email. For cross-org apps, use
eveAppUserAuth()
on the backend instead of
eveUserAuth()
— it consults
/auth/app-access
and selects the org from
X-Eve-Org-Id
,
?eve_org_id=
, or first allowed.
应用默认访问项目所属组织。使用
mode: allowlist
声明哪些客户组织可以使用应用,并启用应用内管理员邀请,发送品牌化魔法链接入职邮件:
yaml
x-eve:
  auth:
    org_access:
      mode: allowlist
      allowed_orgs: [org_customer123, customer-slug]
      invite:
        enabled: true
        admin_roles: [admin, owner]
        invited_role: member          # 固定;应用邀请无法创建管理员
端点:
GET /auth/app-access
返回用户允许访问的组织(以及他们可以邀请的组织);
POST /auth/app-invites
允许组织管理员/所有者使用项目品牌化邮件邀请普通成员。对于跨组织应用,在后端使用
eveAppUserAuth()
而非
eveUserAuth()
——它会查询
/auth/app-access
并从
X-Eve-Org-Id
?eve_org_id=
或第一个允许的组织中选择组织。

Project-Scoped Redirect Allowlist (Custom-Domain Apps)

项目范围的重定向允许列表(自定义域名应用)

The SSO broker only accepts redirect targets under the cluster domain by default. Apps deployed on their own domain must declare their origins:
yaml
x-eve:
  auth:
    allowed_redirect_origins:
      - https://app.example.com
      - https://www.example.com
Entries are origin-only (
scheme://host[:port]
); paths/queries/fragments are rejected at manifest-validate time. The final allowlist returned by
GET /auth/app-context
is the union of: (1) explicit manifest entries, (2) the project's own eligible custom domains (
custom_domains
rows with
environment_id
and status
dns_verified
/
cert_provisioning
/
active
), and (3) cross-org custom domains owned by projects in
allowed_orgs
. Inspect with
eve project auth-context <project_id>
.
This replaces the hard-coded
EVE_DEFAULT_DOMAIN
allowlist for non-cluster origins. The broker uses the list for both
redirect_to
validation in
/callback
and CORS on
/session
and
/logout
. The
@eve-horizon/auth-react
provider auto-passes
project_id
on session/logout calls so cross-site cookies are scoped correctly.
默认情况下,SSO代理仅接受集群域名下的重定向目标。部署在自有域名上的应用必须声明其源:
yaml
x-eve:
  auth:
    allowed_redirect_origins:
      - https://app.example.com
      - https://www.example.com
条目仅包含源(
scheme://host[:port]
);路径/查询/片段会在清单验证时被拒绝。
GET /auth/app-context
返回的最终允许列表是以下内容的并集:(1) 显式清单条目,(2) 项目自身符合条件的自定义域名(
custom_domains
行中带有
environment_id
且状态为
dns_verified
/
cert_provisioning
/
active
),以及(3)
allowed_orgs
中项目拥有的跨组织自定义域名。使用
eve project auth-context <project_id>
查看。
这取代了针对非集群源的硬编码
EVE_DEFAULT_DOMAIN
允许列表。代理会将此列表用于
/callback
中的
redirect_to
验证以及
/session
/logout
上的CORS。
@eve-horizon/auth-react
提供商会在会话/注销调用时自动传递
project_id
,以便跨站点cookie正确作用域。

SameSite=None on Custom Domains (Platform Guarantee)

自定义域名上的SameSite=None(平台保障)

When SSO is deployed with
EVE_SSO_SECURE_COOKIES=true
, the broker emits
eve_sso_rt
and
eve_sso
cookies with
Secure; SameSite=None
. This is required for the React provider's cross-site
fetch('/session', { credentials: 'include' })
probe to carry cookies when the app is on a custom domain. Local k3d (
http://*.lvh.me
) stays on
SameSite=Lax
. Apps no longer need to configure this themselves.
当SSO部署时设置
EVE_SSO_SECURE_COOKIES=true
,代理会发出带有
Secure; SameSite=None
eve_sso_rt
eve_sso
cookie。这是React提供商跨站点
fetch('/session', { credentials: 'include' })
探测在应用位于自定义域名时携带cookie所必需的。本地k3d(
http://*.lvh.me
)保持
SameSite=Lax
。应用无需自行配置此项。

Restrict Self-Signup to Approved Email Domains

将自助注册限制为批准的邮箱域名

The SSO service gates
/auth/signup
and
/auth/magiclink
by email domain when the env var
EVE_SIGNUP_ALLOWED_EMAIL_DOMAINS
is set (comma-separated). Unset means all domains are allowed (default). The signup tab on the SSO login page displays a domain hint when restrictions are active.
bash
undefined
当设置环境变量
EVE_SIGNUP_ALLOWED_EMAIL_DOMAINS
(逗号分隔)时,SSO服务会限制
/auth/signup
/auth/magiclink
的邮箱域名。未设置则允许所有域名(默认)。当限制生效时,SSO登录页面的注册标签会显示域名提示。
bash
undefined

On the SSO deployment, set:

在SSO部署中设置:

EVE_SIGNUP_ALLOWED_EMAIL_DOMAINS=acme.com,partner.io

Rejected requests return HTTP 422 with `error: email_domain_not_allowed`. Use this to keep public SSO endpoints invite-only-by-domain without disabling self-signup entirely. Existing accounts and admin invites are unaffected.
EVE_SIGNUP_ALLOWED_EMAIL_DOMAINS=acme.com,partner.io

被拒绝的请求会返回HTTP 422,包含`error: email_domain_not_allowed`。使用此功能可保持公共SSO端点按域名邀请,而无需完全禁用自助注册。现有账户和管理员邀请不受影响。

Backend (
@eve-horizon/auth
)

后端(
@eve-horizon/auth

Install:
npm install @eve-horizon/auth
Use the unified middleware by default for new apps:
ExportBehavior
eveAuth()
Non-blocking middleware. Verifies user or agent tokens and attaches normalized identity at
req.eveIdentity
.
eveIdentityGuard()
Returns 401 if
req.eveIdentity
is not set. Place on protected routes.
eveAuthConfig()
Handler returning
{ sso_url, eve_api_url, ... }
from auto-injected env vars. Frontend fetches this to discover SSO.
eveAuthMe()
/auth/me
handler for the React SDK and custom clients.
Keep the legacy split middleware only for apps that explicitly want user-only or agent-only handling:
ExportBehavior
eveUserAuth()
User-only non-blocking middleware. Attaches
req.eveUser: { id, email, orgId, role }
.
eveAuthGuard()
Returns 401 if
req.eveUser
is not set.
eveAuthMiddleware()
Blocking middleware for agent/job tokens. Attaches
req.agent
with full
EveTokenClaims
. Returns 401 on failure.
verifyEveToken(token)
JWKS-based local verification (15-min cache). Returns
EveTokenClaims
.
verifyEveTokenRemote(token)
HTTP verification via
/auth/token/verify
. Always current.
Express setup (~3 lines):
typescript
import { eveAuth, eveIdentityGuard, eveAuthConfig, eveAuthMe } from '@eve-horizon/auth';

app.use(eveAuth());
app.get('/auth/config', eveAuthConfig());
app.get('/auth/me', eveAuthMe());               // Full response for React SDK
app.use('/api', eveIdentityGuard());
req.eveIdentity
normalizes both token types:
  • User token:
    id
    ,
    email
    ,
    orgId
    ,
    role
    ,
    permissions
    ,
    isAgent: false
  • Agent/job token:
    jobId
    ,
    agentSlug
    , stable
    email
    as
    {agent_slug}@eve.agent
    ,
    permissions
    ,
    isAgent: true
Use
agentSlug
or the stable agent email for RLS, audit logs, and app-level routing. Do not key agent identity off
{job_id}@eve.agent
; that older pattern was per-job and unstable.
NestJS setup: apply
eveAuth()
globally in
main.ts
, then use a thin guard wrapper:
typescript
// main.ts
import { eveAuth } from '@eve-horizon/auth';
app.use(eveAuth());

// auth.guard.ts -- thin NestJS adapter
@Injectable()
export class EveGuard implements CanActivate {
  canActivate(ctx: ExecutionContext): boolean {
    const req = ctx.switchToHttp().getRequest();
    if (!req.eveIdentity) throw new UnauthorizedException();
    return true;
  }
}

// auth-config.controller.ts
@Controller()
export class AuthConfigController {
  private handler = eveAuthConfig();

  @Get('auth/config')
  getConfig(@Req() req, @Res() res) { this.handler(req, res); }
}
Verification strategies:
eveAuth()
and
eveUserAuth()
default to
'local'
(JWKS, cached 15 min). Use
strategy: 'remote'
for immediate membership freshness at ~50ms latency per request.
Custom role mapping: If your app needs roles beyond Eve's
owner/admin/member
, bridge after
eveAuth()
:
typescript
app.use((req, _res, next) => {
  if (req.eveIdentity && !req.eveIdentity.isAgent) {
    req.user = {
      ...req.eveIdentity,
      appRole: req.eveIdentity.role === 'member' ? 'viewer' : 'admin',
    };
  }
  next();
});
安装:
npm install @eve-horizon/auth
默认使用统一中间件用于新应用:
导出项行为
eveAuth()
非阻塞中间件。验证用户或Agent令牌,并将标准化身份附加到
req.eveIdentity
eveIdentityGuard()
如果
req.eveIdentity
未设置,则返回401。放置在受保护路由上。
eveAuthConfig()
处理程序,从自动注入的环境变量返回
{ sso_url, eve_api_url, ... }
。前端会获取此信息以发现SSO。
eveAuthMe()
/auth/me
处理程序,供React SDK和自定义客户端使用。
仅对于明确需要仅用户或仅Agent处理的应用,保留旧版拆分中间件:
导出项行为
eveUserAuth()
仅用户的非阻塞中间件。将
req.eveUser: { id, email, orgId, role }
附加到请求。
eveAuthGuard()
如果
req.eveUser
未设置,则返回401。
eveAuthMiddleware()
针对Agent/作业令牌的阻塞中间件。将带有完整
EveTokenClaims
req.agent
附加到请求。失败时返回401。
verifyEveToken(token)
基于JWKS的本地验证(缓存15分钟)。返回
EveTokenClaims
verifyEveTokenRemote(token)
通过
/auth/token/verify
进行HTTP验证。始终保持最新。
Express设置(约3行):
typescript
import { eveAuth, eveIdentityGuard, eveAuthConfig, eveAuthMe } from '@eve-horizon/auth';

app.use(eveAuth());
app.get('/auth/config', eveAuthConfig());
app.get('/auth/me', eveAuthMe());               // 供React SDK使用的完整响应
app.use('/api', eveIdentityGuard());
req.eveIdentity
标准化两种令牌类型:
  • 用户令牌:
    id
    email
    orgId
    role
    permissions
    isAgent: false
  • Agent/作业令牌:
    jobId
    agentSlug
    、稳定的
    email
    格式为
    {agent_slug}@eve.agent
    permissions
    isAgent: true
使用
agentSlug
或稳定的Agent邮箱用于RLS、审计日志和应用级路由。不要使用
{job_id}@eve.agent
作为Agent身份标识;旧模式是每个作业的,不稳定。
NestJS设置:在
main.ts
中全局应用
eveAuth()
,然后使用轻量防护包装器:
typescript
// main.ts
import { eveAuth } from '@eve-horizon/auth';
app.use(eveAuth());

// auth.guard.ts -- 轻量NestJS适配器
@Injectable()
export class EveGuard implements CanActivate {
  canActivate(ctx: ExecutionContext): boolean {
    const req = ctx.switchToHttp().getRequest();
    if (!req.eveIdentity) throw new UnauthorizedException();
    return true;
  }
}

// auth-config.controller.ts
@Controller()
export class AuthConfigController {
  private handler = eveAuthConfig();

  @Get('auth/config')
  getConfig(@Req() req, @Res() res) { this.handler(req, res); }
}
验证策略
eveAuth()
eveUserAuth()
默认使用
'local'
(JWKS,缓存15分钟)。使用
strategy: 'remote'
可立即获取成员身份最新状态,每次请求延迟约50ms。
自定义角色映射:如果应用需要Eve的
owner/admin/member
之外的角色,在
eveAuth()
之后进行桥接:
typescript
app.use((req, _res, next) => {
  if (req.eveIdentity && !req.eveIdentity.isAgent) {
    req.user = {
      ...req.eveIdentity,
      appRole: req.eveIdentity.role === 'member' ? 'viewer' : 'admin',
    };
  }
  next();
});

Frontend (
@eve-horizon/auth-react
)

前端(
@eve-horizon/auth-react

Install:
npm install @eve-horizon/auth-react
ExportPurpose
EveAuthProvider
Context provider. Bootstraps session: checks sessionStorage, probes SSO
/session
, caches tokens.
useEveAuth()
Hook:
{ user, loading, error, config, loginWithSso, loginWithToken, logout }
EveLoginGate
Renders children when authenticated, login form otherwise.
EveLoginForm
Built-in SSO + token-paste login UI.
createEveClient(baseUrl?)
Fetch wrapper with automatic Bearer injection.
Simple setup --
EveLoginGate
handles the loading/login/authenticated states:
tsx
import { EveAuthProvider, EveLoginGate } from '@eve-horizon/auth-react';

<EveAuthProvider apiUrl="/api">
  <EveLoginGate>
    <ProtectedApp />
  </EveLoginGate>
</EveAuthProvider>
Custom auth gate -- use
useEveAuth()
for full control over loading, login, and error states:
tsx
import { EveAuthProvider, useEveAuth } from '@eve-horizon/auth-react';

function AuthGate() {
  const { user, loading, loginWithToken, loginWithSso, logout } = useEveAuth();
  if (loading) return <Spinner />;
  if (!user) return <LoginPage onSso={loginWithSso} onToken={loginWithToken} />;
  return <App user={user} onLogout={logout} />;
}

export default () => (
  <EveAuthProvider apiUrl="/api">
    <AuthGate />
  </EveAuthProvider>
);
API calls with auth: Use
createEveClient()
for automatic Bearer token injection:
typescript
import { createEveClient } from '@eve-horizon/auth-react';
const client = createEveClient('/api');
const res = await client.fetch('/data');
安装:
npm install @eve-horizon/auth-react
导出项用途
EveAuthProvider
上下文提供程序。引导会话:检查sessionStorage,探测SSO
/session
,缓存令牌。
useEveAuth()
Hook:
{ user, loading, error, config, loginWithSso, loginWithToken, logout }
EveLoginGate
已认证时渲染子组件,否则显示登录表单。
EveLoginForm
内置SSO + 令牌粘贴登录UI。
createEveClient(baseUrl?)
Fetch包装器,自动注入Bearer令牌。
简单设置——
EveLoginGate
处理加载/登录/已认证状态:
tsx
import { EveAuthProvider, EveLoginGate } from '@eve-horizon/auth-react';

<EveAuthProvider apiUrl="/api">
  <EveLoginGate>
    <ProtectedApp />
  </EveLoginGate>
</EveAuthProvider>
自定义认证门——使用
useEveAuth()
完全控制加载、登录和错误状态:
tsx
import { EveAuthProvider, useEveAuth } from '@eve-horizon/auth-react';

function AuthGate() {
  const { user, loading, loginWithToken, loginWithSso, logout } = useEveAuth();
  if (loading) return <Spinner />;
  if (!user) return <LoginPage onSso={loginWithSso} onToken={loginWithToken} />;
  return <App user={user} onLogout={logout} />;
}

export default () => (
  <EveAuthProvider apiUrl="/api">
    <AuthGate />
  </EveAuthProvider>
);
带认证的API调用:使用
createEveClient()
自动注入Bearer令牌:
typescript
import { createEveClient } from '@eve-horizon/auth-react';
const client = createEveClient('/api');
const res = await client.fetch('/data');

Migration from Custom Auth

从自定义认证迁移

The SDK replaces ~700-800 lines of hand-rolled auth with ~50 lines. Delete custom JWKS/token verification, Bearer extraction middleware, SSO URL discovery, session probe logic, token storage helpers, and login form. Keep app-specific role mapping and local password auth.
For the full migration checklist, types reference, token lifecycle, and advanced patterns (SSE auth, token paste mode, token staleness), see references/app-sso-integration.md.
SDK用约50行代码取代了约700-800行手动编写的认证代码。删除自定义JWKS/令牌验证、Bearer提取中间件、SSO URL发现、会话探测逻辑、令牌存储助手和登录表单。保留应用特定的角色映射和本地密码认证。
有关完整的迁移清单、类型参考、令牌生命周期和高级模式(SSE认证、令牌粘贴模式、令牌过期),请参阅references/app-sso-integration.md

Service Tokens for Deployed Services

部署服务的服务令牌

Every deployed service receives an auto-injected
EVE_SERVICE_TOKEN
(90-day RS256 JWT,
type: service
) for server-to-server calls back into the Eve API. The deployer mints it on each deploy — apps no longer need to manually set this secret.
Tokens default to read-only permissions (
projects:read
,
jobs:read
,
threads:read
,
envs:read
,
secrets:read
,
builds:read
,
pipelines:read
,
agents:read
,
events:read
). Apps that need write access declare additional permissions explicitly in the manifest:
yaml
services:
  api:
    x-eve:
      permissions: [jobs:write, events:write, threads:write]
Use this for app -> Eve API calls (creating jobs, emitting events, updating threads). For the full schema and call patterns, see eve-read-eve-docs/references/secrets-auth.md and eve-read-eve-docs/references/manifest.md.
每个部署的服务都会收到自动注入的
EVE_SERVICE_TOKEN
(90天RS256 JWT,
type: service
),用于服务器到服务器调用Eve API。部署程序会在每次部署时生成它——应用无需手动设置此密钥。
令牌默认拥有只读权限(
projects:read
jobs:read
threads:read
envs:read
secrets:read
builds:read
pipelines:read
agents:read
events:read
)。需要写入权限的应用在清单中显式声明额外权限:
yaml
services:
  api:
    x-eve:
      permissions: [jobs:write, events:write, threads:write]
用于应用 -> Eve API调用(创建作业、发送事件、更新线程)。有关完整模式和调用模式,请参阅eve-read-eve-docs/references/secrets-auth.mdeve-read-eve-docs/references/manifest.md

BYOK Model (LLM API Keys)

BYOK模型(LLM API密钥)

Eve does not proxy inference traffic. All model access is BYOK (Bring Your Own Keys): harnesses and apps bring their own API keys via secrets and call providers directly.
Store LLM provider keys as project secrets:
bash
eve secrets set ANTHROPIC_API_KEY "sk-ant-xxx" --project proj_xxx
eve secrets set OPENAI_API_KEY "sk-xxx" --project proj_xxx
eve secrets set OPENAI_BASE_URL "https://my-vllm.runpod.ai/v1" --project proj_xxx
Harnesses resolve these automatically. For self-hosted models (vLLM, LM Studio via Tailscale), set the base URL and API key as secrets -- Eve provides connectivity via private endpoints (see
eve-deploy-debugging
), not a managed inference layer.
Eve不代理推理流量。所有模型访问均采用BYOK(自带密钥)模式:harness和应用通过密钥携带自己的API密钥,直接调用提供商。
将LLM提供商密钥存储为项目密钥:
bash
eve secrets set ANTHROPIC_API_KEY "sk-ant-xxx" --project proj_xxx
eve secrets set OPENAI_API_KEY "sk-xxx" --project proj_xxx
eve secrets set OPENAI_BASE_URL "https://my-vllm.runpod.ai/v1" --project proj_xxx
Harness会自动解析这些密钥。对于自托管模型(vLLM、通过Tailscale的LM Studio),将基础URL和API密钥设置为密钥——Eve通过私有端点提供连接(参见
eve-deploy-debugging
),而非托管推理层。

Per-Org OAuth Credentials (BYOA)

每个组织的OAuth凭证(BYOA)

Each org brings its own OAuth app credentials for Google Drive, Slack, and other integrations. No cluster-level shared secrets.
bash
undefined
每个组织都为Google Drive、Slack和其他集成带来自己的OAuth应用凭证。无集群级共享密钥。
bash
undefined

View setup instructions (redirect URIs, required scopes)

查看设置说明(重定向URI、所需范围)

eve integrations setup-info google-drive eve integrations setup-info slack
eve integrations setup-info google-drive eve integrations setup-info slack

Register OAuth app credentials

注册OAuth应用凭证

eve integrations configure google-drive
--client-id "xxx.apps.googleusercontent.com"
--client-secret "GOCSPX-xxx"
--label "Acme Corp Google Drive"
eve integrations configure slack
--client-id "12345.67890"
--client-secret "abc123"
--signing-secret "def456"
--app-id "A0123ABC"
--label "Acme Corp Slack Bot"
eve integrations configure google-drive
--client-id "xxx.apps.googleusercontent.com"
--client-secret "GOCSPX-xxx"
--label "Acme Corp Google Drive"
eve integrations configure slack
--client-id "12345.67890"
--client-secret "abc123"
--signing-secret "def456"
--app-id "A0123ABC"
--label "Acme Corp Slack Bot"

View current config (secrets redacted)

查看当前配置(密钥已脱敏)

eve integrations config google-drive
eve integrations config google-drive

Then connect as before (uses per-org credentials)

然后像之前一样连接(使用每个组织的凭证)

eve integrations connect google-drive eve integrations connect slack

Benefits: isolated credentials per org, custom consent screen branding, independent rate limits, no shared-secret blast radius.
eve integrations connect google-drive eve integrations connect slack

优势:每个组织的凭证隔离、自定义同意屏幕品牌、独立速率限制、无共享密钥爆炸半径。

Project Role Resolution

项目角色解析

Role and org membership changes take effect immediately -- the server resolves permissions from live DB memberships, not stale JWT claims. When a request includes a
project_id
but no
org_id
, the permission guard derives the org context from the project's owning org.
The Auth SDK (
@eve-horizon/auth
) exposes this via
eveUserAuth()
middleware. Use
strategy: 'remote'
for immediate membership freshness when needed.
角色和组织成员身份变更会立即生效——服务器从实时数据库成员关系解析权限,而非过期的JWT声明。当请求包含
project_id
但无
org_id
时,权限防护会从项目所属组织推导组织上下文。
Auth SDK(
@eve-horizon/auth
)通过
eveUserAuth()
中间件暴露此功能。必要时使用
strategy: 'remote'
获取即时成员身份最新状态。

Project Secrets

项目密钥

bash
undefined
bash
undefined

Set a secret

设置密钥

eve secrets set API_KEY "your-api-key" --project proj_xxx
eve secrets set API_KEY "your-api-key" --project proj_xxx

List keys (no values)

列出密钥(无值)

eve secrets list --project proj_xxx
eve secrets list --project proj_xxx

Delete a secret

删除密钥

eve secrets delete API_KEY --project proj_xxx
eve secrets delete API_KEY --project proj_xxx

Import from file

从文件导入

eve secrets import .env --project proj_xxx
undefined
eve secrets import .env --project proj_xxx
undefined

Secret Interpolation

密钥插值

Reference secrets in
.eve/manifest.yaml
using
${secret.KEY}
:
yaml
services:
  api:
    environment:
      API_KEY: ${secret.API_KEY}
.eve/manifest.yaml
中使用
${secret.KEY}
引用密钥:
yaml
services:
  api:
    environment:
      API_KEY: ${secret.API_KEY}

Manifest Validation

清单验证

Validate that all required secrets are set before deploying:
bash
eve manifest validate --validate-secrets    # check secret references
eve manifest validate --strict              # fail on missing secrets
部署前验证所有必需密钥已设置:
bash
eve manifest validate --validate-secrets    # 检查密钥引用
eve manifest validate --strict              # 缺少密钥则失败

Local Secrets File

本地密钥文件

For local development, create
.eve/dev-secrets.yaml
(gitignored):
yaml
secrets:
  default:
    API_KEY: local-dev-key
    DB_PASSWORD: local-password
  staging:
    DB_PASSWORD: staging-password
对于本地开发,创建
.eve/dev-secrets.yaml
(已加入git忽略):
yaml
secrets:
  default:
    API_KEY: local-dev-key
    DB_PASSWORD: local-password
  staging:
    DB_PASSWORD: staging-password

Worker Injection

工作节点注入

At job execution time, resolved secrets are injected as environment variables into the worker container. File-type secrets are written to disk and referenced via
EVE_SECRETS_FILE
. The file is removed after the agent process reads it.
作业执行时,解析后的密钥会作为环境变量注入到工作节点容器中。文件类型密钥会写入磁盘,并通过
EVE_SECRETS_FILE
引用。Agent进程读取后会删除该文件。

Git Auth

Git认证

The worker uses secrets for repository access:
  • HTTPS:
    github_token
    secret →
    Authorization: Bearer
    header
  • SSH:
    ssh_key
    secret → written to
    ~/.ssh/
    and used via
    GIT_SSH_COMMAND
工作节点使用密钥访问仓库:
  • HTTPS
    github_token
    密钥 →
    Authorization: Bearer
  • SSH
    ssh_key
    密钥 → 写入
    ~/.ssh/
    并通过
    GIT_SSH_COMMAND
    使用

Auth Mail Delivery (SES)

认证邮件投递(SES)

All branded auth emails (org/app invites, app-scoped magic-link, system-admin Supabase invites) flow through a single
MailerService
. When SMTP points at SES (
GOTRUE_SMTP_HOST=*.amazonaws.com
or
EVE_MAILER_CHECK_SUPPRESSION=true
), the mailer adds a pre-flight
GetSuppressedDestination
check so account-level suppressions cannot silently look like a successful send.
OutcomeBehavior
Address suppressedThrows
EmailSuppressedError
; no SMTP send
Not foundSend proceeds
AWS error (IRSA, throttling, network)Fails open — logs
mailer.suppression_check_failed
, send proceeds
Caller behavior:
sendEligibleMagicLink
swallows
EmailSuppressedError
and returns generic success (preserves account-enumeration defense), logging
mail.suppressed_drop
. Invite paths re-throw so admins see the error.
When
EVE_SES_CONFIGURATION_SET
is set, SES routes Bounce/Complaint/Delivery/Reject events to SNS, which POSTs to
/webhooks/ses-feedback
. The webhook verifies SNS signature, checks
TopicArn
against
EVE_SES_FEEDBACK_TOPIC_ARN
, and persists one row per affected recipient in
email_delivery_events
(idempotent by
sha256(snsMessageId|eventType|recipient)
).
Inspect events via the admin CLI:
bash
eve admin email bounces list
eve admin email bounces list --recipient user@example.com
eve admin email bounces list --event-type Bounce --limit 100 --json
Read-only from the local table; does not mutate SES. To clear an account-level suppression, see the SES suppression runbook.
Structured log events to grep in API pod logs:
mailer.sent
,
mailer.smtp_failed
,
mailer.suppressed
,
mailer.suppression_check_failed
,
mail.suppressed_drop
,
sns.subscription_confirmed
,
sns.rejected
,
ses.feedback_persisted
.
所有品牌化认证邮件(组织/应用邀请、应用范围的魔法链接、系统管理员Supabase邀请)都通过单个
MailerService
发送。当SMTP指向SES(
GOTRUE_SMTP_HOST=*.amazonaws.com
EVE_MAILER_CHECK_SUPPRESSION=true
)时,邮件服务会添加预飞行
GetSuppressedDestination
检查,以便账户级抑制不会被误认为发送成功。
结果行为
地址被抑制抛出
EmailSuppressedError
;不发送SMTP邮件
未找到继续发送
AWS错误(IRSA、限流、网络)开放失败——记录
mailer.suppression_check_failed
,继续发送
调用者行为:
sendEligibleMagicLink
会吞掉
EmailSuppressedError
并返回通用成功(保留账户枚举防御),记录
mail.suppressed_drop
。邀请路径会重新抛出错误,以便管理员看到。
当设置
EVE_SES_CONFIGURATION_SET
时,SES会将退回/投诉/投递/拒绝事件路由到SNS,SNS会POST到
/webhooks/ses-feedback
。Webhook会验证SNS签名,检查
TopicArn
是否与
EVE_SES_FEEDBACK_TOPIC_ARN
匹配,并在
email_delivery_events
中为每个受影响的收件人持久化一行记录(通过
sha256(snsMessageId|eventType|recipient)
实现幂等)。
通过管理员CLI查看事件:
bash
eve admin email bounces list
eve admin email bounces list --recipient user@example.com
eve admin email bounces list --event-type Bounce --limit 100 --json
从本地表只读访问;不修改SES。要清除账户级抑制,请参阅SES抑制运行手册。
在API pod日志中可 grep 的结构化日志事件:
mailer.sent
mailer.smtp_failed
mailer.suppressed
mailer.suppression_check_failed
mail.suppressed_drop
sns.subscription_confirmed
sns.rejected
ses.feedback_persisted

Troubleshooting

故障排除

ProblemFix
Not authenticatedRun
eve auth login
Token expiredRe-run
eve auth login
(tokens auto-refresh if within 5 min of expiry)
Bootstrap already completedUse
eve auth login
(existing user) or
eve admin invite
(new users). On non-prod stacks,
eve auth bootstrap
auto-attempts server recovery. For wrong-email recovery:
eve auth bootstrap --email correct@example.com
Secret missingConfirm with
eve secrets list
and set the key
Interpolation errorVerify
${secret.KEY}
spelling; run
eve manifest validate --validate-secrets
Git clone failedCheck
github_token
or
ssh_key
secret is set
Service can't reach APIVerify
EVE_API_URL
is injected (check
eve env show
)
Scoped access deniedRun
eve access explain <permission> <resource> --org <org>
to see scope match details. Check that the binding's scope constraints include the target path/schema. Built-in roles (owner/admin/member) carry wildcard
envdb
scope, so envdb denial for those roles points at the permission set, not missing scope
Wrong role shownRole is resolved from live DB memberships. Run
eve auth permissions
to see effective role. If multi-org, check
eve auth status
for per-org membership listing
Short-lived Claude token in jobsRun
eve auth creds
to check token type. If
oauth
(not
setup-token
), regenerate with
claude setup-token
then re-sync with
eve auth sync
Codex token expired between jobsAutomatic write-back should refresh it. If not, re-run
eve auth sync
. Check that
~/.codex/auth.json
or
~/.code/auth.json
has a fresh token
App SSO not workingVerify
EVE_SSO_URL
is injected (
eve env show
). For local dev, set
EVE_SSO_URL
,
EVE_ORG_ID
, and
EVE_API_URL
manually
Stale org membership in app tokensDefault 1-day TTL. Use
strategy: 'remote'
in
eveUserAuth()
for immediate membership checks
问题解决方法
未认证运行
eve auth login
令牌过期重新运行
eve auth login
(令牌在过期前5分钟内会自动刷新)
引导已完成使用
eve auth login
(现有用户)或
eve admin invite
(新用户)。在非生产环境中,
eve auth bootstrap
会自动尝试服务器恢复。错误邮箱恢复:
eve auth bootstrap --email correct@example.com
密钥缺失使用
eve secrets list
确认并设置密钥
插值错误验证
${secret.KEY}
拼写;运行
eve manifest validate --validate-secrets
Git克隆失败检查
github_token
ssh_key
密钥已设置
服务无法访问API验证
EVE_API_URL
已注入(检查
eve env show
范围化访问被拒绝运行
eve access explain <permission> <resource> --org <org>
查看范围匹配详情。检查绑定的范围约束是否包含目标路径/模式。内置角色(owner/admin/member)带有通配符
envdb
范围,因此这些角色的envdb拒绝指向权限集,而非缺少范围
显示错误角色角色从实时数据库成员关系解析。运行
eve auth permissions
查看有效角色。如果是多组织,检查
eve auth status
的每个组织成员列表
作业中使用短期Claude令牌运行
eve auth creds
检查令牌类型。如果是
oauth
(非
setup-token
),使用
claude setup-token
重新生成,然后使用
eve auth sync
重新同步
作业间Codex令牌过期自动回写应刷新它。如果没有,重新运行
eve auth sync
。检查
~/.codex/auth.json
~/.code/auth.json
是否有新鲜令牌
应用SSO不工作验证
EVE_SSO_URL
已注入(
eve env show
)。本地开发时,手动设置
EVE_SSO_URL
EVE_ORG_ID
EVE_API_URL
应用令牌中的组织成员身份过期默认1天有效期。在
eveUserAuth()
中使用
strategy: 'remote'
进行即时成员身份检查

Incident Response (Secret Leak)

事件响应(密钥泄露)

If a secret may be compromised:
  1. Contain: Rotate the secret immediately via
    eve secrets set
  2. Invalidate: Redeploy affected environments
  3. Audit: Check
    eve job list
    for recent jobs that used the secret
  4. Recover: Generate new credentials at the source (GitHub, AWS, etc.)
  5. Document: Record the incident and update rotation procedures
如果密钥可能已泄露:
  1. 遏制:立即通过
    eve secrets set
    轮换密钥
  2. 失效:重新部署受影响的环境
  3. 审计:检查
    eve job list
    查看最近使用该密钥的作业
  4. 恢复:在源端生成新凭证(GitHub、AWS等)
  5. 记录:记录事件并更新轮换流程