feat: authsession service

This commit is contained in:
Ilia Denisov
2026-04-08 16:23:07 +02:00
committed by GitHub
parent 28f04916af
commit 86a68ed9d0
174 changed files with 31732 additions and 112 deletions
@@ -0,0 +1,134 @@
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
}