From fa68621b41ab4a9c663db1813e2f74b474dfbce7 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Sun, 18 Jun 2023 09:38:21 +0800 Subject: [PATCH] support api proxy for amap --- cmd/webserver.go | 50 ++++++++---- conf/ezbookkeeping.ini | 13 +++- pkg/api/amap_api_proxies.go | 60 ++++++++++++++ pkg/api/authorizations.go | 6 ++ pkg/api/map_image_proxies.go | 5 +- pkg/api/tokens.go | 1 + pkg/api/twofactor_authorizations.go | 1 + pkg/api/users.go | 2 + pkg/core/context.go | 19 ++++- pkg/core/handler.go | 2 +- pkg/middlewares/amap_api_proxy_auth_cookie.go | 19 +++++ pkg/middlewares/authorization.go | 78 +++++++++---------- pkg/middlewares/server_settings_cookie.go | 36 ++++++--- pkg/services/tokens.go | 5 ++ pkg/settings/setting.go | 24 ++++-- pkg/utils/extractor.go | 20 +++++ src/consts/api.js | 2 + src/lib/map/amap.js | 12 ++- src/lib/services.js | 3 + src/lib/settings.js | 21 ++++- vite.config.js | 4 + 21 files changed, 289 insertions(+), 94 deletions(-) create mode 100644 pkg/api/amap_api_proxies.go create mode 100644 pkg/middlewares/amap_api_proxy_auth_cookie.go create mode 100644 pkg/utils/extractor.go diff --git a/cmd/webserver.go b/cmd/webserver.go index d7a29321..67785754 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -152,27 +152,35 @@ func startWebServer(c *cli.Context) error { } } + if config.MapProvider == settings.AmapProvider && config.AmapSecurityVerificationMethod == settings.AmapSecurityVerificationInternalProxyMethod { + amapApiProxyRoute := router.Group("/_AMapService") + amapApiProxyRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByCookie)) + { + amapApiProxyRoute.GET("/*action", bindProxy(api.AmapApis.AmapApiProxyHandler)) + } + } + apiRoute := router.Group("/api") apiRoute.Use(bindMiddleware(middlewares.RequestId(config))) apiRoute.Use(bindMiddleware(middlewares.RequestLog)) { - apiRoute.POST("/authorize.json", bindApi(api.Authorizations.AuthorizeHandler)) + apiRoute.POST("/authorize.json", bindApiWithTokenUpdate(api.Authorizations.AuthorizeHandler, config)) if config.EnableTwoFactor { twoFactorRoute := apiRoute.Group("/2fa") twoFactorRoute.Use(bindMiddleware(middlewares.JWTTwoFactorAuthorization)) { - twoFactorRoute.POST("/authorize.json", bindApi(api.Authorizations.TwoFactorAuthorizeHandler)) - twoFactorRoute.POST("/recovery.json", bindApi(api.Authorizations.TwoFactorAuthorizeByRecoveryCodeHandler)) + twoFactorRoute.POST("/authorize.json", bindApiWithTokenUpdate(api.Authorizations.TwoFactorAuthorizeHandler, config)) + twoFactorRoute.POST("/recovery.json", bindApiWithTokenUpdate(api.Authorizations.TwoFactorAuthorizeByRecoveryCodeHandler, config)) } } if config.EnableUserRegister { - apiRoute.POST("/register.json", bindApi(api.Users.UserRegisterHandler)) + apiRoute.POST("/register.json", bindApiWithTokenUpdate(api.Users.UserRegisterHandler, config)) } - apiRoute.GET("/logout.json", bindApi(api.Tokens.TokenRevokeCurrentHandler)) + apiRoute.GET("/logout.json", bindApiWithTokenUpdate(api.Tokens.TokenRevokeCurrentHandler, config)) apiV1Route := apiRoute.Group("/v1") apiV1Route.Use(bindMiddleware(middlewares.JWTAuthorization)) @@ -181,17 +189,17 @@ func startWebServer(c *cli.Context) error { apiV1Route.GET("/tokens/list.json", bindApi(api.Tokens.TokenListHandler)) apiV1Route.POST("/tokens/revoke.json", bindApi(api.Tokens.TokenRevokeHandler)) apiV1Route.POST("/tokens/revoke_all.json", bindApi(api.Tokens.TokenRevokeAllHandler)) - apiV1Route.POST("/tokens/refresh.json", bindApi(api.Tokens.TokenRefreshHandler)) + apiV1Route.POST("/tokens/refresh.json", bindApiWithTokenUpdate(api.Tokens.TokenRefreshHandler, config)) // Users apiV1Route.GET("/users/profile/get.json", bindApi(api.Users.UserProfileHandler)) - apiV1Route.POST("/users/profile/update.json", bindApi(api.Users.UserUpdateProfileHandler)) + apiV1Route.POST("/users/profile/update.json", bindApiWithTokenUpdate(api.Users.UserUpdateProfileHandler, config)) // Two Factor Authorization if config.EnableTwoFactor { apiV1Route.GET("/users/2fa/status.json", bindApi(api.TwoFactorAuthorizations.TwoFactorStatusHandler)) apiV1Route.POST("/users/2fa/enable/request.json", bindApi(api.TwoFactorAuthorizations.TwoFactorEnableRequestHandler)) - apiV1Route.POST("/users/2fa/enable/confirm.json", bindApi(api.TwoFactorAuthorizations.TwoFactorEnableConfirmHandler)) + apiV1Route.POST("/users/2fa/enable/confirm.json", bindApiWithTokenUpdate(api.TwoFactorAuthorizations.TwoFactorEnableConfirmHandler, config)) apiV1Route.POST("/users/2fa/disable.json", bindApi(api.TwoFactorAuthorizations.TwoFactorDisableHandler)) apiV1Route.POST("/users/2fa/recovery/regenerate.json", bindApi(api.TwoFactorAuthorizations.TwoFactorRecoveryCodeRegenerateHandler)) } @@ -291,6 +299,23 @@ func bindApi(fn core.ApiHandlerFunc) gin.HandlerFunc { } } +func bindApiWithTokenUpdate(fn core.ApiHandlerFunc, config *settings.Config) gin.HandlerFunc { + return func(ginCtx *gin.Context) { + c := core.WrapContext(ginCtx) + result, err := fn(c) + + if err == nil && config.MapProvider == settings.AmapProvider && config.AmapSecurityVerificationMethod == settings.AmapSecurityVerificationInternalProxyMethod { + middlewares.AmapApiProxyAuthCookie(c, config) + } + + if err != nil { + utils.PrintJsonErrorResult(c, err) + } else { + utils.PrintJsonSuccessResult(c, result) + } + } +} + func bindCsv(fn core.DataHandlerFunc) gin.HandlerFunc { return func(ginCtx *gin.Context) { c := core.WrapContext(ginCtx) @@ -307,12 +332,7 @@ func bindCsv(fn core.DataHandlerFunc) gin.HandlerFunc { func bindProxy(fn core.ProxyHandlerFunc) gin.HandlerFunc { return func(ginCtx *gin.Context) { c := core.WrapContext(ginCtx) - proxy, err := fn(c) - - if err != nil { - utils.PrintDataErrorResult(c, "text/text", err) - } else { - proxy.ServeHTTP(c.Writer, c.Request) - } + proxy := fn(c) + proxy.ServeHTTP(c.Writer, c.Request) } } diff --git a/conf/ezbookkeeping.ini b/conf/ezbookkeeping.ini index 671edeba..22e1deab 100644 --- a/conf/ezbookkeeping.ini +++ b/conf/ezbookkeeping.ini @@ -126,12 +126,19 @@ baidu_map_ak = # For "amap" only, Amap JavaScript API application key, please visit https://lbs.amap.com/api/javascript-api/guide/abc/prepare for more information amap_application_key = -# For "amap" only, Amap JavaScript API security verification method, supports "plain" (not recommend). -amap_security_verification_method = plain +# For "amap" only, Amap JavaScript API security verification method, supports the following methods: +# "internal_proxy": use the internal proxy to request amap api with amap application secret (default) +# "external_proxy": use an external proxy to request amap api (amap application secret should be set by external proxy) +# "plain_text": append amap application secret directly to frontend request (insecurity for public network) +# Please visit https://developer.amap.com/api/jsapi-v2/guide/abc/load for more information +amap_security_verification_method = plain_text -# For "amap" only, Amap JavaScript API application secret, this setting must be provided when "amap_security_verification_method" is set to "plain", please visit https://lbs.amap.com/api/javascript-api/guide/abc/prepare for more information +# For "amap" only, Amap JavaScript API application secret, this setting must be provided when "amap_security_verification_method" is set to "internal_proxy" or "plain_text", please visit https://lbs.amap.com/api/javascript-api/guide/abc/prepare for more information amap_application_secret = +# For "amap" only, Amap JavaScript API external proxy url, this setting must be provided when "amap_security_verification_method" is set to "external_proxy" +amap_api_external_proxy_url = + [exchange_rates] # Exchange rates data source, supports the following types: # "euro_central_bank" diff --git a/pkg/api/amap_api_proxies.go b/pkg/api/amap_api_proxies.go new file mode 100644 index 00000000..004c7ec0 --- /dev/null +++ b/pkg/api/amap_api_proxies.go @@ -0,0 +1,60 @@ +package api + +import ( + "fmt" + "net/http" + "net/http/httputil" + "net/url" + "strings" + + "github.com/mayswind/ezbookkeeping/pkg/core" + "github.com/mayswind/ezbookkeeping/pkg/settings" +) + +const amapCustomMapStylesUrl = "https://webapi.amap.com/v4/map/styles" +const amapOverseasMapUrl = "https://fmap01.amap.com/v3/vectormap" +const amapRestApiUrl = "https://restapi.amap.com/" + +// AmapApiProxy represents amap api proxy +type AmapApiProxy struct { +} + +// Initialize a amap api proxy singleton instance +var ( + AmapApis = &AmapApiProxy{} +) + +// AmapApiProxyHandler returns amap api response +func (p *AmapApiProxy) AmapApiProxyHandler(c *core.Context) *httputil.ReverseProxy { + var targetUrl string + + if strings.HasPrefix(c.Request.RequestURI, "/_AMapService/v4/map/styles") { + targetUrl = amapCustomMapStylesUrl + strings.TrimPrefix(c.Request.URL.Path, "/_AMapService/v4/map/styles") + } else if strings.HasPrefix(c.Request.RequestURI, "/_AMapService/v3/vectormap") { + targetUrl = amapOverseasMapUrl + strings.TrimPrefix(c.Request.URL.Path, "/_AMapService/v3/vectormap") + } else { + targetUrl = amapRestApiUrl + strings.TrimPrefix(c.Request.URL.Path, "/_AMapService/") + } + + director := func(req *http.Request) { + targetRawUrl := fmt.Sprintf("%s?%s&jscode=%s", targetUrl, req.URL.RawQuery, settings.Container.Current.AmapApplicationSecret) + targetUrl, _ := url.Parse(targetRawUrl) + + oldCookies := req.Cookies() + req.Header.Del("Cookie") + + for i := 0; i < len(oldCookies); i++ { + if strings.HasPrefix(oldCookies[i].Name, "ebk_") { + continue + } + + req.AddCookie(oldCookies[i]) + } + + req.URL = targetUrl + req.RequestURI = req.URL.RequestURI() + req.Host = targetUrl.Host + } + + return &httputil.ReverseProxy{Director: director} +} diff --git a/pkg/api/authorizations.go b/pkg/api/authorizations.go index 3c474dd3..33147cfe 100644 --- a/pkg/api/authorizations.go +++ b/pkg/api/authorizations.go @@ -74,6 +74,10 @@ func (a *AuthorizationsApi) AuthorizeHandler(c *core.Context) (interface{}, *err return nil, errs.ErrTokenGenerating } + if !twoFactorEnable { + c.SetTextualToken(token) + } + c.SetTokenClaims(claims) log.InfofWithRequestId(c, "[authorizations.AuthorizeHandler] user \"uid:%d\" has logined, token type is %d, token will be expired at %d", user.Uid, claims.Type, claims.ExpiresAt) @@ -126,6 +130,7 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeHandler(c *core.Context) (interfac return nil, errs.ErrTokenGenerating } + c.SetTextualToken(token) c.SetTokenClaims(claims) log.InfofWithRequestId(c, "[authorizations.TwoFactorAuthorizeHandler] user \"uid:%d\" has authorized two factor via passcode, token will be expired at %d", user.Uid, claims.ExpiresAt) @@ -184,6 +189,7 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.Cont return nil, errs.ErrTokenGenerating } + c.SetTextualToken(token) c.SetTokenClaims(claims) log.InfofWithRequestId(c, "[authorizations.TwoFactorAuthorizeByRecoveryCodeHandler] user \"uid:%d\" has authorized two factor via recovery code \"%s\", token will be expired at %d", user.Uid, credential.RecoveryCode, claims.ExpiresAt) diff --git a/pkg/api/map_image_proxies.go b/pkg/api/map_image_proxies.go index 4a63928c..733aacbd 100644 --- a/pkg/api/map_image_proxies.go +++ b/pkg/api/map_image_proxies.go @@ -7,7 +7,6 @@ import ( "net/url" "github.com/mayswind/ezbookkeeping/pkg/core" - "github.com/mayswind/ezbookkeeping/pkg/errs" ) const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/%s/%s/%s" // https://tile.openstreetmap.org/{z}/{x}/{y}.png @@ -22,7 +21,7 @@ var ( ) // OpenStreetMapTileImageProxyHandler returns open street map tile image -func (p *MapImageProxy) OpenStreetMapTileImageProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) { +func (p *MapImageProxy) OpenStreetMapTileImageProxyHandler(c *core.Context) *httputil.ReverseProxy { director := func(req *http.Request) { zoomLevel := c.Param("zoomLevel") coordinateX := c.Param("coordinateX") @@ -36,5 +35,5 @@ func (p *MapImageProxy) OpenStreetMapTileImageProxyHandler(c *core.Context) (*ht req.Host = imageUrl.Host } - return &httputil.ReverseProxy{Director: director}, nil + return &httputil.ReverseProxy{Director: director} } diff --git a/pkg/api/tokens.go b/pkg/api/tokens.go index ecbc4c62..73b3a2b0 100644 --- a/pkg/api/tokens.go +++ b/pkg/api/tokens.go @@ -191,6 +191,7 @@ func (a *TokensApi) TokenRefreshHandler(c *core.Context) (interface{}, *errs.Err CreatedUnixTime: oldTokenClaims.IssuedAt, } + c.SetTextualToken(token) c.SetTokenClaims(claims) log.InfofWithRequestId(c, "[token.TokenRefreshHandler] user \"uid:%d\" token refreshed, new token will be expired at %d", user.Uid, claims.ExpiresAt) diff --git a/pkg/api/twofactor_authorizations.go b/pkg/api/twofactor_authorizations.go index 9fa159c8..9cdd71a9 100644 --- a/pkg/api/twofactor_authorizations.go +++ b/pkg/api/twofactor_authorizations.go @@ -195,6 +195,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.Conte return confirmResp, nil } + c.SetTextualToken(token) c.SetTokenClaims(claims) log.InfofWithRequestId(c, "[twofactor_authorizations.TwoFactorEnableConfirmHandler] user \"uid:%d\" token refreshed, new token will be expired at %d", user.Uid, claims.ExpiresAt) diff --git a/pkg/api/users.go b/pkg/api/users.go index 0a8e9c3b..321aea84 100644 --- a/pkg/api/users.go +++ b/pkg/api/users.go @@ -85,6 +85,7 @@ func (a *UsersApi) UserRegisterHandler(c *core.Context) (interface{}, *errs.Erro } authResp.Token = token + c.SetTextualToken(token) c.SetTokenClaims(claims) log.InfofWithRequestId(c, "[users.UserRegisterHandler] user \"uid:%d\" has logined, token will be expired at %d", user.Uid, claims.ExpiresAt) @@ -272,6 +273,7 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs } resp.NewToken = token + c.SetTextualToken(token) c.SetTokenClaims(claims) log.InfofWithRequestId(c, "[users.UserUpdateProfileHandler] user \"uid:%d\" token refreshed, new token will be expired at %d", user.Uid, claims.ExpiresAt) diff --git a/pkg/core/context.go b/pkg/core/context.go index 8354c96c..d05f5233 100644 --- a/pkg/core/context.go +++ b/pkg/core/context.go @@ -9,6 +9,7 @@ import ( ) const requestIdFieldKey = "REQUEST_ID" +const textualTokenFieldKey = "TOKEN_STRING" const tokenClaimsFieldKey = "TOKEN_CLAIMS" const responseErrorFieldKey = "RESPONSE_ERROR" @@ -37,7 +38,23 @@ func (c *Context) GetRequestId() string { return requestId.(string) } -// SetTokenClaims sets the given user token id to context +// SetTextualToken sets the given user token to context +func (c *Context) SetTextualToken(token string) { + c.Set(textualTokenFieldKey, token) +} + +// GetTextualToken returns the current user textual token +func (c *Context) GetTextualToken() string { + token, exists := c.Get(textualTokenFieldKey) + + if !exists { + return "" + } + + return token.(string) +} + +// SetTokenClaims sets the given user token to context func (c *Context) SetTokenClaims(claims *UserTokenClaims) { c.Set(tokenClaimsFieldKey, claims) } diff --git a/pkg/core/handler.go b/pkg/core/handler.go index 9a88140a..9dfbe5e0 100644 --- a/pkg/core/handler.go +++ b/pkg/core/handler.go @@ -16,4 +16,4 @@ type ApiHandlerFunc func(*Context) (interface{}, *errs.Error) type DataHandlerFunc func(*Context) ([]byte, string, *errs.Error) // ProxyHandlerFunc represents the reverse proxy handler function -type ProxyHandlerFunc func(*Context) (*httputil.ReverseProxy, *errs.Error) +type ProxyHandlerFunc func(*Context) *httputil.ReverseProxy diff --git a/pkg/middlewares/amap_api_proxy_auth_cookie.go b/pkg/middlewares/amap_api_proxy_auth_cookie.go new file mode 100644 index 00000000..3bd313a3 --- /dev/null +++ b/pkg/middlewares/amap_api_proxy_auth_cookie.go @@ -0,0 +1,19 @@ +package middlewares + +import ( + "github.com/mayswind/ezbookkeeping/pkg/core" + "github.com/mayswind/ezbookkeeping/pkg/settings" +) + +const tokenCookieParam = "ebk_auth_token" + +// AmapApiProxyAuthCookie adds amap api proxy auth cookie to cookies in response +func AmapApiProxyAuthCookie(c *core.Context, config *settings.Config) { + token := c.GetTextualToken() + + if token != "" { + c.SetCookie(tokenCookieParam, token, int(config.TokenExpiredTime), "/_AMapService", "", false, true) + } else { + c.SetCookie(tokenCookieParam, "", -1, "/_AMapService", "", false, true) + } +} diff --git a/pkg/middlewares/authorization.go b/pkg/middlewares/authorization.go index 33263e42..68c44e0d 100644 --- a/pkg/middlewares/authorization.go +++ b/pkg/middlewares/authorization.go @@ -17,58 +17,24 @@ type TokenSourceType byte const ( TOKEN_SOURCE_TYPE_HEADER TokenSourceType = 1 TOKEN_SOURCE_TYPE_ARGUMENT TokenSourceType = 2 + TOKEN_SOURCE_TYPE_COOKIE TokenSourceType = 3 ) const tokenQueryStringParam = "token" -// JWTAuthorization verifies whether current request is valid by jwt token +// JWTAuthorization verifies whether current request is valid by jwt token in header func JWTAuthorization(c *core.Context) { - claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_HEADER) - - if err != nil { - utils.PrintJsonErrorResult(c, err) - return - } - - if claims.Type == core.USER_TOKEN_TYPE_REQUIRE_2FA { - log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%d\" token requires 2fa", claims.Uid) - utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenRequire2FA) - return - } - - if claims.Type != core.USER_TOKEN_TYPE_NORMAL { - log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%d\" token type is invalid", claims.Uid) - utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidTokenType) - return - } - - c.SetTokenClaims(claims) - c.Next() + jwtAuthorization(c, TOKEN_SOURCE_TYPE_HEADER) } -// JWTAuthorizationByQueryString verifies whether current request is valid by jwt token +// JWTAuthorizationByQueryString verifies whether current request is valid by jwt token in query string func JWTAuthorizationByQueryString(c *core.Context) { - claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_ARGUMENT) + jwtAuthorization(c, TOKEN_SOURCE_TYPE_ARGUMENT) +} - if err != nil { - utils.PrintJsonErrorResult(c, err) - return - } - - if claims.Type == core.USER_TOKEN_TYPE_REQUIRE_2FA { - log.WarnfWithRequestId(c, "[authorization.JWTAuthorizationByQueryString] user \"uid:%d\" token requires 2fa", claims.Uid) - utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenRequire2FA) - return - } - - if claims.Type != core.USER_TOKEN_TYPE_NORMAL { - log.WarnfWithRequestId(c, "[authorization.JWTAuthorizationByQueryString] user \"uid:%d\" token type is invalid", claims.Uid) - utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidTokenType) - return - } - - c.SetTokenClaims(claims) - c.Next() +// JWTAuthorizationByCookie verifies whether current request is valid by jwt token in cookie +func JWTAuthorizationByCookie(c *core.Context) { + jwtAuthorization(c, TOKEN_SOURCE_TYPE_COOKIE) } // JWTTwoFactorAuthorization verifies whether current request is valid by 2fa passcode @@ -90,6 +56,30 @@ func JWTTwoFactorAuthorization(c *core.Context) { c.Next() } +func jwtAuthorization(c *core.Context, source TokenSourceType) { + claims, err := getTokenClaims(c, source) + + if err != nil { + utils.PrintJsonErrorResult(c, err) + return + } + + if claims.Type == core.USER_TOKEN_TYPE_REQUIRE_2FA { + log.WarnfWithRequestId(c, "[authorization.jwtAuthorization] user \"uid:%d\" token requires 2fa", claims.Uid) + utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenRequire2FA) + return + } + + if claims.Type != core.USER_TOKEN_TYPE_NORMAL { + log.WarnfWithRequestId(c, "[authorization.jwtAuthorization] user \"uid:%d\" token type is invalid", claims.Uid) + utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidTokenType) + return + } + + c.SetTokenClaims(claims) + c.Next() +} + func getTokenClaims(c *core.Context, source TokenSourceType) (*core.UserTokenClaims, *errs.Error) { token, claims, err := parseToken(c, source) @@ -114,6 +104,8 @@ func getTokenClaims(c *core.Context, source TokenSourceType) (*core.UserTokenCla func parseToken(c *core.Context, source TokenSourceType) (*jwt.Token, *core.UserTokenClaims, error) { if source == TOKEN_SOURCE_TYPE_ARGUMENT { return services.Tokens.ParseTokenByArgument(c, tokenQueryStringParam) + } else if source == TOKEN_SOURCE_TYPE_COOKIE { + return services.Tokens.ParseTokenByCookie(c, tokenCookieParam) } return services.Tokens.ParseTokenByHeader(c) diff --git a/pkg/middlewares/server_settings_cookie.go b/pkg/middlewares/server_settings_cookie.go index 231a61ba..49cada0c 100644 --- a/pkg/middlewares/server_settings_cookie.go +++ b/pkg/middlewares/server_settings_cookie.go @@ -1,7 +1,9 @@ package middlewares import ( + "encoding/base64" "fmt" + "net/url" "strings" "github.com/mayswind/ezbookkeeping/pkg/core" @@ -19,27 +21,31 @@ func ServerSettingsCookie(config *settings.Config) core.MiddlewareHandlerFunc { buildStringSetting("m", config.MapProvider), } - if config.EnableMapDataFetchProxy { + if config.MapProvider == settings.OpenStreetMapProvider && config.EnableMapDataFetchProxy { settingsArr = append(settingsArr, buildBooleanSetting("mp", config.EnableMapDataFetchProxy)) } - if config.GoogleMapAPIKey != "" { - settingsArr = append(settingsArr, buildStringSetting("gmak", config.GoogleMapAPIKey)) + if config.MapProvider == settings.GoogleMapProvider && config.GoogleMapAPIKey != "" { + settingsArr = append(settingsArr, buildEncodedStringSetting("gmak", config.GoogleMapAPIKey)) } - if config.BaiduMapAK != "" { - settingsArr = append(settingsArr, buildStringSetting("bmak", config.BaiduMapAK)) + if config.MapProvider == settings.BaiduMapProvider && config.BaiduMapAK != "" { + settingsArr = append(settingsArr, buildEncodedStringSetting("bmak", config.BaiduMapAK)) } - if config.AMapApplicationKey != "" { - settingsArr = append(settingsArr, buildStringSetting("amak", config.AMapApplicationKey)) + if config.MapProvider == settings.AmapProvider && config.AmapApplicationKey != "" { + settingsArr = append(settingsArr, buildEncodedStringSetting("amak", config.AmapApplicationKey)) } - if config.AMapSecurityVerificationMethod != "" { - settingsArr = append(settingsArr, buildStringSetting("amsv", config.AMapSecurityVerificationMethod)) + if config.MapProvider == settings.AmapProvider && config.AmapSecurityVerificationMethod != "" { + settingsArr = append(settingsArr, buildStringSetting("amsv", strings.Replace(config.AmapSecurityVerificationMethod, "_", "", -1))) - if config.AMapSecurityVerificationMethod == settings.AmapSecurityVerificationPlainMethod { - settingsArr = append(settingsArr, buildStringSetting("amas", config.AMapApplicationSecret)) + if config.AmapSecurityVerificationMethod == settings.AmapSecurityVerificationExternalProxyMethod { + settingsArr = append(settingsArr, buildEncodedStringSetting("amep", config.AmapApiExternalProxyUrl)) + } + + if config.AmapSecurityVerificationMethod == settings.AmapSecurityVerificationPlainTextMethod { + settingsArr = append(settingsArr, buildEncodedStringSetting("amas", config.AmapApplicationSecret)) } } @@ -51,7 +57,13 @@ func ServerSettingsCookie(config *settings.Config) core.MiddlewareHandlerFunc { } func buildStringSetting(key string, value string) string { - return fmt.Sprintf("%s.%s", key, strings.Replace(value, ".", "-", -1)) + return fmt.Sprintf("%s.%s", key, value) +} + +func buildEncodedStringSetting(key string, value string) string { + urlEncodedValue := url.QueryEscape(value) + base64Value := base64.StdEncoding.EncodeToString([]byte(urlEncodedValue)) + return fmt.Sprintf("%s.%s", key, base64Value) } func buildBooleanSetting(key string, value bool) string { diff --git a/pkg/services/tokens.go b/pkg/services/tokens.go index 6f695e33..ba7905ee 100644 --- a/pkg/services/tokens.go +++ b/pkg/services/tokens.go @@ -73,6 +73,11 @@ func (s *TokenService) ParseTokenByArgument(c *core.Context, tokenParameterName return s.parseToken(c, request.ArgumentExtractor{tokenParameterName}) } +// ParseTokenByCookie returns the token model according to request data +func (s *TokenService) ParseTokenByCookie(c *core.Context, tokenCookieName string) (*jwt.Token, *core.UserTokenClaims, error) { + return s.parseToken(c, utils.CookieExtractor{tokenCookieName}) +} + // CreateToken generates a new normal token and saves to database func (s *TokenService) CreateToken(user *models.User, ctx *core.Context) (string, *core.UserTokenClaims, error) { return s.createToken(user, core.USER_TOKEN_TYPE_NORMAL, s.getUserAgent(ctx), s.CurrentConfig().TokenExpiredTimeDuration) diff --git a/pkg/settings/setting.go b/pkg/settings/setting.go index 023c9eef..4e52cfd9 100644 --- a/pkg/settings/setting.go +++ b/pkg/settings/setting.go @@ -72,7 +72,9 @@ const ( // Amap security verification method const ( - AmapSecurityVerificationPlainMethod string = "plain" + AmapSecurityVerificationInternalProxyMethod string = "internal_proxy" + AmapSecurityVerificationExternalProxyMethod string = "external_proxy" + AmapSecurityVerificationPlainTextMethod string = "plain_text" ) // Exchange rates data source types @@ -186,9 +188,10 @@ type Config struct { MapProvider string GoogleMapAPIKey string BaiduMapAK string - AMapApplicationKey string - AMapSecurityVerificationMethod string - AMapApplicationSecret string + AmapApplicationKey string + AmapSecurityVerificationMethod string + AmapApplicationSecret string + AmapApiExternalProxyUrl string EnableMapDataFetchProxy bool // Exchange Rates @@ -462,15 +465,20 @@ func loadMapConfiguration(config *Config, configFile *ini.File, sectionName stri config.EnableMapDataFetchProxy = getConfigItemBoolValue(configFile, sectionName, "map_data_fetch_proxy", false) config.GoogleMapAPIKey = getConfigItemStringValue(configFile, sectionName, "google_map_api_key") config.BaiduMapAK = getConfigItemStringValue(configFile, sectionName, "baidu_map_ak") - config.AMapApplicationKey = getConfigItemStringValue(configFile, sectionName, "amap_application_key") + config.AmapApplicationKey = getConfigItemStringValue(configFile, sectionName, "amap_application_key") - if getConfigItemStringValue(configFile, sectionName, "amap_security_verification_method") == AmapSecurityVerificationPlainMethod { - config.AMapSecurityVerificationMethod = AmapSecurityVerificationPlainMethod + if getConfigItemStringValue(configFile, sectionName, "amap_security_verification_method") == AmapSecurityVerificationInternalProxyMethod { + config.AmapSecurityVerificationMethod = AmapSecurityVerificationInternalProxyMethod + } else if getConfigItemStringValue(configFile, sectionName, "amap_security_verification_method") == AmapSecurityVerificationExternalProxyMethod { + config.AmapSecurityVerificationMethod = AmapSecurityVerificationExternalProxyMethod + } else if getConfigItemStringValue(configFile, sectionName, "amap_security_verification_method") == AmapSecurityVerificationPlainTextMethod { + config.AmapSecurityVerificationMethod = AmapSecurityVerificationPlainTextMethod } else { return errs.ErrInvalidAmapSecurityVerificationMethod } - config.AMapApplicationSecret = getConfigItemStringValue(configFile, sectionName, "amap_application_secret") + config.AmapApplicationSecret = getConfigItemStringValue(configFile, sectionName, "amap_application_secret") + config.AmapApiExternalProxyUrl = getConfigItemStringValue(configFile, sectionName, "amap_api_external_proxy_url") return nil } diff --git a/pkg/utils/extractor.go b/pkg/utils/extractor.go new file mode 100644 index 00000000..367b7445 --- /dev/null +++ b/pkg/utils/extractor.go @@ -0,0 +1,20 @@ +package utils + +import ( + "net/http" + + "github.com/golang-jwt/jwt/v5/request" +) + +// CookieExtractor extracts a token from request cookies +type CookieExtractor []string + +func (e CookieExtractor) ExtractToken(req *http.Request) (string, error) { + for _, arg := range e { + if cookie, _ := req.Cookie(arg); cookie != nil { + return cookie.Value, nil + } + } + + return "", request.ErrNoTokenInRequest +} diff --git a/src/consts/api.js b/src/consts/api.js index 99df5a8a..2e7b6cd8 100644 --- a/src/consts/api.js +++ b/src/consts/api.js @@ -1,5 +1,6 @@ const baseApiUrlPath = '/api'; const baseProxyUrlPath = '/proxy'; +const baseAmapApiProxyUrlPath = '/_AMapService'; const googleMapJavascriptUrl = 'https://maps.googleapis.com/maps/api/js'; const baiduMapJavascriptUrl = 'https://api.map.baidu.com/api?v=3.0'; const amapJavascriptUrl = 'https://webapi.amap.com/maps?v=2.0'; @@ -7,6 +8,7 @@ const amapJavascriptUrl = 'https://webapi.amap.com/maps?v=2.0'; export default { baseApiUrlPath: baseApiUrlPath, baseProxyUrlPath: baseProxyUrlPath, + baseAmapApiProxyUrlPath: baseAmapApiProxyUrlPath, googleMapJavascriptUrl: googleMapJavascriptUrl, baiduMapJavascriptUrl: baiduMapJavascriptUrl, amapJavascriptUrl: amapJavascriptUrl diff --git a/src/lib/map/amap.js b/src/lib/map/amap.js index 96cd21a6..a4687663 100644 --- a/src/lib/map/amap.js +++ b/src/lib/map/amap.js @@ -1,6 +1,6 @@ -import { asyncLoadAssets } from "@/lib/misc.js"; -import services from "@/lib/services.js"; -import settings from "@/lib/settings.js"; +import { asyncLoadAssets } from '@/lib/misc.js'; +import services from '@/lib/services.js'; +import settings from '@/lib/settings.js'; import logger from '@/lib/logger.js'; const amapHolder = { @@ -15,7 +15,11 @@ export function loadAmapAssets() { if (!window._AMapSecurityConfig) { const amapSecurityConfig = {}; - if (settings.getAmapSecurityVerificationMethod() === 'plain') { + if (settings.getAmapSecurityVerificationMethod() === 'internalproxy') { + amapSecurityConfig.serviceHost = services.generateAmapApiInternalProxyUrl(); + } else if (settings.getAmapSecurityVerificationMethod() === 'externalproxy') { + amapSecurityConfig.serviceHost = settings.getAmapApiExternalProxyUrl(); + } else if (settings.getAmapSecurityVerificationMethod() === 'plaintext') { amapSecurityConfig.securityJsCode = settings.getAmapApplicationSecret(); } diff --git a/src/lib/services.js b/src/lib/services.js index 4ef1ca70..012e65ac 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -423,5 +423,8 @@ export default { }, generateAmapJavascriptUrl: (callbackFnName) => { return `${api.amapJavascriptUrl}&key=${settings.getAmapApplicationKey()}&callback=${callbackFnName}`; + }, + generateAmapApiInternalProxyUrl: () => { + return `${window.location.origin}${api.baseAmapApiProxyUrlPath}`; } }; diff --git a/src/lib/settings.js b/src/lib/settings.js index 01c5eec4..8c83410d 100644 --- a/src/lib/settings.js +++ b/src/lib/settings.js @@ -3,6 +3,8 @@ import Cookies from 'js-cookie'; import currencyConstants from '@/consts/currency.js'; import statisticsConstants from '@/consts/statistics.js'; +import { base64decode } from '@/lib/common.js'; + const settingsLocalStorageKey = 'ebk_app_settings'; const serverSettingsCookieKey = 'ebk_server_settings'; @@ -119,6 +121,16 @@ function getServerSetting(key) { return undefined; } +function getServerDecodedSetting(key) { + const value = getServerSetting(key); + + if (!value) { + return value; + } + + return decodeURIComponent(base64decode(value)); +} + function clearSettings() { localStorage.removeItem(settingsLocalStorageKey); } @@ -169,10 +181,11 @@ export default { isDataExportingEnabled: () => getServerSetting('e') === '1', getMapProvider: () => getServerSetting('m'), isMapDataFetchProxyEnabled: () => getServerSetting('mp') === '1', - getGoogleMapAPIKey: () => getServerSetting('gmak'), - getBaiduMapAK: () => getServerSetting('bmak'), - getAmapApplicationKey: () => getServerSetting('amak'), + getGoogleMapAPIKey: () => getServerDecodedSetting('gmak'), + getBaiduMapAK: () => getServerDecodedSetting('bmak'), + getAmapApplicationKey: () => getServerDecodedSetting('amak'), getAmapSecurityVerificationMethod: () => getServerSetting('amsv'), - getAmapApplicationSecret: () => getServerSetting('amas'), + getAmapApiExternalProxyUrl: () => getServerDecodedSetting('amep'), + getAmapApplicationSecret: () => getServerDecodedSetting('amas'), clearSettings: clearSettings }; diff --git a/vite.config.js b/vite.config.js index 4b861f90..4fd4027e 100644 --- a/vite.config.js +++ b/vite.config.js @@ -139,6 +139,10 @@ export default defineConfig(async () => { '/proxy': { target: 'http://127.0.0.1:8080/', changeOrigin: true + }, + '/_AMapService': { + target: 'http://127.0.0.1:8080/', + changeOrigin: true } } },