auth: switch user login to session token and decouple tenant access
This commit is contained in:
@@ -91,80 +91,62 @@ func main() {
|
||||
srv := server.New(cfg)
|
||||
srv.StartCleanup()
|
||||
|
||||
// Admin-only Middleware
|
||||
// Admin-only Middleware (master token only)
|
||||
adminMiddleware := func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v1/auth/login" {
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
valid := authHeader == fmt.Sprintf("Bearer %d", cfg.Token)
|
||||
if !valid {
|
||||
ac, ok := srv.ResolveAccess(r, cfg.Token)
|
||||
if !ok || ac.Kind != "master" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintf(w, `{"error":401,"message":"unauthorized"}`)
|
||||
return
|
||||
}
|
||||
// RBAC: admin only
|
||||
if srv.Store() != nil {
|
||||
if u, err := srv.Store().GetUserByToken(server.BearerToken(r)); err == nil && u != nil {
|
||||
if u.Status != 1 || u.Role != "admin" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
fmt.Fprintf(w, `{"error":403,"message":"forbidden"}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Tenant or Admin Middleware
|
||||
// Tenant or Admin Middleware (session/apikey/master)
|
||||
tenantMiddleware := func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v1/auth/login" {
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == fmt.Sprintf("Bearer %d", cfg.Token) {
|
||||
next(w, r)
|
||||
ac, ok := srv.ResolveAccess(r, cfg.Token)
|
||||
if !ok {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintf(w, `{"error":401,"message":"unauthorized"}`)
|
||||
return
|
||||
}
|
||||
// check API key + RBAC
|
||||
if srv.Store() != nil {
|
||||
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
|
||||
// role check if user exists
|
||||
if u, err := srv.Store().GetUserByToken(server.BearerToken(r)); err == nil && u != nil {
|
||||
if u.Status != 1 {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
fmt.Fprintf(w, `{"error":403,"message":"forbidden"}`)
|
||||
return
|
||||
}
|
||||
if u.Role == "operator" {
|
||||
path := r.URL.Path
|
||||
if path != "/api/v1/nodes" && path != "/api/v1/sdwans" && path != "/api/v1/sdwan/edit" && path != "/api/v1/connect" && path != "/api/v1/nodes/apps" && path != "/api/v1/nodes/kick" && path != "/api/v1/stats" && path != "/api/v1/health" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
fmt.Fprintf(w, `{"error":403,"message":"forbidden"}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
next(w, r)
|
||||
if ac.Kind == "session" && ac.Role == "operator" {
|
||||
path := r.URL.Path
|
||||
if path != "/api/v1/nodes" && path != "/api/v1/sdwans" && path != "/api/v1/sdwan/edit" && path != "/api/v1/connect" && path != "/api/v1/nodes/apps" && path != "/api/v1/nodes/kick" && path != "/api/v1/stats" && path != "/api/v1/health" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
fmt.Fprintf(w, `{"error":403,"message":"forbidden"}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprintf(w, `{"error":401,"message":"unauthorized"}`)
|
||||
return
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
getTenantID := func(r *http.Request) int64 {
|
||||
tok := server.BearerToken(r)
|
||||
if tok == "" {
|
||||
return 0
|
||||
}
|
||||
if ac, ok := srv.ResolveTenantAccessToken(tok); ok && ac.Kind != "master" {
|
||||
return ac.TenantID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/ws", srv.HandleWS)
|
||||
|
||||
@@ -201,7 +183,7 @@ func main() {
|
||||
_ = json.Unmarshal(body, &reqTok)
|
||||
_ = json.Unmarshal(body, &reqUser)
|
||||
|
||||
// --- user login ---
|
||||
// --- user login (session token) ---
|
||||
if reqUser.TenantID > 0 && reqUser.Username != "" && reqUser.Password != "" {
|
||||
if srv.Store() == nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -216,23 +198,24 @@ func main() {
|
||||
fmt.Fprintf(w, `{"error":1,"message":"invalid credentials"}`)
|
||||
return
|
||||
}
|
||||
// issue API key for this tenant and return subnet
|
||||
key, err := srv.Store().CreateAPIKey(reqUser.TenantID, "all", 0)
|
||||
sessionToken, exp, err := srv.Store().CreateSessionToken(u.ID, reqUser.TenantID, u.Role, 24*time.Hour)
|
||||
if err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, `{"error":1,"message":"create token failed"}`)
|
||||
fmt.Fprintf(w, `{"error":1,"message":"create session failed"}`)
|
||||
return
|
||||
}
|
||||
ten, _ := srv.Store().GetTenantByID(reqUser.TenantID)
|
||||
resp := struct {
|
||||
Error int `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Token string `json:"token"`
|
||||
Role string `json:"role"`
|
||||
Status int `json:"status"`
|
||||
Subnet string `json:"subnet"`
|
||||
}{0, "ok", key, u.Role, u.Status, ""}
|
||||
Error int `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Token string `json:"token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
Role string `json:"role"`
|
||||
Status int `json:"status"`
|
||||
Subnet string `json:"subnet"`
|
||||
}{0, "ok", sessionToken, "session", exp, u.Role, u.Status, ""}
|
||||
if ten != nil {
|
||||
resp.Subnet = ten.Subnet
|
||||
}
|
||||
@@ -289,13 +272,8 @@ func main() {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
// tenant filter by API key
|
||||
tenantID := int64(0)
|
||||
if srv.Store() != nil {
|
||||
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
|
||||
tenantID = ten.ID
|
||||
}
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
if tenantID > 0 {
|
||||
nodes := srv.GetOnlineNodesByTenant(tenantID)
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"nodes": nodes})
|
||||
@@ -311,13 +289,8 @@ func main() {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
// tenant filter by API key
|
||||
tenantID := int64(0)
|
||||
if srv.Store() != nil {
|
||||
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
|
||||
tenantID = ten.ID
|
||||
}
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
if tenantID > 0 {
|
||||
_ = json.NewEncoder(w).Encode(srv.GetSDWANTenant(tenantID))
|
||||
return
|
||||
@@ -335,13 +308,8 @@ func main() {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// tenant filter by API key
|
||||
tenantID := int64(0)
|
||||
if srv.Store() != nil {
|
||||
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
|
||||
tenantID = ten.ID
|
||||
}
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
if tenantID > 0 {
|
||||
if err := srv.SetSDWANTenant(tenantID, req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
@@ -378,13 +346,8 @@ func main() {
|
||||
http.Error(w, "node not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
// tenant filter by API key
|
||||
tenantID := int64(0)
|
||||
if srv.Store() != nil {
|
||||
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
|
||||
tenantID = ten.ID
|
||||
}
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
if tenantID > 0 && node.TenantID != tenantID {
|
||||
http.Error(w, "node not found", http.StatusNotFound)
|
||||
return
|
||||
@@ -413,13 +376,8 @@ func main() {
|
||||
http.Error(w, "node not found or offline", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
// tenant filter by API key
|
||||
tenantID := int64(0)
|
||||
if srv.Store() != nil {
|
||||
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
|
||||
tenantID = ten.ID
|
||||
}
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
if tenantID > 0 && node.TenantID != tenantID {
|
||||
http.Error(w, "node not found", http.StatusNotFound)
|
||||
return
|
||||
@@ -451,13 +409,8 @@ func main() {
|
||||
http.Error(w, "source node offline", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
// tenant filter by API key
|
||||
tenantID := int64(0)
|
||||
if srv.Store() != nil {
|
||||
if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
|
||||
tenantID = ten.ID
|
||||
}
|
||||
}
|
||||
// tenant filter by session/apikey
|
||||
tenantID := getTenantID(r)
|
||||
if tenantID > 0 && fromNode.TenantID != tenantID {
|
||||
http.Error(w, "node not found", http.StatusNotFound)
|
||||
return
|
||||
@@ -529,10 +482,10 @@ func main() {
|
||||
|
||||
// Enable TCP keepalive at server level
|
||||
httpSrv := &http.Server{
|
||||
Handler: mux,
|
||||
Handler: mux,
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
}
|
||||
go func() {
|
||||
if err := httpSrv.Serve(ln); err != http.ErrServerClosed {
|
||||
|
||||
Reference in New Issue
Block a user