console v0.5.0: auth через deck API, login overlay, logout

This commit is contained in:
Naeel
2026-04-19 08:25:23 +03:00
parent c40fc47025
commit 579bf97e17
2 changed files with 183 additions and 10 deletions
+91 -7
View File
@@ -40,6 +40,12 @@ var (
const defaultSATokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
var deckAPIs = map[string]string{
"prod": "https://deck-api.ngcloud.ru/api/v1",
"dev": "https://deck-api-dev.ngcloud.ru/api/v1",
"test": "https://deck-api-test.ngcloud.ru/api/v1",
}
type server struct {
dyn dynamic.Interface
ns string
@@ -54,6 +60,7 @@ type server struct {
tokenMu sync.Mutex
cachedJWT string
tokenExpAt time.Time
tokenCache sync.Map
}
type createFunctionRequest struct {
@@ -138,12 +145,32 @@ func main() {
mux.HandleFunc("/api/httptriggers", s.handleList(httpTrigGVR))
mux.HandleFunc("/api/timetriggers", s.handleList(timeTrigGVR))
mux.HandleFunc("/console/api/environments", s.handleList(environmentGVR))
mux.HandleFunc("/console/api/packages", s.handleList(packageGVR))
mux.HandleFunc("/console/api/functions", s.handleFunctionsRoot)
mux.HandleFunc("/console/api/functions/", s.handleFunctionsAction)
mux.HandleFunc("/console/api/httptriggers", s.handleList(httpTrigGVR))
mux.HandleFunc("/console/api/timetriggers", s.handleList(timeTrigGVR))
auth := func(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := strings.TrimSpace(r.Header.Get("X-Auth-Token"))
env := strings.TrimSpace(strings.ToLower(r.Header.Get("X-Auth-Env")))
if _, ok := deckAPIs[env]; !ok {
env = "test"
}
if token == "" {
writeJSONError(w, http.StatusUnauthorized, "unauthorized")
return
}
if err := s.validateDeckToken(token, env); err != nil {
writeJSONError(w, http.StatusUnauthorized, "unauthorized")
return
}
h(w, r)
}
}
mux.HandleFunc("/console/api/auth", s.handleAuth)
mux.HandleFunc("/console/api/environments", auth(s.handleList(environmentGVR)))
mux.HandleFunc("/console/api/packages", auth(s.handleList(packageGVR)))
mux.HandleFunc("/console/api/functions", auth(s.handleFunctionsRoot))
mux.HandleFunc("/console/api/functions/", auth(s.handleFunctionsAction))
mux.HandleFunc("/console/api/httptriggers", auth(s.handleList(httpTrigGVR)))
mux.HandleFunc("/console/api/timetriggers", auth(s.handleList(timeTrigGVR)))
httpServer := &http.Server{
Addr: ":" + port,
@@ -714,6 +741,63 @@ func (s *server) getRouterToken() string {
return s.cachedJWT
}
func (s *server) validateDeckToken(token, env string) error {
cacheKey := env + ":" + token
if v, ok := s.tokenCache.Load(cacheKey); ok {
if time.Now().Before(v.(time.Time)) {
return nil
}
s.tokenCache.Delete(cacheKey)
}
apiBase, ok := deckAPIs[env]
if !ok {
return fmt.Errorf("unknown env: %s", env)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiBase+"/index.cfm/instances", nil)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+token)
resp, err := s.http.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
_, _ = io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusUnauthorized {
return fmt.Errorf("invalid token")
}
s.tokenCache.Store(cacheKey, time.Now().Add(5*time.Minute))
return nil
}
func (s *server) handleAuth(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
writeJSONError(w, http.StatusMethodNotAllowed, "method not allowed")
return
}
var body struct {
Token string `json:"token"`
Env string `json:"env"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || strings.TrimSpace(body.Token) == "" {
writeJSONError(w, http.StatusBadRequest, "token required")
return
}
env := strings.TrimSpace(strings.ToLower(body.Env))
if _, ok := deckAPIs[env]; !ok {
env = "test"
}
if err := s.validateDeckToken(body.Token, env); err != nil {
writeJSONError(w, http.StatusUnauthorized, "invalid token")
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true, "env": env})
}
func (s *server) handleDeleteFunction(w http.ResponseWriter, r *http.Request, name string) {
ctx, cancel := context.WithTimeout(r.Context(), 20*time.Second)
defer cancel()
@@ -817,7 +901,7 @@ func withCORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Auth-Token, X-Auth-Env")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return