一行命令注册账号、拿 JWT,用于 devnull.cn 各服务间的身份验证。
账号与设备也可以在网页上管理:管理控制台(用
access_key + secret_key 登录,secret_key 只在浏览器里参与哈希)。
下载后执行 devnull-cli init 即可创建账号(工作量证明,不需要邮箱),
凭据写入 ~/.devnull/credentials(0600,与 Python SDK 共用同一份)。
下载 devnull-cli(linux/amd64 示例),一条命令完成注册并把凭证存到 ~/.devnull/credentials:
curl -fsSL https://files-1252667388.cos.ap-beijing.myqcloud.com/pub/devnull-cli-releases/latest/devnull-cli-linux-amd64 \
-o /usr/local/bin/devnull-cli && chmod +x /usr/local/bin/devnull-cli && devnull-cli init
其他平台把文件名换成对应的即可:
| 平台 | 文件名 |
|---|---|
| Linux amd64 | devnull-cli-linux-amd64 |
| Linux arm64 | devnull-cli-linux-arm64 |
| macOS Intel | devnull-cli-darwin-amd64 |
| macOS Apple Silicon | devnull-cli-darwin-arm64 |
| Windows | devnull-cli-windows-amd64.exe |
下载地址前缀:https://files-1252667388.cos.ap-beijing.myqcloud.com/pub/devnull-cli-releases/latest/
devnull-cli token
用 ~/.devnull/credentials 里保存的凭证签一个断言,换回 15 分钟的 EdDSA 访问令牌并打印。
其他常用子命令:devnull-cli whoami(看 account_id/角色/scope)、
devnull-cli rekey(1.x 老账号换一次 2.0 凭据)。
不再把派生密码发到服务端:客户端用 secret_key 签一个 60 秒、一次性、
aud 绑定本服务的 HS256 断言,换来 15 分钟的 EdDSA 访问令牌。secret_key 永不
上网;令牌由 JWKS 公钥在资源服务本地验签,资源服务不再持有任何共享密钥。
pip install theid
python3 - <<'EOF'
import theid
print(theid.Client().authorization_headers())
EOF
手工调用等价于(签名细节见 /identity/.well-known/identity-configuration):
curl -s -X POST https://devnull.cn/identity/oauth/token \
-d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
-d assertion=$ASSERTION \
-d audience=thedns \
-d 'scope=profile:read device:read'
{"access_token":"eyJ...","token_type":"Bearer","expires_in":900,"scope":"profile:read device:read","account_id":"016869757"}
资源服务验签只需要公钥:
pip install "theid[verify]"
from theid import JWKSClient
jwks = JWKSClient() # 公钥缓存 300 秒,未知 kid 自动刷新一次
claims = jwks.verify(token, audience='thedns', issuer='https://devnull.cn/identity')
老账号要先轮换一次(服务端只存了 SK 的哈希,无法反推)。用旧的
sha256(sha256(ak)+sha256(sk)) 作为密码换新的一对,旧凭据立即失效,新凭据只返回一次:
curl -s -X POST https://devnull.cn/identity/keys/rekey \
-H 'Content-Type: application/json' \
-d '{"access_key":"DK...","password":"<derived>"}'
令牌里的 scope 由角色推导、只能下放不能提权(user 是
profile:read,fe 再加 device:read 与
device:write);aud 决定这张令牌能用于哪些服务。老式 1 小时 HS256
令牌在迁移期内仍然有效。
访问其他 devnull.cn 服务时,带上拿到的 token:
Authorization: Bearer <token>
每个账号持有一个角色集合,自助注册默认是 {user}。判定时取集合里最高的那一档:
user < fe < admin < superadmin。服务端每次特权请求都重读数据库里的角色,
所以改角色、删账号立即生效,不用等 token 过期。
| 角色 | 能做什么 |
|---|---|
user | 基线角色,每个账号都有、不可移除:登录、拿 JWT 给其他 devnull.cn 服务用。设备接口一律 403(连设备列表都看不到)。 |
fe | 现场工程师:激活设备(提交配对码)、看自己的设备与待激活队列、改自己设备的 device_info、撤销自己的设备。别人的设备看不到也删不掉(返回 404 而非 403)。 |
admin | 看全部设备与账号;撤销/删除任意设备;删账号;只能增减他人的 fe。 |
superadmin | 增减 fe/admin/superadmin;不能改自己的角色;系统必须始终保留至少一个 superadmin。 |
角色只能被 admin/superadmin 授予(见下面的账号管理接口),所以现场工程师的
fe 需要找人加;设备激活同理,纯 user 提交配对码会拿到 403。
管理控制台是设备与账号的管理页(公开页面,自身不存任何凭据):登录后可以
激活设备、查看与撤销设备、编辑设备备注、管理账号角色。登录时浏览器用 secret_key
签一个 60 秒、一次性的断言再换 EdDSA 令牌,secret_key 不会离开浏览器;
换来的 JWT 只存 sessionStorage,关掉标签页即失效。页面上的按钮只是"能不能点",
真正的授权判定都在服务端(越权一律 403)。多个账号(比如既是 fe 又是
admin 的两套凭据)可以存在浏览器本机一键切换 —— 那是明文存
localStorage,只建议在自用机器上这么做。
面向客户现场的联网设备。设备首次接入时,由现场工程师用人类账号激活一次;之后设备拿着 自己的 PSK 换 JWT,续期不需要人参与。设备凭证与人类凭证完全独立:设备 token 不能用于 人类接口,人类密码泄露也不影响已激活的设备。
设备开机后注册,一次性拿到 pairing_code(显示在屏幕上给人看的)和
device_psk(存进设备安全存储,之后不再传输):
curl -s -X POST https://devnull.cn/identity/device/register \
-H 'Content-Type: application/json' \
-d '{"device_id":"rk3588-001","device_info":{"model":"RK3588","firmware":"1.0.0"}}'
{"pairing_code":"QCQA-636V","device_psk":"0Yh...","expires_in":600,"status":"pending"}
device_psk 只返回这一次,服务端只保留 Argon2id 哈希,丢了就重新注册。配对码
10 分钟内有效、只能用一次;确认之前 PSK 换不到 token。
工程师用人类账号登录取 JWT(devnull-cli token),把设备屏幕上的配对码提交上来。
任何 fe 及以上的人都能激活屏幕上正在显示的设备——现场语义就是"当班的工程师激活眼前的盒子":
curl -s -X POST https://devnull.cn/identity/device/confirm \
-H "Authorization: Bearer $HUMAN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"pairing_code":"QCQA-636V"}'
{"device_id":"rk3588-001","status":"active"}
这一步同时确定设备归属:设备属于执行确认的那个账号。换个账号确认,就是把设备转移给新账号。
OAuth2 client_credentials:client_id 是 device_id,
client_secret 是 device_psk。
curl -s -X POST https://devnull.cn/identity/token \
-d grant_type=client_credentials \
-d client_id=rk3588-001 \
-d client_secret=$DEVICE_PSK
{"access_token":"eyJ...","token_type":"Bearer","expires_in":3600}
token 有效期 1 小时,设备侧按时续期即可(比如还剩 5 分钟时续)。payload 里 sub
是 device:rk3588-001,principal_type 是 device,
owner 是归属账号。
# 我的设备(fe);?scope=pending 看待激活队列,?status= 过滤状态
curl -s 'https://devnull.cn/identity/devices?scope=own&status=active' \
-H "Authorization: Bearer $HUMAN_TOKEN"
# 编辑 device_info:浅合并,保留键 label / note
curl -s -X PATCH https://devnull.cn/identity/devices/rk3588-001 \
-H "Authorization: Bearer $HUMAN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"device_info":{"label":"3号机房","note":"靠窗那台"}}'
# 撤销(若还是未确认的注册,则直接丢掉那一行)
curl -s -X DELETE https://devnull.cn/identity/devices/rk3588-001 \
-H "Authorization: Bearer $HUMAN_TOKEN"
列表条目带 status、device_info、时间戳,待激活时还会带
code_expires_in;owner_user_id 只在 admin 及以上调用时返回
(fe 看自己名下的设备不需要它)。列表永远不返回 pairing_code:能读到码
就等于能激活别人看不见的设备,码只存在于设备屏幕上。
撤销是可恢复的:撤销后设备重新走一遍上面的流程(重新注册 → 新的配对码 → 重新确认)就能 恢复,不需要换硬件。撤销在设备下一次换 token 时生效,最长滞后一个 token 周期。
DELETE 对已激活设备只把它置为 revoked(幂等 204),那一行会作为
审计记录留下;只有未确认的 pending 行会被直接删掉。所以要让一台已撤销设备
彻底从库里消失(比如清理测试设备):再 register 一次让它回到 pending,
然后 DELETE 即可。同一台设备想重新上岗也走这条路径。
| 接口 | 说明 | 最低角色 | 状态码 |
|---|---|---|---|
POST /device/register | 设备注册 / 重新激活,返回配对码和 PSK(仅一次) | —(匿名) | 200 / 400 / 409 |
POST /device/confirm | 确认配对码,激活设备并确定归属 | fe | 200 / 400 / 401 / 403 / 404 / 410 |
POST /token | 设备用 device_id + PSK 换 JWT | —(匿名) | 200 / 400 / 401 / 403 |
GET /devices | 设备列表:?scope=own(默认)/ pending 待激活队列 / all 全部 | fe | 200 / 401 / 403 |
PATCH /devices/{device_id} | 浅合并 device_info | fe(自己的)/ admin(任意) | 200 / 400 / 401 / 403 / 404 |
DELETE /devices/{device_id} | 撤销设备,或清理未确认的注册 | fe(自己的)/ admin(任意) | 204 / 400 / 401 / 403 / 404 |
GET /me | 当前账号与角色集合(管理页首屏用) | user | 200 / 401 |
GET /users | 账号列表:account_id、角色、设备数 | admin | 200 / 401 / 403 |
POST /users/{account_id}/roles | 增减角色 {"add":[],"remove":[]}(user 不可动) | admin / superadmin | 200 / 400 / 401 / 403 / 404 |
DELETE /users/{account_id} | 删账号,名下设备一并转 revoked | admin | 204 / 401 / 403 / 404 |
401:凭证不对(不会暴露 device_id 是否存在,因此没有“设备不存在”这个错误);
403:凭证对但角色不够,或状态不允许(未确认 / 已撤销 / 拿设备 token 调人类接口);
404:配对码不存在,或设备/账号不属于你(越权也报 404,不确认别人手里有什么);
409:设备已激活,或已有未过期的配对码;410:配对码过期,重新注册即可。
设备 id 只允许字母、数字、.、_、-,最长 64 字符,
建议直接用出厂序列号(如 rk3588-001)。单个账号能注册多少设备不做限制。
# 账号列表:account_id、角色集合、设备数
curl -s https://devnull.cn/identity/users \
-H "Authorization: Bearer $ADMIN_TOKEN"
# 增减角色:增量式,user 不可加也不可删
curl -s -X POST https://devnull.cn/identity/users/123456789/roles \
-H "Authorization: Bearer $SUPERADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"add":["fe"],"remove":[]}'
# 删账号:名下设备一并转 revoked(保留记录),账号本身不可恢复
curl -s -X DELETE https://devnull.cn/identity/users/123456789 \
-H "Authorization: Bearer $ADMIN_TOKEN"
规则:任何人不能改自己的角色;admin 只能增减别人的 fe,也管不了
admin/superadmin;最后一个 superadmin 不能被降级或删除(403);
纯 user 账号由 admin 删除,它名下的设备会转 revoked,
需要 fe 重新激活才能回到可用状态。