Devices and auth
A device's cloud identity is an Ed25519 signing key plus an X25519 box key, nothing else: no student ID, no name. Registration costs a proof of work; plugin tokens are minted from a signed challenge and live 10 minutes.
Registers a device identity. Authenticated by proof of work instead of a token: find a
decimal nonce such that SHA-256(prefix ‖ UTF-8(nonce)) starts with at least
pow.difficulty zero bits (from GET /v1/meta), where
prefix = UTF-8("pku-harness-cloud/v1/pow\n" + salt + "\n" + signPub + "\n" + boxPub + "\n")The server accepts its current salt and the previous one. Binding the keys into the prefix
means one solution registers exactly one identity. Clients refuse a difficulty above 28
(MAX_POW_DIFFICULTY); the default is 16 (about one second on a phone). Use solvePow from
@pku-harness/cloud-protocol.
application/json- body
signPub*Key3232 bytes, base64url (Ed25519 or X25519 public key, hash).
^[A-Za-z0-9_-]{43}$boxPub*Key3232 bytes, base64url (Ed25519 or X25519 public key, hash).
^[A-Za-z0-9_-]{43}$pow*The server-generated device id.
application/json- response
deviceId*DeviceIdServer-generated, base64url.
^[A-Za-z0-9_-]{8,64}$curl -X POST 'https://cloud.example.org/v1/devices' \ -H 'Content-Type: application/json' \ -d '{ "signPub": "string", "boxPub": "string", "pow": { "salt": "string", "nonce": "string" }}'{ "deviceId": "string"}Exchanges an Ed25519-signed challenge for a 10-minute bearer token bound to one device, one
plugin and a set of services (claims sub = deviceId, plg = plugin, svc = services).
The signed bytes are these lines joined by \n (no trailing newline):
pku-harness-cloud/v1/auth<aud> provider origin, lowercase scheme://host[:port], no path<deviceId><plugin><services> deduplicated, sorted, joined by ","<ts> Unix ms, decimal<nonce> 16 random bytes, base64urlThe server compares aud with its own origin, accepts |now − ts| ≤ 5 min and rejects a
reused nonce within that window. Use signAuthChallenge from
@pku-harness/cloud-protocol.
application/json- body
aud*stringThe provider origin, lowercase, no path or trailing slash.
^https?:\/\/[a-z0-9.\-:[\]]+$deviceId*DeviceIdServer-generated, base64url.
^[A-Za-z0-9_-]{8,64}$plugin*PluginIdRegistry id rule, plus mcp-… host ids for remote plugins.
^[a-z0-9][a-z0-9-]{1,63}$services*array<>1 <= itemsts*MsUnix time in milliseconds.
0 <= valuenonce*Nonce1616 bytes, base64url.
^[A-Za-z0-9_-]{22}$sig*Sig6464-byte Ed25519 signature, base64url.
^[A-Za-z0-9_-]{86}$A bearer token for this plugin's requests.
application/json- response
token*stringOpaque to clients.
16 <= lengthexpiresAt*MsUnix time in milliseconds.
0 <= valuecurl -X POST 'https://cloud.example.org/v1/auth/token' \ -H 'Content-Type: application/json' \ -d '{ "aud": "string", "deviceId": "string", "plugin": "string", "services": [ "store" ], "ts": 0, "nonce": "string", "sig": "string"}'{ "token": "stringstringstri", "expiresAt": 0}Wipes every record of the device (store, channels, push schedules). Any plugin token of the device works. The app calls it from Settings → 删除云端数据 and before a full reset.
pluginTokenAuthorizationBearer <token>Token from POST /v1/auth/token (10 minutes, one device + one plugin + services). Channel owner endpoints require the token of the device and plugin that opened the channel.
Deleted.
application/json- response
ok*booleancurl -X DELETE 'https://cloud.example.org/v1/devices/me'{ "ok": true}