215 lines
7.4 KiB
Go
215 lines
7.4 KiB
Go
package ports
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"galaxy/authsession/internal/domain/common"
|
|
"galaxy/authsession/internal/domain/devicesession"
|
|
)
|
|
|
|
// SessionStore provides source-of-truth persistence for device sessions
|
|
// without exposing storage-specific encoding or transaction primitives.
|
|
type SessionStore interface {
|
|
// Get returns the stored session for deviceSessionID. Implementations must
|
|
// wrap ErrNotFound when deviceSessionID does not exist.
|
|
Get(ctx context.Context, deviceSessionID common.DeviceSessionID) (devicesession.Session, error)
|
|
|
|
// ListByUserID returns every stored session for userID in newest-first
|
|
// order. Implementations must return an empty slice, not ErrNotFound, when
|
|
// userID has no stored sessions.
|
|
ListByUserID(ctx context.Context, userID common.UserID) ([]devicesession.Session, error)
|
|
|
|
// CountActiveByUserID returns the number of active sessions currently stored
|
|
// for userID.
|
|
CountActiveByUserID(ctx context.Context, userID common.UserID) (int, error)
|
|
|
|
// Create persists record as a new device session. Implementations must wrap
|
|
// ErrConflict when record.ID already exists.
|
|
Create(ctx context.Context, record devicesession.Session) error
|
|
|
|
// Revoke stores a revoked view of one target session. Implementations must
|
|
// wrap ErrNotFound when input.DeviceSessionID does not exist.
|
|
Revoke(ctx context.Context, input RevokeSessionInput) (RevokeSessionResult, error)
|
|
|
|
// RevokeAllByUserID stores revoked views for all currently active sessions
|
|
// owned by input.UserID.
|
|
RevokeAllByUserID(ctx context.Context, input RevokeUserSessionsInput) (RevokeUserSessionsResult, error)
|
|
}
|
|
|
|
// RevokeSessionInput describes one single-session revoke mutation requested
|
|
// from SessionStore.
|
|
type RevokeSessionInput struct {
|
|
// DeviceSessionID identifies the session that should be revoked.
|
|
DeviceSessionID common.DeviceSessionID
|
|
|
|
// Revocation stores the audit metadata that must be attached to the revoked
|
|
// session.
|
|
Revocation devicesession.Revocation
|
|
}
|
|
|
|
// Validate reports whether RevokeSessionInput contains a complete revoke
|
|
// request.
|
|
func (i RevokeSessionInput) Validate() error {
|
|
if err := i.DeviceSessionID.Validate(); err != nil {
|
|
return fmt.Errorf("revoke session input device session id: %w", err)
|
|
}
|
|
if err := i.Revocation.Validate(); err != nil {
|
|
return fmt.Errorf("revoke session input revocation: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RevokeSessionOutcome identifies the coarse outcome of revoking one device
|
|
// session.
|
|
type RevokeSessionOutcome string
|
|
|
|
const (
|
|
// RevokeSessionOutcomeRevoked reports that an active session was moved to
|
|
// the revoked state by the current mutation.
|
|
RevokeSessionOutcomeRevoked RevokeSessionOutcome = "revoked"
|
|
|
|
// RevokeSessionOutcomeAlreadyRevoked reports that the requested session had
|
|
// already been revoked before the current mutation.
|
|
RevokeSessionOutcomeAlreadyRevoked RevokeSessionOutcome = "already_revoked"
|
|
)
|
|
|
|
// IsKnown reports whether RevokeSessionOutcome is supported by the current
|
|
// session-store contract.
|
|
func (o RevokeSessionOutcome) IsKnown() bool {
|
|
switch o {
|
|
case RevokeSessionOutcomeRevoked, RevokeSessionOutcomeAlreadyRevoked:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// RevokeSessionResult describes the stable outcome returned by SessionStore
|
|
// after a single-session revoke attempt.
|
|
type RevokeSessionResult struct {
|
|
// Outcome reports whether the session was revoked just now or had already
|
|
// been revoked.
|
|
Outcome RevokeSessionOutcome
|
|
|
|
// Session stores the current source-of-truth session state after the revoke
|
|
// attempt.
|
|
Session devicesession.Session
|
|
}
|
|
|
|
// Validate reports whether RevokeSessionResult satisfies the session-store
|
|
// contract invariants.
|
|
func (r RevokeSessionResult) Validate() error {
|
|
if !r.Outcome.IsKnown() {
|
|
return fmt.Errorf("revoke session result outcome %q is unsupported", r.Outcome)
|
|
}
|
|
if err := r.Session.Validate(); err != nil {
|
|
return fmt.Errorf("revoke session result session: %w", err)
|
|
}
|
|
if r.Session.Status != devicesession.StatusRevoked {
|
|
return errors.New("revoke session result session must be revoked")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RevokeUserSessionsInput describes one bulk user-session revoke mutation
|
|
// requested from SessionStore.
|
|
type RevokeUserSessionsInput struct {
|
|
// UserID identifies the owner whose active sessions should be revoked.
|
|
UserID common.UserID
|
|
|
|
// Revocation stores the audit metadata that must be attached to every
|
|
// revoked session.
|
|
Revocation devicesession.Revocation
|
|
}
|
|
|
|
// Validate reports whether RevokeUserSessionsInput contains a complete bulk
|
|
// revoke request.
|
|
func (i RevokeUserSessionsInput) Validate() error {
|
|
if err := i.UserID.Validate(); err != nil {
|
|
return fmt.Errorf("revoke user sessions input user id: %w", err)
|
|
}
|
|
if err := i.Revocation.Validate(); err != nil {
|
|
return fmt.Errorf("revoke user sessions input revocation: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RevokeUserSessionsOutcome identifies the coarse outcome of revoking all
|
|
// active sessions of one user.
|
|
type RevokeUserSessionsOutcome string
|
|
|
|
const (
|
|
// RevokeUserSessionsOutcomeRevoked reports that one or more active sessions
|
|
// were revoked by the current mutation.
|
|
RevokeUserSessionsOutcomeRevoked RevokeUserSessionsOutcome = "revoked"
|
|
|
|
// RevokeUserSessionsOutcomeNoActiveSessions reports that the target user did
|
|
// not currently own any active sessions.
|
|
RevokeUserSessionsOutcomeNoActiveSessions RevokeUserSessionsOutcome = "no_active_sessions"
|
|
)
|
|
|
|
// IsKnown reports whether RevokeUserSessionsOutcome is supported by the
|
|
// current session-store contract.
|
|
func (o RevokeUserSessionsOutcome) IsKnown() bool {
|
|
switch o {
|
|
case RevokeUserSessionsOutcomeRevoked, RevokeUserSessionsOutcomeNoActiveSessions:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// RevokeUserSessionsResult describes the stable outcome returned by
|
|
// SessionStore after one bulk revoke attempt.
|
|
type RevokeUserSessionsResult struct {
|
|
// Outcome reports whether at least one active session was revoked.
|
|
Outcome RevokeUserSessionsOutcome
|
|
|
|
// UserID identifies the owner whose sessions were evaluated.
|
|
UserID common.UserID
|
|
|
|
// Sessions stores the current source-of-truth session states for every
|
|
// session affected by the bulk revoke operation.
|
|
Sessions []devicesession.Session
|
|
}
|
|
|
|
// Validate reports whether RevokeUserSessionsResult satisfies the bulk
|
|
// session-store contract invariants.
|
|
func (r RevokeUserSessionsResult) Validate() error {
|
|
if !r.Outcome.IsKnown() {
|
|
return fmt.Errorf("revoke user sessions result outcome %q is unsupported", r.Outcome)
|
|
}
|
|
if err := r.UserID.Validate(); err != nil {
|
|
return fmt.Errorf("revoke user sessions result user id: %w", err)
|
|
}
|
|
for index, session := range r.Sessions {
|
|
if err := session.Validate(); err != nil {
|
|
return fmt.Errorf("revoke user sessions result session %d: %w", index, err)
|
|
}
|
|
if session.Status != devicesession.StatusRevoked {
|
|
return fmt.Errorf("revoke user sessions result session %d must be revoked", index)
|
|
}
|
|
if session.UserID != r.UserID {
|
|
return fmt.Errorf("revoke user sessions result session %d belongs to %q, want %q", index, session.UserID, r.UserID)
|
|
}
|
|
}
|
|
|
|
switch r.Outcome {
|
|
case RevokeUserSessionsOutcomeRevoked:
|
|
if len(r.Sessions) == 0 {
|
|
return errors.New("revoke user sessions result must include sessions when outcome is revoked")
|
|
}
|
|
case RevokeUserSessionsOutcomeNoActiveSessions:
|
|
if len(r.Sessions) != 0 {
|
|
return errors.New("revoke user sessions result must not include sessions when outcome is no_active_sessions")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|