mirror of
https://github.com/sigcn/pg.git
synced 2025-09-27 01:05:51 +08:00
143 lines
3.6 KiB
Go
143 lines
3.6 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"runtime/debug"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/sigcn/pg/langs"
|
|
"github.com/sigcn/pg/peermap/api/types"
|
|
"github.com/sigcn/pg/peermap/auth"
|
|
"github.com/sigcn/pg/peermap/config"
|
|
"github.com/sigcn/pg/peermap/oidc"
|
|
)
|
|
|
|
var (
|
|
Version string = "dev"
|
|
ErrForbidden = langs.Error{Code: 10000, Msg: "forbidden"}
|
|
)
|
|
|
|
type contextKey string
|
|
|
|
type ApiV1 struct {
|
|
Config config.Config
|
|
Auth *auth.Authenticator
|
|
PeerStore types.PeerStore
|
|
Grant oidc.Grant
|
|
|
|
mux http.ServeMux
|
|
initOnce sync.Once
|
|
}
|
|
|
|
func (a *ApiV1) init() {
|
|
a.initOnce.Do(func() {
|
|
a.mux.HandleFunc("GET /api/v1/r8/stuns", a.handleQuerySTUNs)
|
|
a.mux.HandleFunc("GET /api/v1/r5/peers", a.handleQueryPeers)
|
|
a.mux.HandleFunc("GET /api/v1/r5/psns.json", a.handleDownloadSecret)
|
|
a.mux.HandleFunc("GET /api/v1/r5/server_info", a.handleQueryServerInfo)
|
|
})
|
|
}
|
|
|
|
func (a *ApiV1) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
a.init()
|
|
token := r.Header.Get("X-Token")
|
|
secret, err := a.Auth.ParseSecret(token)
|
|
if err != nil {
|
|
langs.Err(err).MarshalTo(w)
|
|
return
|
|
}
|
|
if strings.HasPrefix(r.URL.Path, "/api/v1/r5/") && !secret.Admin {
|
|
ErrForbidden.MarshalTo(w)
|
|
return
|
|
}
|
|
if time.Until(time.Unix(secret.Deadline, 0)) <
|
|
a.Config.SecretValidityPeriod-a.Config.SecretRotationPeriod {
|
|
if newSecret, err := a.Grant(secret.Network, langs.IfElse(secret.Admin, "PG_ADM", "")); err == nil {
|
|
b, _ := json.Marshal(newSecret)
|
|
w.Header().Add("X-Set-Token", string(b))
|
|
}
|
|
}
|
|
a.mux.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), contextKey("secret"), secret)))
|
|
}
|
|
|
|
func (a *ApiV1) handleQuerySTUNs(w http.ResponseWriter, r *http.Request) {
|
|
var ret []string
|
|
for _, stun := range a.Config.STUNs {
|
|
if strings.Contains(stun, "://") {
|
|
ret = append(ret, stun)
|
|
} else {
|
|
ret = append(ret, fmt.Sprintf("udp://%s", stun))
|
|
}
|
|
}
|
|
langs.Data[[]string]{Data: ret}.MarshalTo(w)
|
|
}
|
|
|
|
func (a *ApiV1) handleQueryPeers(w http.ResponseWriter, r *http.Request) {
|
|
peers, err := a.PeerStore.Peers(r.Context().Value(contextKey("secret")).(auth.JSONSecret).Network)
|
|
if err != nil {
|
|
langs.Err(err).MarshalTo(w)
|
|
return
|
|
}
|
|
langs.Data[any]{Data: peers}.MarshalTo(w)
|
|
}
|
|
|
|
func (a *ApiV1) handleDownloadSecret(w http.ResponseWriter, r *http.Request) {
|
|
secret := r.Context().Value(contextKey("secret")).(auth.JSONSecret)
|
|
secretJSON, err := a.Grant(secret.Network, "")
|
|
if err != nil {
|
|
langs.Err(err).MarshalTo(w)
|
|
return
|
|
}
|
|
fileName := fmt.Sprintf("%s_psns.json", secret.Network)
|
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", fileName))
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
w.Header().Set("Content-Transfer-Encoding", "binary")
|
|
json.NewEncoder(w).Encode(secretJSON)
|
|
slog.Info("Generate a secret", "network", secret.Network)
|
|
}
|
|
|
|
func (a *ApiV1) handleQueryServerInfo(w http.ResponseWriter, r *http.Request) {
|
|
info, err := readBuildInfo()
|
|
if err != nil {
|
|
langs.Err(err).MarshalTo(w)
|
|
}
|
|
langs.Data[any]{Data: serverInfo{Version: Version, buildInfo: info}}.MarshalTo(w)
|
|
}
|
|
|
|
type buildInfo struct {
|
|
GoVersion string `json:"go_version"`
|
|
VCSRevision string `json:"vcs_revision"`
|
|
VCSTime string `json:"vcs_time"`
|
|
}
|
|
|
|
func readBuildInfo() (buildInfo buildInfo, err error) {
|
|
info, ok := debug.ReadBuildInfo()
|
|
if !ok {
|
|
err = errors.ErrUnsupported
|
|
return
|
|
}
|
|
buildInfo.GoVersion = info.GoVersion
|
|
for _, s := range info.Settings {
|
|
if s.Key == "vcs.revision" {
|
|
buildInfo.VCSRevision = s.Value
|
|
continue
|
|
}
|
|
if s.Key == "vcs.time" {
|
|
buildInfo.VCSTime = s.Value
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
type serverInfo struct {
|
|
Version string `json:"version"`
|
|
buildInfo
|
|
}
|