mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-07 08:06:37 +08:00
refactor(api): extract repository layer and thin HTTP handlers
Introduce internal/repository for data access and cache-backed system config reads. Move business logic into logics.go across admin push, user, template, cache, system_config, and upload/handler packages. Remove Gin from internal/util by relocating request-scoped helpers to oauth/gin_context.go. Propagate request context for config lookups in user flows. Slim model entities and delete model-level DB/cache helpers. Wire handlers to logics/repository so targeted packages no longer call db.DB directly. Update admin router tests to use ErrorHandlerMiddleware.
This commit is contained in:
@@ -9,12 +9,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func isOIDCLoginEnabled(ctx context.Context) bool {
|
||||
enabled, err := model.GetBoolByKey(ctx, model.ConfigKeyOIDCLoginEnabled)
|
||||
enabled, err := repository.GetBoolByKey(ctx, model.ConfigKeyOIDCLoginEnabled)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
@@ -37,7 +38,7 @@ func resolveAuthSource(ctx context.Context, sourceName string) (*model.AuthSourc
|
||||
}
|
||||
|
||||
func activeLoginSources(ctx context.Context) []AuthSourceView {
|
||||
enabled, err := model.GetBoolByKey(ctx, model.ConfigKeyOIDCLoginEnabled)
|
||||
enabled, err := repository.GetBoolByKey(ctx, model.ConfigKeyOIDCLoginEnabled)
|
||||
if err == nil && !enabled {
|
||||
return nil
|
||||
}
|
||||
@@ -62,8 +63,8 @@ func activeLoginSources(ctx context.Context) []AuthSourceView {
|
||||
}
|
||||
|
||||
func getFrontendLoginRedirectURL(ctx context.Context) (string, error) {
|
||||
var sc model.SystemConfig
|
||||
if err := sc.GetByKey(ctx, model.ConfigKeyServerAddress); err != nil || strings.TrimSpace(sc.Value) == "" {
|
||||
sc, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeyServerAddress)
|
||||
if err != nil || strings.TrimSpace(sc.Value) == "" {
|
||||
return "", errors.New(errServerAddressMissing)
|
||||
}
|
||||
return strings.TrimRight(sc.Value, "/") + "/login", nil
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package oauth
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
// GetFromContext 从 Gin 请求上下文获取指定类型的值。
|
||||
func GetFromContext[T any](c *gin.Context, key string) (T, bool) {
|
||||
value, exists := c.Get(key)
|
||||
if !exists {
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
typed, ok := value.(T)
|
||||
return typed, ok
|
||||
}
|
||||
|
||||
// SetToContext 设置值到 Gin 请求上下文。
|
||||
func SetToContext[T any](c *gin.Context, key string, value T) {
|
||||
c.Set(key, value)
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/Rain-kl/Wavelet/internal/listener"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
"github.com/Rain-kl/Wavelet/pkg/logger"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -188,7 +189,7 @@ func handleCallbackLogin(ctx context.Context, c *gin.Context, source *model.Auth
|
||||
// handleCallbackRegister 处理 OAuth 回调中的自动注册流程
|
||||
// 若注册被禁用则保存 pending 信息并返回 false;若注册成功则返回新用户;若出错则返回 false
|
||||
func handleCallbackRegister(ctx context.Context, c *gin.Context, source *model.AuthSource, userInfo *model.OAuthUserInfo) (model.User, bool) {
|
||||
registrationEnabled, regErr := model.GetBoolByKey(ctx, model.ConfigKeyRegistrationEnabled)
|
||||
registrationEnabled, regErr := repository.GetBoolByKey(ctx, model.ConfigKeyRegistrationEnabled)
|
||||
if regErr != nil {
|
||||
registrationEnabled = true
|
||||
}
|
||||
@@ -223,4 +224,4 @@ func handleCallbackRegister(ctx context.Context, c *gin.Context, source *model.A
|
||||
logger.InfoF(ctx, "[LoginAudit] successful OAuth registration via source: %s, external ID: %s, user: %s, ID: %d, IP: %s", source.Name, userInfo.Sub, user.Username, user.ID, c.ClientIP())
|
||||
|
||||
return user, true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response"
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
|
||||
otel_trace "github.com/Rain-kl/Wavelet/pkg/trace"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -62,8 +62,8 @@ func GetUserFromRequest(c *gin.Context) (*model.User, error) {
|
||||
if user.Username == "system" {
|
||||
return nil, errors.New("system user is not allowed to login")
|
||||
}
|
||||
util.SetToContext(c, TokenAuthKey, true)
|
||||
util.SetToContext(c, TokenAdminKey, tokenRecord.IsAdmin)
|
||||
SetToContext(c, TokenAuthKey, true)
|
||||
SetToContext(c, TokenAdminKey, tokenRecord.IsAdmin)
|
||||
return user, nil
|
||||
}
|
||||
}
|
||||
@@ -91,8 +91,8 @@ func GetUserFromRequest(c *gin.Context) (*model.User, error) {
|
||||
}
|
||||
|
||||
// set keys in context for session auth
|
||||
util.SetToContext(c, TokenAuthKey, false)
|
||||
util.SetToContext(c, TokenAdminKey, false)
|
||||
SetToContext(c, TokenAuthKey, false)
|
||||
SetToContext(c, TokenAdminKey, false)
|
||||
|
||||
// 强行阻止 system 用户任何会话/Token 鉴权通过
|
||||
if user.Username == "system" {
|
||||
@@ -119,7 +119,7 @@ func LoginRequired() gin.HandlerFunc {
|
||||
LogForAudit(ctx, user, c)
|
||||
|
||||
// set user info
|
||||
util.SetToContext(c, UserObjKey, user)
|
||||
SetToContext(c, UserObjKey, user)
|
||||
|
||||
// next
|
||||
c.Next()
|
||||
@@ -129,7 +129,7 @@ func LoginRequired() gin.HandlerFunc {
|
||||
// DisallowTokenAuth 拒绝使用 Access Token 进行身份验证的请求访问该端点
|
||||
func DisallowTokenAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if tokenAuth, _ := util.GetFromContext[bool](c, TokenAuthKey); tokenAuth {
|
||||
if tokenAuth, _ := GetFromContext[bool](c, TokenAuthKey); tokenAuth {
|
||||
response.AbortForbidden(c, ErrTokenAuthNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/config"
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
)
|
||||
@@ -1094,7 +1095,7 @@ func TestOIDCPolicyEnforcement(t *testing.T) {
|
||||
Key: model.ConfigKeyOIDCLoginEnabled,
|
||||
Value: "false",
|
||||
})
|
||||
mockRedis.Del(context.Background(), db.PrefixedKey(model.SystemConfigRedisHashKey)+":"+model.ConfigKeyOIDCLoginEnabled)
|
||||
mockRedis.Del(context.Background(), db.PrefixedKey(repository.SystemConfigRedisHashKey)+":"+model.ConfigKeyOIDCLoginEnabled)
|
||||
wLoginDisabled := performRequest(router, http.MethodGet, "/api/v1/oauth/login?source="+testSourceName, nil, nil, nil)
|
||||
if wLoginDisabled.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 when OIDC globally disabled, got %d", wLoginDisabled.Code)
|
||||
@@ -1102,7 +1103,7 @@ func TestOIDCPolicyEnforcement(t *testing.T) {
|
||||
|
||||
// Re-enable globally, but deactivate source
|
||||
dbConn.Model(&model.SystemConfig{}).Where("key = ?", model.ConfigKeyOIDCLoginEnabled).Update("value", "true")
|
||||
mockRedis.Del(context.Background(), db.PrefixedKey(model.SystemConfigRedisHashKey)+":"+model.ConfigKeyOIDCLoginEnabled)
|
||||
mockRedis.Del(context.Background(), db.PrefixedKey(repository.SystemConfigRedisHashKey)+":"+model.ConfigKeyOIDCLoginEnabled)
|
||||
dbConn.Model(&model.AuthSource{}).Where("name = ?", testSourceName).Update("is_active", false)
|
||||
|
||||
wSourceInactive := performRequest(router, http.MethodGet, "/api/v1/oauth/login?source="+testSourceName, nil, nil, nil)
|
||||
@@ -1113,7 +1114,7 @@ func TestOIDCPolicyEnforcement(t *testing.T) {
|
||||
// --- 2. Test Authorize enforcement ---
|
||||
// Deactivate globally again
|
||||
dbConn.Model(&model.SystemConfig{}).Where("key = ?", model.ConfigKeyOIDCLoginEnabled).Update("value", "false")
|
||||
mockRedis.Del(context.Background(), db.PrefixedKey(model.SystemConfigRedisHashKey)+":"+model.ConfigKeyOIDCLoginEnabled)
|
||||
mockRedis.Del(context.Background(), db.PrefixedKey(repository.SystemConfigRedisHashKey)+":"+model.ConfigKeyOIDCLoginEnabled)
|
||||
dbConn.Model(&model.AuthSource{}).Where("name = ?", testSourceName).Update("is_active", true)
|
||||
|
||||
wAuthDisabled := performRequest(router, http.MethodGet, "/api/v1/oauth/"+testSourceName+"/authorize", nil, nil, nil)
|
||||
@@ -1124,7 +1125,7 @@ func TestOIDCPolicyEnforcement(t *testing.T) {
|
||||
// --- 3. Test Callback enforcement ---
|
||||
// Set up a valid state beforehand (when enabled)
|
||||
dbConn.Model(&model.SystemConfig{}).Where("key = ?", model.ConfigKeyOIDCLoginEnabled).Update("value", "true")
|
||||
mockRedis.Del(context.Background(), db.PrefixedKey(model.SystemConfigRedisHashKey)+":"+model.ConfigKeyOIDCLoginEnabled)
|
||||
mockRedis.Del(context.Background(), db.PrefixedKey(repository.SystemConfigRedisHashKey)+":"+model.ConfigKeyOIDCLoginEnabled)
|
||||
dbConn.Model(&model.AuthSource{}).Where("name = ?", testSourceName).Update("is_active", true)
|
||||
|
||||
wLogin := performRequest(router, http.MethodGet, "/api/v1/oauth/login?source="+testSourceName, nil, nil, nil)
|
||||
@@ -1149,7 +1150,7 @@ func TestOIDCPolicyEnforcement(t *testing.T) {
|
||||
|
||||
// Now disable OIDC globally and attempt callback
|
||||
dbConn.Model(&model.SystemConfig{}).Where("key = ?", model.ConfigKeyOIDCLoginEnabled).Update("value", "false")
|
||||
mockRedis.Del(context.Background(), db.PrefixedKey(model.SystemConfigRedisHashKey)+":"+model.ConfigKeyOIDCLoginEnabled)
|
||||
mockRedis.Del(context.Background(), db.PrefixedKey(repository.SystemConfigRedisHashKey)+":"+model.ConfigKeyOIDCLoginEnabled)
|
||||
reqBody := fmt.Sprintf(`{"state":"%s","code":"test_auth_code"}`, state)
|
||||
wCallbackDisabled := performRequest(router, http.MethodPost, "/api/v1/oauth/callback", []byte(reqBody), map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
@@ -1160,7 +1161,7 @@ func TestOIDCPolicyEnforcement(t *testing.T) {
|
||||
|
||||
// Enable globally but deactivate source and attempt callback
|
||||
dbConn.Model(&model.SystemConfig{}).Where("key = ?", model.ConfigKeyOIDCLoginEnabled).Update("value", "true")
|
||||
mockRedis.Del(context.Background(), db.PrefixedKey(model.SystemConfigRedisHashKey)+":"+model.ConfigKeyOIDCLoginEnabled)
|
||||
mockRedis.Del(context.Background(), db.PrefixedKey(repository.SystemConfigRedisHashKey)+":"+model.ConfigKeyOIDCLoginEnabled)
|
||||
dbConn.Model(&model.AuthSource{}).Where("name = ?", testSourceName).Update("is_active", false)
|
||||
|
||||
// Since callback deletes state, we need to generate state again
|
||||
|
||||
@@ -10,7 +10,7 @@ import ("net/http"
|
||||
"github.com/Rain-kl/Wavelet/pkg/logger"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/Rain-kl/Wavelet/internal/util"
|
||||
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/common/response")
|
||||
|
||||
@@ -60,7 +60,7 @@ func BuildBasicUserInfo(user *model.User, needChange bool) BasicUserInfo {
|
||||
// @Router /api/v1/user-info [get]
|
||||
// @Router /api/v1/user/self [get]
|
||||
func UserInfo(c *gin.Context) {
|
||||
user, _ := util.GetFromContext[*model.User](c, UserObjKey)
|
||||
user, _ := GetFromContext[*model.User](c, UserObjKey)
|
||||
session := sessions.Default(c)
|
||||
needChange := session.Get("need_change_password") == true
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/config"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
@@ -56,7 +57,7 @@ func setLoginSession(ctx context.Context, c *gin.Context, user *model.User) erro
|
||||
maxAge := config.Config.App.SessionAge
|
||||
isSessionCookie := false
|
||||
|
||||
ttlHours, err := model.GetIntByKey(ctx, model.ConfigKeyLoginSessionTTLHours)
|
||||
ttlHours, err := repository.GetIntByKey(ctx, model.ConfigKeyLoginSessionTTLHours)
|
||||
if err == nil {
|
||||
switch {
|
||||
case ttlHours == -1:
|
||||
|
||||
Reference in New Issue
Block a user