package storage import ( "context" "fmt" "net/netip" "strings" "time" "unicode" "unicode/utf8" ) const UserAgentMaxBytes = 256 func CleanUserAgent(s string) string { s = strings.Trim(s, " \t") if !utf8.ValidString(s) { return "" } for _, r := range s { if unicode.IsControl(r) || unicode.Is(unicode.Bidi_Control, r) { return "" } } if len(s) <= UserAgentMaxBytes { return s } cut := UserAgentMaxBytes for cut > 0 && !utf8.RuneStart(s[cut]) { cut-- } return s[:cut] } func validClientColumns(in CreateForwardSessionInput) error { switch { case in.UserAgent != "" && CleanUserAgent(in.UserAgent) != in.UserAgent: return fmt.Errorf("user agent is not clean or over %d bytes", UserAgentMaxBytes) case in.ClientID != "" && !validClientID(in.ClientID): return fmt.Errorf("client id is not an OIDC client id") case in.Address.IsValid() && (in.Address.Zone() != "" || in.Address.Is4In6()): return fmt.Errorf("address carries a zone or is IPv4-mapped") } return nil } func validClientID(s string) bool { if len(s) > 32 { return false } for i := 0; i < len(s); i++ { c := s[i] if (c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '_' { return false } } return true } func nullAddress(a netip.Addr) any { if !a.IsValid() { return nil } return a.String() } type SessionSeen struct { ID string At, StaleBefore time.Time } func (s *Store) TouchForwardSession(ctx context.Context, id string, at, staleBefore time.Time) (bool, error) { changed, err := s.TouchForwardSessions(ctx, []SessionSeen{{ID: id, At: at, StaleBefore: staleBefore}}) return changed > 0, err } func (s *Store) TouchForwardSessions(ctx context.Context, seen []SessionSeen) (int64, error) { const operation = "touch forward sessions" if err := s.checkOpen(operation); err != nil { return 0, err } tx, err := s.sql.BeginTx(ctx, nil) if err != nil { return 0, classify("begin "+operation, err) } defer tx.Rollback() var changed int64 for _, one := range seen { n, err := execCount(ctx, tx, operation, ` UPDATE forward_sessions SET last_seen_at = ? WHERE id = ? AND revoked = 0 AND (last_seen_at IS NULL OR last_seen_at < ?)`, timeToInt(one.At), one.ID, timeToInt(one.StaleBefore)) if err != nil { return 0, err } changed += n } return changed, classify("commit "+operation, tx.Commit()) }