feat: add single-admin application onboarding and token rotation
This commit is contained in:
105
internal/transport/httpserver/console_applications.go
Normal file
105
internal/transport/httpserver/console_applications.go
Normal 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})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user