package main import ( "context" "crypto/tls" "database/sql" "encoding/json" "net/http" "time" "tailscale.com/client/tailscale" "tailscale.com/tsnet" "within.website/ln" "within.website/ln/ex" ) type Server struct { db *sql.DB mux *http.ServeMux srv *tsnet.Server } func NewServer(db *sql.DB, srv *tsnet.Server) *Server { mux := http.NewServeMux() s := &Server{ db: db, mux: mux, srv: srv, } mux.HandleFunc("/api/whois", s.whois) return s } func (s *Server) ListenAndServe() error { l, err := s.srv.Listen("tcp", ":443") if err != nil { return err } l = tls.NewListener(l, &tls.Config{ GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { c, err := tailscale.GetCertificate(chi) if err != nil { ln.Error(context.Background(), err, ln.F{"remote_addr": chi.Conn.RemoteAddr().String()}) } return c, err }, }) hs := &http.Server{ IdleTimeout: 5 * time.Minute, Handler: ex.HTTPLog(s), } ln.Log(context.Background(), ln.Info("listening on https://twitchalitics.shark-harmonic.ts.net")) return hs.Serve(l) } func (s Server) whois(w http.ResponseWriter, r *http.Request) { userInfo, err := tailscale.WhoIs(r.Context(), r.RemoteAddr) if err != nil { http.Error(w, "can't get whois response: "+err.Error(), http.StatusInternalServerError) ln.Error(r.Context(), err) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(userInfo) } func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { userInfo, err := tailscale.WhoIs(r.Context(), r.RemoteAddr) if err != nil { http.Error(w, "can't get whois response: "+err.Error(), http.StatusInternalServerError) ln.Error(r.Context(), err) return } ctx := ln.WithF(r.Context(), ln.F{"username": userInfo.UserProfile.LoginName, "hostname": userInfo.Node.ComputedName, "os": userInfo.Node.Hostinfo.OS}) r = r.WithContext(ctx) s.mux.ServeHTTP(w, r) }