跳到主要内容

用户注册与管理 API

Rocky 当前提供两个用户身份相关的 API:注册(/api/register)和登录(/api/auth)。两者都是 Next.js BFF 路由,背后写入 Postgres 的 auth.users 表 + 调用 Canton Validator 分配 party。

Token 模型基于 Canton JWT,由 Validator 签发。如果不熟悉 Party / Validator,请先阅读 Canton 网络介绍


一、概览

维度当前实现
认证模型Email + Password(bcrypt 哈希)
Token 类型Canton JWT(由 Validator 签发,与 party 绑定)
Session 存储浏览器 localStorage
用户表auth.users (Postgres)
Party 分配DevNet 走 Validator /v0/admin/users;LocalNet 走 Daml admin API

Rocky 的 auth.users 表与每个用户的 Canton Party 一一对应。注册时同时完成:

  1. 校验 email / password / username
  2. 调用 Validator 分配 Canton Party(hint = username)
  3. INSERT 到 auth.users(含 bcrypt 密码哈希)
  4. 返回 Canton JWT + party + email + user_id

后续所有需要身份的接口(perp 下单 / claim MTC 等)都通过 user_id 或 party 在 BFF 层验证。


二、POST /api/register — 注册

新建一个 Rocky 账户。

2.1 Request

POST /api/register
Content-Type: application/json

{
"email": "alice@example.com",
"password": "alice-secret-123",
"username": "alice"
}
字段类型必填规则
emailstring匹配 /^[^\s@]+@[^\s@]+\.[^\s@]+$/。大小写不敏感(存储原文,比较时 lower(email)
passwordstring长度 ≥ 8。无字符复杂度要求(DevNet 简化)
usernamestring匹配 /^[a-zA-Z0-9_-]{3,32}$/。作为 Canton party hint 使用

2.2 Response — 200 OK

{
"success": true,
"token": "eyJhbGciOiJSUzI1NiIs...",
"party": "alice::1220ca15423a1b049bf8e84132360f246057aa18f5e9e36a77db535b75bd287cceff",
"username": "alice",
"email": "alice@example.com",
"user_id": "df94d294-5319-49d6-ba8d-aa08094bc1e4"
}
字段含义
tokenCanton JWT。用于调用需要 user-act-as 的链上接口
party完整 Canton party id(前缀 = username,后缀 = participant 指纹)
user_idauth.users.user_id 主键(UUIDv4),后续所有 perp 接口的 user_id 参数都用这个

2.3 Response — 错误

HTTPerror message触发条件
400"valid email is required"email 缺失 / 格式不对
400"password must be at least 8 characters"password 缺失 / 长度 < 8
400"username must be 3-32 characters (letters, numbers, underscore, hyphen)"username 缺失 / 不匹配正则
409"an account with this email already exists"email 已存在(大小写不敏感)
409"username is already taken"username 已存在
500<canton validator error>Validator 分配 party 失败(DevNet)/ Daml admin API 失败(LocalNet)

2.4 Example

curl -s -X POST https://demo.rocky.exchange/api/register \
-H 'Content-Type: application/json' \
-d '{
"email": "alice@example.com",
"password": "alice-secret-123",
"username": "alice"
}' | jq

2.5 后台副作用

成功一次后:

  • auth.users 新增一行
  • Canton 上多了一个 party,UUIDv5(party) 是用户在 perp 系统中的 user_id 派生方式(前端 getPerpUserId()
  • 用户即可登录 / 下单 / 充值 CC / 触发 MTC 奖励

三、POST /api/auth — 登录

凭 email + password 换取 Canton JWT。

3.1 Request

POST /api/auth
Content-Type: application/json

{
"email": "alice@example.com",
"password": "alice-secret-123"
}
字段类型必填规则
emailstring大小写不敏感,按 lower(email) 查询
passwordstringbcrypt verify 与存储的 hash 比较

3.2 Response — 200 OK

{
"token": "eyJhbGciOiJSUzI1NiIs...",
"party": "alice::1220ca15423a1b049bf8e84132360f246057aa18f5e9e36a77db535b75bd287cceff",
"username": "alice",
"email": "alice@example.com",
"user_id": "df94d294-5319-49d6-ba8d-aa08094bc1e4"
}

注意:与 /api/register 不同的是,登录的 response 没有 success: true 字段(早期约定差异,未来会统一)。

3.3 Response — 错误

HTTPerror message触发条件
400"email is required"email 缺失
400"password is required"password 缺失
401"invalid email or password"email 不存在 OR password 错误(同一错误,故意不区分以避免邮箱枚举)
500<canton authToken error>Validator 签发 JWT 失败

3.4 Example

curl -s -X POST https://demo.rocky.exchange/api/auth \
-H 'Content-Type: application/json' \
-d '{
"email": "alice@example.com",
"password": "alice-secret-123"
}' | jq

3.5 安全注意

  • 错误统一为 "invalid email or password"不要泄露 email 是否存在。客户端不应根据 status code 区分两种情况
  • 当前没有速率限制 / IP 锁定(DevNet 简化),生产环境需补
  • 密码以 bcrypt 哈希存储(10 rounds),永不返回 / 永不日志

四、Token 与 localStorage

登录 / 注册成功后,前端会把 4 个值写入浏览器 localStorage:

keyvalue用途
mtc_tokenCanton JWT调用链上接口时附带
mtc_party完整 Canton party id链上合约的 user-act-as
mtc_usernameusername显示用
mtc_emailemail显示用

历史名前缀 mtc_* 沿用自旧品牌(MTC 即 Mining Token Credit),未做统一改名以避免破坏现有 session。

4.1 退出(Logout)

没有专门的 logout API。前端只需清掉这 4 个 localStorage 项 + 跳转到 /。Token 仍然在 Canton 那侧有效直到自然过期(典型 1 小时),但本地不再持有。

// TopNav.tsx — handleLogout
localStorage.removeItem("mtc_token");
localStorage.removeItem("mtc_party");
localStorage.removeItem("mtc_username");
localStorage.removeItem("mtc_email");
router.push("/");

4.2 Token 续期

Canton JWT 自然过期后用户会被服务端拒绝。当前没有 refresh token 机制——用户必须重新登录。计划改进:

  • POST /api/auth/refresh 接口,凭旧 token + user_id 换新 token
  • 或将 token 寿命延长到 7 天,配合敏感操作再次验证

五、数据存储

5.1 auth.users schema

参见 services/internal-ledger/migrations/20260525002_auth_users.sql

CREATE TABLE auth.users (
user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
username TEXT NOT NULL UNIQUE,
party TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX users_email_lower_idx ON auth.users (lower(email));

5.2 与 auth.api_keys 的关系

auth.api_keys 是另一张表(funnel 做市机器人用,HMAC 密钥)。两表完全独立:

  • auth.users — 网页登录用户
  • auth.api_keys — 程序化交易的 API key

互不影响。


六、规划中的接口

以下接口当前尚未实现,仅做规划:

接口方法用途
/api/auth/meGET用 token 换当前用户的完整信息(用于刷新页面后恢复 session)
/api/auth/refreshPOST用旧 token 换新 token,延长会话
/api/auth/passwordPUT修改密码(需要旧密码)
/api/auth/emailPUT修改邮箱(需要重新验证邮箱所有权 → 需要 SMTP)
/api/auth/forgot-passwordPOST"忘记密码"流程(需要 SMTP 发送 reset link)
/api/auth/logoutPOST服务端主动失效 token(需要 token revocation list)
/api/admin/usersGET / DELETE管理员后台:列出 / 禁用用户

实现时机:依赖 SMTP 接入(密码重置)+ token revocation 机制(admin 禁用 / 主动 logout)。


七、相关文档

  • Canton 网络介绍 — Party / Validator 概念
  • CC 充值流程 — user_id 在 perp 接口中的用法
  • MTC 挖矿奖励 — token + party 在挖矿场景的用法
  • 仓库源码:
    • mtc-exchange/src/app/api/register/route.ts
    • mtc-exchange/src/app/api/auth/route.ts
    • mtc-exchange/src/lib/users.ts
    • mtc-exchange/src/lib/passwords.ts
    • services/internal-ledger/migrations/20260525002_auth_users.sql