feat: self-service worker enrollment bound to a user account
Let a signed-in user turn their own machine into a worker without the shared token. The coordinator already binds a JWT-authenticated registration to owner_id as untrusted; this adds the missing pieces. userservice: long-lived worker keys (scimesh_wk_live_*, hash-at-rest) with create/list/revoke and a public /worker-tokens/exchange that trades a key for a short-lived JWT carrying the owner current role/verified. python worker: SCIMESH_WORKER_KEY + SCIMESH_USERSERVICE_URL; a token provider exchanges the key and refreshes the JWT proactively and on 401, so a long-running worker survives token expiry. Static bearer token path is unchanged. coordinator UI: an "add your machine" page that mints a key and shows a ready-to-run command, proxying key management to the userservice; the dashboard gains an owner-scoped "my machines" section. docs: how to run a worker from your account, plus the untrusted/quorum/ verified trust model.
This commit is contained in:
@@ -41,3 +41,53 @@ func toUserResponse(u *domain.User) userResponse {
|
||||
CreatedAt: u.CreatedAt.UTC().Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
// createWorkerKeyRequest is the body for minting a worker key. Name is an
|
||||
// optional human label; the domain defaults it when blank.
|
||||
type createWorkerKeyRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// exchangeWorkerKeyRequest trades a worker key for a short-lived JWT.
|
||||
type exchangeWorkerKeyRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type exchangeWorkerKeyResponse struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
// workerKeyResponse is the public view of a key. It never carries the secret —
|
||||
// only the non-secret prefix used to identify a row.
|
||||
type workerKeyResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Prefix string `json:"prefix"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
LastUsedAt string `json:"last_used_at,omitempty"`
|
||||
}
|
||||
|
||||
// createdWorkerKeyResponse extends the public view with the one-time plaintext,
|
||||
// returned only from the create call and never again.
|
||||
type createdWorkerKeyResponse struct {
|
||||
workerKeyResponse
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type workerKeysResponse struct {
|
||||
WorkerKeys []workerKeyResponse `json:"worker_keys"`
|
||||
}
|
||||
|
||||
func toWorkerKeyResponse(k *domain.WorkerKey) workerKeyResponse {
|
||||
resp := workerKeyResponse{
|
||||
ID: k.ID.String(),
|
||||
Name: k.Name,
|
||||
Prefix: k.Prefix,
|
||||
CreatedAt: k.CreatedAt.UTC().Format(time.RFC3339),
|
||||
}
|
||||
if k.LastUsedAt != nil {
|
||||
resp.LastUsedAt = k.LastUsedAt.UTC().Format(time.RFC3339)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user