119 lines
4.2 KiB
Go
119 lines
4.2 KiB
Go
// Package shared defines cross-service primitives used by Game Lobby
|
|
// application services: the Actor identity carried into every service
|
|
// call, the authorization sentinel errors translated to the
|
|
// `forbidden` HTTP code at the transport boundary, and the helpers
|
|
// that derive one from a transport layer.
|
|
package shared
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ActorKind identifies the caller class of one Lobby service operation.
|
|
type ActorKind string
|
|
|
|
const (
|
|
// ActorKindAdmin reports that the caller is Admin Service routed through
|
|
// the internal trusted HTTP port. Admin callers are pre-authorized by the
|
|
// admin role check that Admin Service performs at the gateway boundary
|
|
// before forwarding the request.
|
|
ActorKindAdmin ActorKind = "admin"
|
|
|
|
// ActorKindUser reports that the caller is an authenticated platform user
|
|
// routed through Edge Gateway. User callers are identified by the
|
|
// `X-User-ID` header injected by Edge Gateway.
|
|
ActorKindUser ActorKind = "user"
|
|
)
|
|
|
|
// IsKnown reports whether kind belongs to the frozen actor-kind vocabulary.
|
|
func (kind ActorKind) IsKnown() bool {
|
|
switch kind {
|
|
case ActorKindAdmin, ActorKindUser:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Actor identifies the caller of one Lobby service operation. User actors
|
|
// carry a non-empty UserID; admin actors carry an empty UserID.
|
|
type Actor struct {
|
|
// Kind reports the caller class.
|
|
Kind ActorKind
|
|
|
|
// UserID stores the platform user identifier for ActorKindUser callers.
|
|
// It must be empty for ActorKindAdmin callers.
|
|
UserID string
|
|
}
|
|
|
|
// NewAdminActor returns one Actor that identifies the trusted admin caller.
|
|
func NewAdminActor() Actor {
|
|
return Actor{Kind: ActorKindAdmin}
|
|
}
|
|
|
|
// NewUserActor returns one Actor that identifies the user caller with userID.
|
|
func NewUserActor(userID string) Actor {
|
|
return Actor{Kind: ActorKindUser, UserID: userID}
|
|
}
|
|
|
|
// IsAdmin reports whether actor is the trusted admin caller.
|
|
func (actor Actor) IsAdmin() bool {
|
|
return actor.Kind == ActorKindAdmin
|
|
}
|
|
|
|
// IsUser reports whether actor is an authenticated platform user.
|
|
func (actor Actor) IsUser() bool {
|
|
return actor.Kind == ActorKindUser
|
|
}
|
|
|
|
// Validate reports whether actor carries a structurally valid identity.
|
|
// Admin actors must not carry a user identifier; user actors must carry a
|
|
// non-empty trimmed user identifier.
|
|
func (actor Actor) Validate() error {
|
|
if !actor.Kind.IsKnown() {
|
|
return fmt.Errorf("actor kind %q is unsupported", actor.Kind)
|
|
}
|
|
switch actor.Kind {
|
|
case ActorKindAdmin:
|
|
if strings.TrimSpace(actor.UserID) != "" {
|
|
return fmt.Errorf("admin actor must not carry a user id")
|
|
}
|
|
case ActorKindUser:
|
|
if strings.TrimSpace(actor.UserID) == "" {
|
|
return fmt.Errorf("user actor must carry a non-empty user id")
|
|
}
|
|
if strings.TrimSpace(actor.UserID) != actor.UserID {
|
|
return fmt.Errorf("user actor id must not contain surrounding whitespace")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ErrForbidden reports that the caller is not authorized for the requested
|
|
// operation on the requested resource. The transport layer translates it to
|
|
// the HTTP `403 forbidden` error envelope.
|
|
var ErrForbidden = errors.New("forbidden")
|
|
|
|
// ErrEligibilityDenied reports that the User Service eligibility snapshot
|
|
// rejected the acting user for the requested operation. It covers both
|
|
// "user not found" (Exists=false) and any sanction or marker that
|
|
// collapses the relevant `can_*` flag to false. The transport layer
|
|
// translates it to the HTTP `422 eligibility_denied` envelope.
|
|
var ErrEligibilityDenied = errors.New("eligibility denied")
|
|
|
|
// ErrServiceUnavailable reports that an upstream synchronous dependency
|
|
// (User Service, Game Master, etc.) is unreachable or violated its
|
|
// contract. The transport layer translates it to the HTTP
|
|
// `503 service_unavailable` envelope.
|
|
var ErrServiceUnavailable = errors.New("service unavailable")
|
|
|
|
// ErrSubjectNotFound reports that the operation references a subject
|
|
// (a user, a pending race-name registration, etc.) that is not present
|
|
// in the relevant store and is not naturally surfaced through one of
|
|
// the domain `ErrNotFound` sentinels. The transport layer translates
|
|
// it to the HTTP `404 subject_not_found` envelope.
|
|
var ErrSubjectNotFound = errors.New("subject not found")
|