Files
2026-06-04 16:03:10 -04:00

124 lines
2.3 KiB
Go

package prism
import (
"context"
"git.wisehodl.dev/jay/go-mana-component"
"log/slog"
"sync"
"time"
"git.wisehodl.dev/jay/go-roots-ws"
)
type ChallengeReceived struct {
Challenge string
At time.Time
}
type AuthResponseSent struct {
At time.Time
}
type AuthResponseFailed struct {
Err error
At time.Time
}
type AuthManager struct {
envoy *Envoy
respond func(challenge string) ([]byte, error)
challenge string
inbox <-chan InboxMessage
events <-chan OutboundPoolEvent
ctx context.Context
cancel context.CancelFunc
mu sync.Mutex
wg sync.WaitGroup
handler slog.Handler
logger *slog.Logger
}
func NewAuthManager(e *Envoy, respond func(string) ([]byte, error)) *AuthManager {
ctx, cancel := context.WithCancel(
component.MustExtend(e.Context(), "auth_manager"))
m := &AuthManager{
envoy: e,
respond: respond,
inbox: e.SubscribeInbox([]string{"AUTH"}),
events: e.SubscribeEvents(),
ctx: ctx,
cancel: cancel,
}
if e.Handler() != nil {
comp := component.FromContext(ctx)
m.handler = e.Handler()
m.logger = slog.New(m.handler).With(slog.Any("component", comp))
}
m.wg.Go(m.routeInbox)
m.wg.Go(m.handleEvents)
return m
}
func (m *AuthManager) Close() {
m.cancel()
m.wg.Wait()
}
func (m *AuthManager) routeInbox() {
for {
select {
case <-m.ctx.Done():
return
case msg := <-m.inbox:
challenge, err := envelope.FindAuthChallenge(msg.Data)
if err != nil {
continue
}
m.mu.Lock()
m.challenge = challenge
m.mu.Unlock()
now := time.Now()
m.envoy.Observer().Record(msg.ID, ChallengeReceived{Challenge: challenge, At: now})
signed, err := m.respond(challenge)
if err != nil {
m.envoy.Observer().Record(msg.ID, AuthResponseFailed{Err: err, At: time.Now()})
continue
}
if err := m.envoy.Send([]byte(envelope.EncloseAuthResponse(signed))); err != nil {
m.envoy.Observer().Record(msg.ID, AuthResponseFailed{Err: err, At: time.Now()})
continue
}
if m.logger != nil {
m.logger.Info("responded to auth", "challenge", challenge)
}
m.envoy.Observer().Record(msg.ID, AuthResponseSent{At: time.Now()})
}
}
}
func (m *AuthManager) handleEvents() {
for {
select {
case <-m.ctx.Done():
return
case ev := <-m.events:
if ev.Kind == EventDisconnected {
m.mu.Lock()
m.challenge = ""
m.mu.Unlock()
}
}
}
}