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.
113 lines
3.2 KiB
Go
113 lines
3.2 KiB
Go
package usecase
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/emil28092005/SciMesh/users/internal/domain"
|
|
)
|
|
|
|
// CreateWorkerKey mints a long-lived worker key for a user and returns the
|
|
// one-time plaintext to show once.
|
|
type CreateWorkerKey struct {
|
|
keys WorkerKeyRepository
|
|
clock Clock
|
|
}
|
|
|
|
func NewCreateWorkerKey(keys WorkerKeyRepository, clock Clock) *CreateWorkerKey {
|
|
return &CreateWorkerKey{keys: keys, clock: clock}
|
|
}
|
|
|
|
// Execute returns the stored key (hash only) and the plaintext secret. The
|
|
// secret is never persisted, so this is the sole moment it can be surfaced.
|
|
func (uc *CreateWorkerKey) Execute(ctx context.Context, userID uuid.UUID, name string) (*domain.WorkerKey, string, error) {
|
|
key, raw, err := domain.NewWorkerKey(userID, name, uc.clock.Now())
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
if err := uc.keys.Insert(ctx, key); err != nil {
|
|
return nil, "", err
|
|
}
|
|
return key, raw, nil
|
|
}
|
|
|
|
// ListWorkerKeys returns a user's live keys for display and management.
|
|
type ListWorkerKeys struct {
|
|
keys WorkerKeyRepository
|
|
}
|
|
|
|
func NewListWorkerKeys(keys WorkerKeyRepository) *ListWorkerKeys {
|
|
return &ListWorkerKeys{keys: keys}
|
|
}
|
|
|
|
func (uc *ListWorkerKeys) Execute(ctx context.Context, userID uuid.UUID) ([]*domain.WorkerKey, error) {
|
|
return uc.keys.ListByUser(ctx, userID)
|
|
}
|
|
|
|
// RevokeWorkerKey retires one of the caller's keys.
|
|
type RevokeWorkerKey struct {
|
|
keys WorkerKeyRepository
|
|
}
|
|
|
|
func NewRevokeWorkerKey(keys WorkerKeyRepository) *RevokeWorkerKey {
|
|
return &RevokeWorkerKey{keys: keys}
|
|
}
|
|
|
|
func (uc *RevokeWorkerKey) Execute(ctx context.Context, userID, id uuid.UUID) error {
|
|
return uc.keys.Revoke(ctx, id, userID)
|
|
}
|
|
|
|
// ExchangeWorkerKey trades a valid worker key for a short-lived JWT. The JWT
|
|
// carries the owner's current role and verified flag, so a worker that refreshes
|
|
// after an admin verifies the owner picks up the upgraded trust on its next
|
|
// registration.
|
|
type ExchangeWorkerKey struct {
|
|
keys WorkerKeyRepository
|
|
users UserRepository
|
|
tokens TokenIssuer
|
|
ttl time.Duration
|
|
}
|
|
|
|
func NewExchangeWorkerKey(keys WorkerKeyRepository, users UserRepository, tokens TokenIssuer, ttl time.Duration) *ExchangeWorkerKey {
|
|
return &ExchangeWorkerKey{keys: keys, users: users, tokens: tokens, ttl: ttl}
|
|
}
|
|
|
|
// Execute returns a signed token and its lifetime in seconds. Every failure to
|
|
// resolve the key to a usable owner collapses to ErrInvalidWorkerKey so a caller
|
|
// cannot tell an unknown key from a revoked one or a deleted owner.
|
|
func (uc *ExchangeWorkerKey) Execute(ctx context.Context, rawKey string) (string, int, error) {
|
|
if rawKey == "" {
|
|
return "", 0, ErrInvalidWorkerKey
|
|
}
|
|
|
|
key, err := uc.keys.GetActiveByHash(ctx, domain.HashWorkerKey(rawKey))
|
|
if err != nil {
|
|
if errors.Is(err, ErrWorkerKeyNotFound) {
|
|
return "", 0, ErrInvalidWorkerKey
|
|
}
|
|
return "", 0, err
|
|
}
|
|
|
|
u, err := uc.users.GetByID(ctx, key.UserID)
|
|
if err != nil {
|
|
if errors.Is(err, ErrUserNotFound) {
|
|
return "", 0, ErrInvalidWorkerKey
|
|
}
|
|
return "", 0, err
|
|
}
|
|
|
|
token, err := uc.tokens.Issue(u)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
|
|
// Best-effort: a failed timestamp update must not sink an otherwise valid
|
|
// exchange the worker depends on to keep running.
|
|
_ = uc.keys.TouchLastUsed(ctx, key.ID)
|
|
|
|
return token, int(uc.ttl.Seconds()), nil
|
|
}
|