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:
@@ -14,23 +14,31 @@ import (
|
||||
|
||||
// UseCases bundles the application services the handlers drive.
|
||||
type UseCases struct {
|
||||
Register *usecase.Register
|
||||
Login *usecase.Login
|
||||
SetVerified *usecase.SetVerified
|
||||
SetRole *usecase.SetRole
|
||||
Users usecase.UserRepository
|
||||
Register *usecase.Register
|
||||
Login *usecase.Login
|
||||
SetVerified *usecase.SetVerified
|
||||
SetRole *usecase.SetRole
|
||||
CreateWorkerKey *usecase.CreateWorkerKey
|
||||
ListWorkerKeys *usecase.ListWorkerKeys
|
||||
RevokeWorkerKey *usecase.RevokeWorkerKey
|
||||
ExchangeWorkerKey *usecase.ExchangeWorkerKey
|
||||
Users usecase.UserRepository
|
||||
}
|
||||
|
||||
// NewServer wires the routes and the middleware stack and returns the handler.
|
||||
// The issuer verifies tokens for the JWT-protected routes.
|
||||
func NewServer(log *slog.Logger, uc UseCases, issuer auth.Issuer) http.Handler {
|
||||
h := &Handlers{
|
||||
register: uc.Register,
|
||||
login: uc.Login,
|
||||
setVerified: uc.SetVerified,
|
||||
setRole: uc.SetRole,
|
||||
users: uc.Users,
|
||||
log: log,
|
||||
register: uc.Register,
|
||||
login: uc.Login,
|
||||
setVerified: uc.SetVerified,
|
||||
setRole: uc.SetRole,
|
||||
createWorkerKey: uc.CreateWorkerKey,
|
||||
listWorkerKeys: uc.ListWorkerKeys,
|
||||
revokeWorkerKey: uc.RevokeWorkerKey,
|
||||
exchangeWorkerKey: uc.ExchangeWorkerKey,
|
||||
users: uc.Users,
|
||||
log: log,
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@@ -41,6 +49,14 @@ func NewServer(log *slog.Logger, uc UseCases, issuer auth.Issuer) http.Handler {
|
||||
// /me proves a token round-trips; it sits behind JWT auth.
|
||||
mux.Handle("GET /me", chain(http.HandlerFunc(h.handleMe), withJWT(issuer)))
|
||||
|
||||
// Worker keys: a user mints a long-lived key (JWT-protected), and a worker
|
||||
// trades it for a short-lived JWT on the public exchange endpoint — the key
|
||||
// itself is the credential there, so no prior token is required.
|
||||
mux.HandleFunc("POST /worker-tokens/exchange", h.handleExchangeWorkerKey)
|
||||
mux.Handle("POST /worker-keys", chain(http.HandlerFunc(h.handleCreateWorkerKey), withJWT(issuer)))
|
||||
mux.Handle("GET /worker-keys", chain(http.HandlerFunc(h.handleListWorkerKeys), withJWT(issuer)))
|
||||
mux.Handle("DELETE /worker-keys/{id}", chain(http.HandlerFunc(h.handleRevokeWorkerKey), withJWT(issuer)))
|
||||
|
||||
// Admin-only: grant or revoke the trusted-contributor badge. withAdmin sits
|
||||
// inside withJWT so the role is available from the verified token.
|
||||
mux.Handle("POST /users/{id}/verify",
|
||||
|
||||
Reference in New Issue
Block a user