135 lines
3.8 KiB
Go
135 lines
3.8 KiB
Go
package shared
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"galaxy/authsession/internal/domain/devicesession"
|
|
"galaxy/authsession/internal/domain/gatewayprojection"
|
|
)
|
|
|
|
// Session mirrors the frozen internal read-model DTO used by later trusted
|
|
// transport handlers.
|
|
type Session struct {
|
|
// DeviceSessionID is the stable identifier of one device session.
|
|
DeviceSessionID string
|
|
|
|
// UserID is the stable identifier of the session owner.
|
|
UserID string
|
|
|
|
// ClientPublicKey is the base64-encoded raw 32-byte Ed25519 public key of
|
|
// the device session.
|
|
ClientPublicKey string
|
|
|
|
// Status reports whether the session is active or revoked.
|
|
Status string
|
|
|
|
// CreatedAt is the RFC3339 UTC timestamp at which the session was created.
|
|
CreatedAt string
|
|
|
|
// RevokedAt is the RFC3339 UTC timestamp at which the session was revoked,
|
|
// when the session is revoked.
|
|
RevokedAt *string
|
|
|
|
// RevokeReasonCode is the machine-readable revoke reason code when the
|
|
// session is revoked.
|
|
RevokeReasonCode *string
|
|
|
|
// RevokeActorType is the machine-readable revoke actor type when the
|
|
// session is revoked.
|
|
RevokeActorType *string
|
|
|
|
// RevokeActorID is the optional stable revoke actor identifier when the
|
|
// session is revoked.
|
|
RevokeActorID *string
|
|
}
|
|
|
|
// ToSession converts source-of-truth session into the frozen internal read DTO
|
|
// shape.
|
|
func ToSession(record devicesession.Session) (Session, error) {
|
|
if err := record.Validate(); err != nil {
|
|
return Session{}, fmt.Errorf("map session: %w", err)
|
|
}
|
|
|
|
result := Session{
|
|
DeviceSessionID: record.ID.String(),
|
|
UserID: record.UserID.String(),
|
|
ClientPublicKey: record.ClientPublicKey.String(),
|
|
Status: string(record.Status),
|
|
CreatedAt: formatTime(record.CreatedAt),
|
|
}
|
|
|
|
if record.Revocation != nil {
|
|
revokedAt := formatTime(record.Revocation.At)
|
|
reasonCode := record.Revocation.ReasonCode.String()
|
|
actorType := record.Revocation.ActorType.String()
|
|
result.RevokedAt = &revokedAt
|
|
result.RevokeReasonCode = &reasonCode
|
|
result.RevokeActorType = &actorType
|
|
if record.Revocation.ActorID != "" {
|
|
actorID := record.Revocation.ActorID
|
|
result.RevokeActorID = &actorID
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// ToSessions converts every source-of-truth session into the frozen internal
|
|
// read DTO shape.
|
|
func ToSessions(records []devicesession.Session) ([]Session, error) {
|
|
result := make([]Session, 0, len(records))
|
|
for index, record := range records {
|
|
mapped, err := ToSession(record)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("map session %d: %w", index, err)
|
|
}
|
|
result = append(result, mapped)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// ToGatewayProjectionSnapshot converts source-of-truth session into the
|
|
// separate gateway-facing projection model.
|
|
func ToGatewayProjectionSnapshot(record devicesession.Session) (gatewayprojection.Snapshot, error) {
|
|
if err := record.Validate(); err != nil {
|
|
return gatewayprojection.Snapshot{}, fmt.Errorf("map gateway projection snapshot: %w", err)
|
|
}
|
|
|
|
snapshot := gatewayprojection.Snapshot{
|
|
DeviceSessionID: record.ID,
|
|
UserID: record.UserID,
|
|
ClientPublicKey: record.ClientPublicKey.String(),
|
|
Status: gatewayprojection.Status(record.Status),
|
|
}
|
|
if record.Revocation != nil {
|
|
snapshot.RevokedAt = cloneTimePointer(commonTimePointer(record.Revocation.At.UTC()))
|
|
snapshot.RevokeReasonCode = record.Revocation.ReasonCode
|
|
snapshot.RevokeActorType = record.Revocation.ActorType
|
|
snapshot.RevokeActorID = record.Revocation.ActorID
|
|
}
|
|
if err := snapshot.Validate(); err != nil {
|
|
return gatewayprojection.Snapshot{}, fmt.Errorf("map gateway projection snapshot: %w", err)
|
|
}
|
|
|
|
return snapshot, nil
|
|
}
|
|
|
|
func formatTime(value time.Time) string {
|
|
return value.UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
func commonTimePointer(value time.Time) *time.Time {
|
|
return &value
|
|
}
|
|
|
|
func cloneTimePointer(value *time.Time) *time.Time {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
|
|
cloned := *value
|
|
return &cloned
|
|
}
|