mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
auth_proxy still proxied login/signup/userinfo/send-verification-code at a prefix the edge refuses with 403, while iam.go beside it had already moved. One dialect now, matching the rest of the package. The guard reads string literals through go/ast rather than grepping lines, so a comment recording a dead upstream path stays documentation and only a real route can fail the build. Co-authored-by: Hanzo Dev <[email protected]>
173 lines
4.9 KiB
Go
173 lines
4.9 KiB
Go
package platform
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hanzoai/base/core"
|
|
"github.com/hanzoai/base/tools/router"
|
|
)
|
|
|
|
// AuthProxyConfig holds the subset of PlatformConfig needed by the auth proxy.
|
|
type AuthProxyConfig struct {
|
|
IAMEndpoint string
|
|
IAMOrg string
|
|
IAMApp string
|
|
}
|
|
|
|
// registerAuthRoutes registers IAM auth proxy routes under /v1/platform/auth.
|
|
func (p *plugin) registerAuthRoutes(r *router.Router[*core.RequestEvent]) {
|
|
auth := r.Group("/v1/platform/auth")
|
|
|
|
auth.POST("/verify-phone", p.handleVerifyPhone)
|
|
auth.POST("/verify-code", p.handleVerifyCode)
|
|
auth.POST("/login", p.handleLogin)
|
|
auth.POST("/signup", p.handleSignup)
|
|
auth.GET("/userinfo", p.handleUserinfo)
|
|
}
|
|
|
|
// proxyToIAM forwards a request to the IAM service and copies the response back.
|
|
func (p *plugin) proxyToIAM(e *core.RequestEvent, method, path string, body []byte) error {
|
|
endpoint := strings.TrimRight(p.config.IAMEndpoint, "/")
|
|
url := endpoint + path
|
|
|
|
var reqBody io.Reader
|
|
if body != nil {
|
|
reqBody = strings.NewReader(string(body))
|
|
}
|
|
|
|
client := &http.Client{Timeout: 15 * time.Second}
|
|
|
|
req, err := http.NewRequestWithContext(e.Request.Context(), method, url, reqBody)
|
|
if err != nil {
|
|
p.app.Logger().Error("auth proxy: failed to build request",
|
|
"path", path,
|
|
"error", err.Error(),
|
|
)
|
|
return e.JSON(http.StatusBadGateway, map[string]any{"message": "proxy error"})
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Forward auth headers from the original request.
|
|
if v := e.Request.Header.Get("Authorization"); v != "" {
|
|
req.Header.Set("Authorization", v)
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
p.app.Logger().Warn("auth proxy: IAM request failed",
|
|
"path", path,
|
|
"error", err.Error(),
|
|
)
|
|
return e.JSON(http.StatusBadGateway, map[string]any{"message": "IAM unavailable"})
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Copy all response headers back.
|
|
for k, vv := range resp.Header {
|
|
for _, v := range vv {
|
|
e.Response.Header().Add(k, v)
|
|
}
|
|
}
|
|
|
|
// Copy status and body.
|
|
e.Response.WriteHeader(resp.StatusCode)
|
|
io.Copy(e.Response, resp.Body)
|
|
return nil
|
|
}
|
|
|
|
// handleVerifyPhone proxies a phone verification request to IAM.
|
|
//
|
|
// Maps { countryCode, phone } to IAM /v1/iam/send-verification-code.
|
|
func (p *plugin) handleVerifyPhone(e *core.RequestEvent) error {
|
|
raw, err := io.ReadAll(e.Request.Body)
|
|
if err != nil {
|
|
return e.JSON(http.StatusBadRequest, map[string]any{"message": "failed to read request body"})
|
|
}
|
|
|
|
var body struct {
|
|
CountryCode string `json:"countryCode"`
|
|
Phone string `json:"phone"`
|
|
}
|
|
if err := json.Unmarshal(raw, &body); err != nil {
|
|
return e.JSON(http.StatusBadRequest, map[string]any{"message": "invalid request body"})
|
|
}
|
|
|
|
phone := strings.TrimSpace(body.Phone)
|
|
if phone == "" {
|
|
return e.JSON(http.StatusBadRequest, map[string]any{"message": "phone is required"})
|
|
}
|
|
|
|
iamBody, _ := json.Marshal(map[string]any{
|
|
"applicationId": p.config.IAMApp,
|
|
"dest": phone,
|
|
"type": "phone",
|
|
"countryCode": body.CountryCode,
|
|
"method": "login",
|
|
})
|
|
|
|
return p.proxyToIAM(e, http.MethodPost, "/v1/iam/send-verification-code", iamBody)
|
|
}
|
|
|
|
// handleVerifyCode proxies OTP verification to IAM login.
|
|
//
|
|
// Maps { phone, code } to IAM /v1/iam/login.
|
|
func (p *plugin) handleVerifyCode(e *core.RequestEvent) error {
|
|
raw, err := io.ReadAll(e.Request.Body)
|
|
if err != nil {
|
|
return e.JSON(http.StatusBadRequest, map[string]any{"message": "failed to read request body"})
|
|
}
|
|
|
|
var body struct {
|
|
Phone string `json:"phone"`
|
|
Code string `json:"code"`
|
|
}
|
|
if err := json.Unmarshal(raw, &body); err != nil {
|
|
return e.JSON(http.StatusBadRequest, map[string]any{"message": "invalid request body"})
|
|
}
|
|
|
|
phone := strings.TrimSpace(body.Phone)
|
|
code := strings.TrimSpace(body.Code)
|
|
if phone == "" || code == "" {
|
|
return e.JSON(http.StatusBadRequest, map[string]any{"message": "phone and code are required"})
|
|
}
|
|
|
|
iamBody, _ := json.Marshal(map[string]any{
|
|
"application": p.config.IAMApp,
|
|
"organization": p.config.IAMOrg,
|
|
"username": phone,
|
|
"code": code,
|
|
"type": "code",
|
|
})
|
|
|
|
return p.proxyToIAM(e, http.MethodPost, "/v1/iam/login", iamBody)
|
|
}
|
|
|
|
// handleLogin proxies login credentials to IAM.
|
|
func (p *plugin) handleLogin(e *core.RequestEvent) error {
|
|
raw, err := io.ReadAll(e.Request.Body)
|
|
if err != nil {
|
|
return e.JSON(http.StatusBadRequest, map[string]any{"message": "failed to read request body"})
|
|
}
|
|
|
|
return p.proxyToIAM(e, http.MethodPost, "/v1/iam/login", raw)
|
|
}
|
|
|
|
// handleSignup proxies signup to IAM.
|
|
func (p *plugin) handleSignup(e *core.RequestEvent) error {
|
|
raw, err := io.ReadAll(e.Request.Body)
|
|
if err != nil {
|
|
return e.JSON(http.StatusBadRequest, map[string]any{"message": "failed to read request body"})
|
|
}
|
|
|
|
return p.proxyToIAM(e, http.MethodPost, "/v1/iam/signup", raw)
|
|
}
|
|
|
|
// handleUserinfo proxies a userinfo request to IAM.
|
|
func (p *plugin) handleUserinfo(e *core.RequestEvent) error {
|
|
return p.proxyToIAM(e, http.MethodGet, "/v1/iam/userinfo", nil)
|
|
}
|