feat: authsession service
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
// Package devicesession defines the source-of-truth domain model for one
|
||||
// authenticated device session.
|
||||
package devicesession
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"galaxy/authsession/internal/domain/common"
|
||||
)
|
||||
|
||||
// Status identifies the coarse lifecycle state of one device session.
|
||||
type Status string
|
||||
|
||||
const (
|
||||
// StatusActive reports that the session may be used for authenticated
|
||||
// request verification.
|
||||
StatusActive Status = "active"
|
||||
|
||||
// StatusRevoked reports that the session has been revoked and must no
|
||||
// longer authenticate requests.
|
||||
StatusRevoked Status = "revoked"
|
||||
)
|
||||
|
||||
// RevokeReasonDeviceLogout reports that one device logged itself out.
|
||||
const RevokeReasonDeviceLogout common.RevokeReasonCode = "device_logout"
|
||||
|
||||
// RevokeReasonLogoutAll reports that the session was revoked by a
|
||||
// user-scoped logout-all action.
|
||||
const RevokeReasonLogoutAll common.RevokeReasonCode = "logout_all"
|
||||
|
||||
// RevokeReasonAdminRevoke reports that the session was revoked
|
||||
// administratively.
|
||||
const RevokeReasonAdminRevoke common.RevokeReasonCode = "admin_revoke"
|
||||
|
||||
// RevokeReasonUserBlocked reports that the session was revoked because future
|
||||
// auth flow for the user or e-mail was blocked.
|
||||
const RevokeReasonUserBlocked common.RevokeReasonCode = "user_blocked"
|
||||
|
||||
// IsKnown reports whether Status is one of the device-session states
|
||||
// supported by the current domain model.
|
||||
func (s Status) IsKnown() bool {
|
||||
switch s {
|
||||
case StatusActive, StatusRevoked:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// CanTransitionTo reports whether the current device-session Status may move
|
||||
// to next under the Stage-2 lifecycle rules.
|
||||
func (s Status) CanTransitionTo(next Status) bool {
|
||||
return s == StatusActive && next == StatusRevoked
|
||||
}
|
||||
|
||||
// IsKnownRevokeReasonCode reports whether code is one of the built-in revoke
|
||||
// reasons fixed by the Stage-2 domain model.
|
||||
func IsKnownRevokeReasonCode(code common.RevokeReasonCode) bool {
|
||||
switch code {
|
||||
case RevokeReasonDeviceLogout,
|
||||
RevokeReasonLogoutAll,
|
||||
RevokeReasonAdminRevoke,
|
||||
RevokeReasonUserBlocked:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Revocation stores the audit metadata recorded when a session is revoked.
|
||||
type Revocation struct {
|
||||
// At reports when the revoke took effect.
|
||||
At time.Time
|
||||
|
||||
// ReasonCode stores one machine-readable revoke reason code.
|
||||
ReasonCode common.RevokeReasonCode
|
||||
|
||||
// ActorType stores one machine-readable initiator type.
|
||||
ActorType common.RevokeActorType
|
||||
|
||||
// ActorID optionally stores a stable initiator identifier.
|
||||
ActorID string
|
||||
}
|
||||
|
||||
// Validate reports whether Revocation contains all metadata required for a
|
||||
// revoked session.
|
||||
func (r Revocation) Validate() error {
|
||||
if r.At.IsZero() {
|
||||
return errors.New("session revocation time must not be zero")
|
||||
}
|
||||
if err := r.ReasonCode.Validate(); err != nil {
|
||||
return fmt.Errorf("session revocation reason code: %w", err)
|
||||
}
|
||||
if err := r.ActorType.Validate(); err != nil {
|
||||
return fmt.Errorf("session revocation actor type: %w", err)
|
||||
}
|
||||
if strings.TrimSpace(r.ActorID) != r.ActorID {
|
||||
return errors.New("session revocation actor id must not contain surrounding whitespace")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Session is the minimal source-of-truth aggregate shape fixed by Stage 2.
|
||||
type Session struct {
|
||||
// ID identifies the device session.
|
||||
ID common.DeviceSessionID
|
||||
|
||||
// UserID identifies the durable user linkage for the session.
|
||||
UserID common.UserID
|
||||
|
||||
// ClientPublicKey stores the validated device public key in parsed form.
|
||||
ClientPublicKey common.ClientPublicKey
|
||||
|
||||
// Status reports the coarse lifecycle state of the session.
|
||||
Status Status
|
||||
|
||||
// CreatedAt reports when the session was created.
|
||||
CreatedAt time.Time
|
||||
|
||||
// Revocation is present only when Status is StatusRevoked.
|
||||
Revocation *Revocation
|
||||
}
|
||||
|
||||
// Validate reports whether Session satisfies the Stage-2 structural and
|
||||
// lifecycle invariants.
|
||||
func (s Session) Validate() error {
|
||||
if err := s.ID.Validate(); err != nil {
|
||||
return fmt.Errorf("session id: %w", err)
|
||||
}
|
||||
if err := s.UserID.Validate(); err != nil {
|
||||
return fmt.Errorf("session user id: %w", err)
|
||||
}
|
||||
if err := s.ClientPublicKey.Validate(); err != nil {
|
||||
return fmt.Errorf("session client public key: %w", err)
|
||||
}
|
||||
if !s.Status.IsKnown() {
|
||||
return fmt.Errorf("session status %q is unsupported", s.Status)
|
||||
}
|
||||
if s.CreatedAt.IsZero() {
|
||||
return errors.New("session creation time must not be zero")
|
||||
}
|
||||
|
||||
switch s.Status {
|
||||
case StatusActive:
|
||||
if s.Revocation != nil {
|
||||
return errors.New("active session must not contain revocation metadata")
|
||||
}
|
||||
case StatusRevoked:
|
||||
if s.Revocation == nil {
|
||||
return errors.New("revoked session must contain revocation metadata")
|
||||
}
|
||||
if err := s.Revocation.Validate(); err != nil {
|
||||
return fmt.Errorf("session revocation: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user