feat: add single-admin application onboarding and token rotation

This commit is contained in:
2026-09-27 10:18:31 +08:00
parent 98ff028228
commit 87aa710013
24 changed files with 1341 additions and 25 deletions

View File

@@ -0,0 +1,105 @@
package httpserver
import (
"net/http"
"net/url"
"strings"
"git.yuntuo.hk/k2safe/orbit/internal/consoleauth"
"git.yuntuo.hk/k2safe/orbit/internal/fault"
core "git.yuntuo.hk/k2safe/orbit/model"
"git.yuntuo.hk/k2safe/orbit/platform/auth"
)
// RegisterConsoleApplications 只接受登录 Cookie;业务 Bearer 不能访问跨应用管理。
func RegisterConsoleApplications(server *http.Server, consoleAuth consoleauth.Store, store consoleauth.Applications) {
if server == nil || consoleAuth == nil || store == nil {
return
}
mux, ok := server.Handler.(*http.ServeMux)
if !ok {
return
}
check := func(w http.ResponseWriter, r *http.Request) (auth.Token, bool) {
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("X-Content-Type-Options", "nosniff")
token, ok := consoleCookieToken(r)
if !ok || consoleAuth.ValidateSession(r.Context(), token) != nil {
writeError(w, "", fault.Unauthorized)
return auth.Token{}, false
}
// 自定义请求头触发跨域预检;不开放 CORS,另校验浏览器 Origin 和 Fetch Metadata。
if r.Method != "GET" {
if r.Header.Get("X-Orbit-Console") != "1" || r.Header.Get("Sec-Fetch-Site") == "cross-site" {
writeError(w, "", fault.Forbidden)
return auth.Token{}, false
}
if origin := r.Header.Get("Origin"); origin != "" {
u, err := url.Parse(origin)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || !strings.EqualFold(u.Host, r.Host) || u.User != nil {
writeError(w, "", fault.Forbidden)
return auth.Token{}, false
}
}
}
return token, true
}
mux.HandleFunc("GET /console/applications", func(w http.ResponseWriter, r *http.Request) {
token, ok := check(w, r)
if !ok {
return
}
out, err := store.ListApplications(r.Context(), token, core.ID(r.URL.Query().Get("after")))
if err != nil {
writeError(w, "", err)
return
}
writeConsoleJSON(w, 200, map[string]any{"data": out})
})
mux.HandleFunc("GET /console/applications/{namespace}", func(w http.ResponseWriter, r *http.Request) {
token, ok := check(w, r)
if !ok {
return
}
out, err := store.GetApplication(r.Context(), token, core.ID(r.PathValue("namespace")))
if err != nil {
writeError(w, "", err)
return
}
writeConsoleJSON(w, 200, map[string]any{"data": out})
})
mux.HandleFunc("POST /console/applications/prepare", func(w http.ResponseWriter, r *http.Request) {
token, ok := check(w, r)
if !ok {
return
}
var in consoleauth.ApplicationInput
if err := decodeBody(r, &in); err != nil {
writeError(w, "", err)
return
}
out, err := store.PrepareApplication(r.Context(), token, in)
if err != nil {
writeError(w, "", err)
return
}
writeConsoleJSON(w, 200, map[string]any{"data": out})
})
mux.HandleFunc("POST /console/applications/apply", func(w http.ResponseWriter, r *http.Request) {
token, ok := check(w, r)
if !ok {
return
}
var in consoleauth.ApplicationOperation
if err := decodeBody(r, &in); err != nil {
writeError(w, "", err)
return
}
out, err := store.ApplyApplication(r.Context(), token, in)
if err != nil {
writeError(w, "", err)
return
}
writeConsoleJSON(w, 200, map[string]any{"data": out})
})
}