mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
[优化] go 引用调整
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rain-kl/openflare/openflare-server/common"
|
||||
"github.com/rain-kl/openflare/openflare-server/common/response"
|
||||
"github.com/rain-kl/openflare/openflare-server/controller/bind"
|
||||
"github.com/rain-kl/openflare/openflare-server/model"
|
||||
"github.com/rain-kl/openflare/openflare-server/service"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const pendingExternalAccountSessionKey = "pending_external_account"
|
||||
|
||||
type authSourceTogglePayload struct {
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
||||
type authSourcePayload struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
DisplayName string `json:"display_name"`
|
||||
IsActive bool `json:"is_active"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
OpenIDDiscoveryURL string `json:"openid_discovery_url"`
|
||||
Scopes string `json:"scopes"`
|
||||
IconURL string `json:"icon_url"`
|
||||
}
|
||||
|
||||
func (payload authSourcePayload) toModel() model.AuthSource {
|
||||
return model.AuthSource{
|
||||
Name: payload.Name,
|
||||
Type: payload.Type,
|
||||
DisplayName: payload.DisplayName,
|
||||
IsActive: payload.IsActive,
|
||||
ClientID: payload.ClientID,
|
||||
ClientSecret: payload.ClientSecret,
|
||||
OpenIDDiscoveryURL: payload.OpenIDDiscoveryURL,
|
||||
Scopes: payload.Scopes,
|
||||
IconURL: payload.IconURL,
|
||||
}
|
||||
}
|
||||
|
||||
func ListAuthSources(c *gin.Context) {
|
||||
sources, err := model.GetAuthSources()
|
||||
if err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.RespondSuccess(c, sources)
|
||||
}
|
||||
|
||||
func CreateAuthSource(c *gin.Context) {
|
||||
var payload authSourcePayload
|
||||
if err := bind.DecodeJSONBody(c.Request.Body, &payload); err != nil {
|
||||
response.RespondBadRequest(c, "无效的参数")
|
||||
return
|
||||
}
|
||||
source := payload.toModel()
|
||||
if err := model.CreateAuthSource(&source); err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
source.Sanitize()
|
||||
response.RespondSuccess(c, source)
|
||||
}
|
||||
|
||||
func UpdateAuthSource(c *gin.Context) {
|
||||
id, err := parseAuthSourceID(c)
|
||||
if err != nil {
|
||||
response.RespondBadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
var payload authSourcePayload
|
||||
if err := bind.DecodeJSONBody(c.Request.Body, &payload); err != nil {
|
||||
response.RespondBadRequest(c, "无效的参数")
|
||||
return
|
||||
}
|
||||
source := payload.toModel()
|
||||
source.ID = id
|
||||
keepSecret := strings.TrimSpace(source.ClientSecret) == ""
|
||||
if err := model.UpdateAuthSource(&source, keepSecret); err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
updated, err := model.GetAuthSourceByID(id)
|
||||
if err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
updated.Sanitize()
|
||||
response.RespondSuccess(c, updated)
|
||||
}
|
||||
|
||||
func DeleteAuthSource(c *gin.Context) {
|
||||
id, err := parseAuthSourceID(c)
|
||||
if err != nil {
|
||||
response.RespondBadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
if err := model.DeleteAuthSource(id); err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.RespondSuccessMessage(c, "")
|
||||
}
|
||||
|
||||
func ToggleAuthSource(c *gin.Context) {
|
||||
id, err := parseAuthSourceID(c)
|
||||
if err != nil {
|
||||
response.RespondBadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
var payload authSourceTogglePayload
|
||||
if err := bind.DecodeJSONBody(c.Request.Body, &payload); err != nil {
|
||||
response.RespondBadRequest(c, "无效的参数")
|
||||
return
|
||||
}
|
||||
if err := model.ToggleAuthSource(id, payload.IsActive); err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.RespondSuccessMessage(c, "")
|
||||
}
|
||||
|
||||
func OAuthAuthorize(c *gin.Context) {
|
||||
source, err := getAuthSourceFromRoute(c)
|
||||
if err != nil {
|
||||
response.RespondBadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
if !source.IsActive {
|
||||
response.RespondFailure(c, "认证源未启用")
|
||||
return
|
||||
}
|
||||
if err := source.Validate(); err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
state, err := service.GenerateOAuthState()
|
||||
if err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
session := sessions.Default(c)
|
||||
session.Set(oauthStateSessionKey(source.ID), state)
|
||||
if err := session.Save(); err != nil {
|
||||
response.RespondFailure(c, "无法保存授权状态,请重试")
|
||||
return
|
||||
}
|
||||
redirectURL := oauthFrontendCallbackURL(c, source.ID)
|
||||
authorizeURL, err := service.BuildAuthorizeURL(c.Request.Context(), source, redirectURL, state)
|
||||
if err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.RespondSuccess(c, gin.H{"authorize_url": authorizeURL})
|
||||
}
|
||||
|
||||
func OAuthCallback(c *gin.Context) {
|
||||
source, err := getAuthSourceFromRoute(c)
|
||||
if err != nil {
|
||||
response.RespondBadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
if !source.IsActive {
|
||||
response.RespondFailure(c, "认证源未启用")
|
||||
return
|
||||
}
|
||||
session := sessions.Default(c)
|
||||
expectedState, _ := session.Get(oauthStateSessionKey(source.ID)).(string)
|
||||
state := c.Query("state")
|
||||
if expectedState == "" || state == "" || state != expectedState {
|
||||
response.RespondFailure(c, "授权状态无效,请重新登录")
|
||||
return
|
||||
}
|
||||
session.Delete(oauthStateSessionKey(source.ID))
|
||||
if err := session.Save(); err != nil {
|
||||
response.RespondFailure(c, "无法更新授权状态,请重试")
|
||||
return
|
||||
}
|
||||
if oauthError := c.Query("error"); oauthError != "" {
|
||||
description := c.Query("error_description")
|
||||
if description == "" {
|
||||
description = oauthError
|
||||
}
|
||||
response.RespondFailure(c, description)
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := service.ExchangeOAuthProfile(c.Request.Context(), source, c.Query("code"), oauthFrontendCallbackURL(c, source.ID))
|
||||
if err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
var currentUserID *int
|
||||
if currentUser := currentUserFromOpenFlareToken(c); currentUser != nil {
|
||||
currentUserID = ¤tUser.Id
|
||||
}
|
||||
result, pending, err := service.CompleteOAuthLogin(source, profile, currentUserID)
|
||||
if err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
if pending != nil {
|
||||
raw, err := json.Marshal(pending)
|
||||
if err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
session.Set(pendingExternalAccountSessionKey, string(raw))
|
||||
if err := session.Save(); err != nil {
|
||||
response.RespondFailure(c, "无法保存待绑定账号,请重试")
|
||||
return
|
||||
}
|
||||
response.RespondSuccess(c, result)
|
||||
return
|
||||
}
|
||||
if result.User != nil {
|
||||
cleanUser, err := setLoginToken(result.User)
|
||||
if err != nil {
|
||||
response.RespondFailure(c, "无法保存会话信息,请重试")
|
||||
return
|
||||
}
|
||||
result.User = cleanUser
|
||||
}
|
||||
response.RespondSuccess(c, result)
|
||||
}
|
||||
|
||||
func LinkExistingOAuthAccount(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
raw, _ := session.Get(pendingExternalAccountSessionKey).(string)
|
||||
if raw == "" {
|
||||
response.RespondFailure(c, "待绑定第三方账号已失效,请重新登录")
|
||||
return
|
||||
}
|
||||
var pending service.PendingExternalAccount
|
||||
if err := json.Unmarshal([]byte(raw), &pending); err != nil {
|
||||
response.RespondFailure(c, "待绑定第三方账号无效,请重新登录")
|
||||
return
|
||||
}
|
||||
var input service.LinkExistingRequest
|
||||
if err := bind.DecodeJSONBody(c.Request.Body, &input); err != nil {
|
||||
response.RespondBadRequest(c, "无效的参数")
|
||||
return
|
||||
}
|
||||
user, err := service.LinkPendingExternalAccount(&pending, input)
|
||||
if err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
session.Delete(pendingExternalAccountSessionKey)
|
||||
if err := session.Save(); err != nil {
|
||||
response.RespondFailure(c, "无法更新会话信息,请重试")
|
||||
return
|
||||
}
|
||||
cleanUser, err := setLoginToken(user)
|
||||
if err != nil {
|
||||
response.RespondFailure(c, "无法保存会话信息,请重试")
|
||||
return
|
||||
}
|
||||
response.RespondSuccess(c, service.OAuthCallbackResult{Status: "linked", User: cleanUser})
|
||||
}
|
||||
|
||||
func ListExternalAccounts(c *gin.Context) {
|
||||
userID := c.GetInt("id")
|
||||
accounts, err := model.ListExternalAccountsByUserID(userID)
|
||||
if err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.RespondSuccess(c, accounts)
|
||||
}
|
||||
|
||||
func DeleteExternalAccount(c *gin.Context) {
|
||||
rawID := strings.TrimSpace(c.Param("id"))
|
||||
parsedID, err := strconv.ParseUint(rawID, 10, 64)
|
||||
if err != nil || parsedID == 0 {
|
||||
response.RespondBadRequest(c, "绑定记录 ID 无效")
|
||||
return
|
||||
}
|
||||
if err := model.DeleteExternalAccountForUser(uint(parsedID), c.GetInt("id")); err != nil {
|
||||
response.RespondFailure(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.RespondSuccessMessage(c, "")
|
||||
}
|
||||
|
||||
func parseAuthSourceID(c *gin.Context) (uint, error) {
|
||||
raw := c.Param("source_id")
|
||||
if raw == "" {
|
||||
raw = c.Param("id")
|
||||
}
|
||||
parsed, err := strconv.ParseUint(raw, 10, 64)
|
||||
if err != nil || parsed == 0 {
|
||||
return 0, fmt.Errorf("认证源 ID 无效")
|
||||
}
|
||||
return uint(parsed), nil
|
||||
}
|
||||
|
||||
func getAuthSourceFromRoute(c *gin.Context) (*model.AuthSource, error) {
|
||||
raw := strings.TrimSpace(c.Param("source"))
|
||||
if raw == "" {
|
||||
raw = strings.TrimSpace(c.Param("source_id"))
|
||||
}
|
||||
if raw == "" {
|
||||
raw = strings.TrimSpace(c.Param("id"))
|
||||
}
|
||||
if raw == "" {
|
||||
return nil, fmt.Errorf("认证源不能为空")
|
||||
}
|
||||
if parsed, err := strconv.ParseUint(raw, 10, 64); err == nil && parsed > 0 {
|
||||
source, err := model.GetAuthSourceByID(uint(parsed))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
source, err := model.GetAuthSourceByName(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func oauthStateSessionKey(sourceID uint) string {
|
||||
return fmt.Sprintf("oauth_state_%d", sourceID)
|
||||
}
|
||||
|
||||
func oauthFrontendCallbackURL(c *gin.Context, sourceID uint) string {
|
||||
base := strings.TrimRight(common.ServerAddress, "/")
|
||||
if base == "" {
|
||||
scheme := "http"
|
||||
if c.Request.TLS != nil || c.GetHeader("X-Forwarded-Proto") == "https" {
|
||||
scheme = "https"
|
||||
}
|
||||
host := c.Request.Host
|
||||
if forwardedHost := c.GetHeader("X-Forwarded-Host"); forwardedHost != "" {
|
||||
host = forwardedHost
|
||||
}
|
||||
base = scheme + "://" + host
|
||||
}
|
||||
source, err := model.GetAuthSourceByID(sourceID)
|
||||
sourceName := strconv.FormatUint(uint64(sourceID), 10)
|
||||
if err == nil && strings.TrimSpace(source.Name) != "" {
|
||||
sourceName = source.Name
|
||||
}
|
||||
callback, _ := url.JoinPath(base, "oauth", sourceName)
|
||||
return callback
|
||||
}
|
||||
Reference in New Issue
Block a user