feat: scaffold ai gateway identity and design

This commit is contained in:
2026-05-09 16:01:32 +08:00
parent 6323e70e49
commit 5b20f017eb
18 changed files with 3043 additions and 372 deletions
+130 -3
View File
@@ -2,8 +2,10 @@ package httpapi
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
@@ -12,9 +14,10 @@ import (
func (s *Server) health(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{
"ok": true,
"service": "easyai-ai-gateway",
"env": s.cfg.AppEnv,
"ok": true,
"service": "easyai-ai-gateway",
"env": s.cfg.AppEnv,
"identityMode": s.cfg.IdentityMode,
})
}
@@ -31,6 +34,100 @@ func (s *Server) me(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, user)
}
func (s *Server) register(w http.ResponseWriter, r *http.Request) {
if !s.localIdentityEnabled() {
writeError(w, http.StatusForbidden, "local registration is disabled")
return
}
var input store.LocalRegisterInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "invalid json body")
return
}
user, err := s.store.RegisterLocalUser(r.Context(), input)
if err != nil {
if errors.Is(err, store.ErrWeakPassword) {
writeError(w, http.StatusBadRequest, err.Error())
return
}
if errors.Is(err, store.ErrInvalidInvitation) {
writeError(w, http.StatusBadRequest, err.Error())
return
}
s.logger.Error("register local user failed", "error", err)
writeError(w, http.StatusConflict, "user already exists or tenant is unavailable")
return
}
s.writeAuthResponse(w, http.StatusCreated, user)
}
func (s *Server) login(w http.ResponseWriter, r *http.Request) {
if !s.localIdentityEnabled() {
writeError(w, http.StatusForbidden, "local login is disabled")
return
}
var input store.LocalLoginInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "invalid json body")
return
}
user, err := s.store.AuthenticateLocalUser(r.Context(), input)
if err != nil {
if errors.Is(err, store.ErrInvalidCredentials) {
writeError(w, http.StatusUnauthorized, "invalid account or password")
return
}
s.logger.Error("login local user failed", "error", err)
writeError(w, http.StatusInternalServerError, "login failed")
return
}
s.writeAuthResponse(w, http.StatusOK, user)
}
func (s *Server) localIdentityEnabled() bool {
mode := strings.ToLower(strings.TrimSpace(s.cfg.IdentityMode))
return mode == "" || mode == "standalone" || mode == "hybrid"
}
func (s *Server) writeAuthResponse(w http.ResponseWriter, status int, user store.GatewayUser) {
authUser := authUserFromGatewayUser(user)
const ttl = 24 * time.Hour
token, err := s.auth.SignJWT(authUser, ttl)
if err != nil {
s.logger.Error("sign local jwt failed", "error", err)
writeError(w, http.StatusInternalServerError, "token sign failed")
return
}
writeJSON(w, status, map[string]any{
"accessToken": token,
"tokenType": "Bearer",
"expiresIn": int(ttl.Seconds()),
"user": authUser,
})
}
func authUserFromGatewayUser(user store.GatewayUser) *auth.User {
roles := user.Roles
if len(roles) == 0 {
roles = []string{"user"}
}
tenantID := user.TenantID
if tenantID == "" {
tenantID = user.TenantKey
}
return &auth.User{
ID: user.ID,
Username: user.Username,
Roles: roles,
TenantID: tenantID,
GatewayTenantID: user.GatewayTenantID,
TenantKey: user.TenantKey,
Source: "gateway",
GatewayUserID: user.ID,
UserGroupID: user.DefaultUserGroupID,
}
}
func (s *Server) listPlatforms(w http.ResponseWriter, r *http.Request) {
platforms, err := s.store.ListPlatforms(r.Context())
if err != nil {
@@ -103,6 +200,36 @@ func (s *Server) listPricingRules(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
func (s *Server) listTenants(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListTenants(r.Context())
if err != nil {
s.logger.Error("list tenants failed", "error", err)
writeError(w, http.StatusInternalServerError, "list tenants failed")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
func (s *Server) listUsers(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListUsers(r.Context())
if err != nil {
s.logger.Error("list users failed", "error", err)
writeError(w, http.StatusInternalServerError, "list users failed")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
func (s *Server) listUserGroups(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListUserGroups(r.Context())
if err != nil {
s.logger.Error("list user groups failed", "error", err)
writeError(w, http.StatusInternalServerError, "list user groups failed")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
func (s *Server) estimatePricing(w http.ResponseWriter, r *http.Request) {
var body map[string]any
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {