mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-01 22:46:38 +08:00
oauth
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
package auth_source
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/linux-do/credit/internal/model"
|
||||
"github.com/linux-do/credit/internal/util"
|
||||
)
|
||||
|
||||
type AuthSourceRequest 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"`
|
||||
}
|
||||
|
||||
type ToggleAuthSourceRequest struct {
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
||||
// ListAuthSources 获取认证源列表
|
||||
// @Summary 获取认证源列表
|
||||
// @Description 返回所有已配置的 OAuth/OIDC 认证源列表,包括已启用和未启用的,需要管理员权限
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Success 200 {object} util.ResponseAny{data=[]model.AuthSource} "认证源列表"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Router /api/v1/admin/auth-sources [get]
|
||||
func ListAuthSources(c *gin.Context) {
|
||||
sources, err := model.GetAuthSources()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, util.OK(sources))
|
||||
}
|
||||
|
||||
// CreateAuthSource 创建认证源
|
||||
// @Summary 创建认证源
|
||||
// @Description 创建一个新的 OAuth/OIDC 认证源配置,认证源名称必须唯一且符合命名规范,需要管理员权限
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param request body auth_source.AuthSourceRequest true "创建认证源参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.AuthSource} "创建成功,返回认证源信息"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误或验证失败"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Router /api/v1/admin/auth-sources [post]
|
||||
func CreateAuthSource(c *gin.Context) {
|
||||
var req AuthSourceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
source := model.AuthSource{
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
DisplayName: req.DisplayName,
|
||||
IsActive: req.IsActive,
|
||||
ClientID: req.ClientID,
|
||||
ClientSecret: req.ClientSecret,
|
||||
OpenIDDiscoveryURL: req.OpenIDDiscoveryURL,
|
||||
Scopes: req.Scopes,
|
||||
IconURL: req.IconURL,
|
||||
}
|
||||
if err := model.CreateAuthSource(&source); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
source.Sanitize()
|
||||
c.JSON(http.StatusOK, util.OK(source))
|
||||
}
|
||||
|
||||
// UpdateAuthSource 更新认证源
|
||||
// @Summary 更新认证源
|
||||
// @Description 更新指定 ID 的认证源配置。若 client_secret 字段为空,则保留原有密钥不变,需要管理员权限
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path uint64 true "认证源 ID 或名称"
|
||||
// @Param request body auth_source.AuthSourceRequest true "更新认证源参数"
|
||||
// @Success 200 {object} util.ResponseAny{data=model.AuthSource} "更新成功,返回更新后的认证源信息"
|
||||
// @Failure 400 {object} util.ResponseAny "参数错误或验证失败"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Failure 500 {object} util.ResponseAny "内部错误"
|
||||
// @Router /api/v1/admin/auth-sources/{id} [put]
|
||||
func UpdateAuthSource(c *gin.Context) {
|
||||
id, err := parseSourceID(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var req AuthSourceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
source := model.AuthSource{
|
||||
ID: id,
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
DisplayName: req.DisplayName,
|
||||
IsActive: req.IsActive,
|
||||
ClientID: req.ClientID,
|
||||
ClientSecret: req.ClientSecret,
|
||||
OpenIDDiscoveryURL: req.OpenIDDiscoveryURL,
|
||||
Scopes: req.Scopes,
|
||||
IconURL: req.IconURL,
|
||||
}
|
||||
keepSecret := source.ClientSecret == ""
|
||||
if err := model.UpdateAuthSource(&source, keepSecret); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
updated, err := model.GetAuthSourceByID(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
updated.Sanitize()
|
||||
c.JSON(http.StatusOK, util.OK(updated))
|
||||
}
|
||||
|
||||
// ToggleAuthSource 切换认证源启用状态
|
||||
// @Summary 切换认证源启用状态
|
||||
// @Description 启用或禁用指定认证源。尝试启用时将验证 Client ID 和 Client Secret 是否已配置,需要管理员权限
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path uint64 true "认证源 ID 或名称"
|
||||
// @Param request body auth_source.ToggleAuthSourceRequest true "启用状态"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "切换成功"
|
||||
// @Failure 400 {object} util.ResponseAny "验证失败或认证源不存在"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Router /api/v1/admin/auth-sources/{id}/toggle [put]
|
||||
func ToggleAuthSource(c *gin.Context) {
|
||||
id, err := parseSourceID(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var req ToggleAuthSourceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := model.ToggleAuthSource(id, req.IsActive); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
}
|
||||
|
||||
// DeleteAuthSource 删除认证源
|
||||
// @Summary 删除认证源
|
||||
// @Description 删除指定认证源及其关联的所有外部帐号绑定记录,警告:删除后相关用户将无法通过该源登录,需要管理员权限
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security SessionCookie
|
||||
// @Param id path uint64 true "认证源 ID 或名称"
|
||||
// @Success 200 {object} util.ResponseAny{data=string} "删除成功"
|
||||
// @Failure 400 {object} util.ResponseAny "ID 无效或删除失败"
|
||||
// @Failure 401 {object} util.ResponseAny "未登录"
|
||||
// @Failure 403 {object} util.ResponseAny "无管理员权限"
|
||||
// @Router /api/v1/admin/auth-sources/{id} [delete]
|
||||
func DeleteAuthSource(c *gin.Context) {
|
||||
id, err := parseSourceID(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
if err := model.DeleteAuthSource(id); err != nil {
|
||||
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, util.OKNil())
|
||||
}
|
||||
|
||||
func parseSourceID(c *gin.Context) (uint64, error) {
|
||||
raw := c.Param("id")
|
||||
if raw == "" {
|
||||
return 0, errors.New("认证源 ID 无效")
|
||||
}
|
||||
source, err := model.GetAuthSourceByName(raw)
|
||||
if err == nil {
|
||||
return source.ID, nil
|
||||
}
|
||||
var id uint64
|
||||
if _, scanErr := fmt.Sscanf(raw, "%d", &id); scanErr != nil || id == 0 {
|
||||
return 0, errors.New("认证源 ID 无效")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
Copyright 2026 linux.do
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package auth_source
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/linux-do/credit/internal/apps/oauth"
|
||||
"github.com/linux-do/credit/internal/model"
|
||||
"github.com/linux-do/credit/internal/testhelper"
|
||||
"github.com/linux-do/credit/internal/util"
|
||||
)
|
||||
|
||||
func setupTestRouter(authUser *model.User) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
adminGroup := r.Group("/api/v1/admin")
|
||||
|
||||
// Mock authentication middleware
|
||||
adminGroup.Use(func(c *gin.Context) {
|
||||
if authUser != nil {
|
||||
util.SetToContext(c, oauth.UserObjKey, authUser)
|
||||
}
|
||||
c.Next()
|
||||
})
|
||||
|
||||
adminGroup.GET("/auth-sources", ListAuthSources)
|
||||
adminGroup.POST("/auth-sources", CreateAuthSource)
|
||||
adminGroup.PUT("/auth-sources/:id", UpdateAuthSource)
|
||||
adminGroup.PUT("/auth-sources/:id/toggle", ToggleAuthSource)
|
||||
adminGroup.DELETE("/auth-sources/:id", DeleteAuthSource)
|
||||
return r
|
||||
}
|
||||
|
||||
func TestListAuthSources(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
// Seed source
|
||||
source := model.AuthSource{
|
||||
ID: 1,
|
||||
Name: "google",
|
||||
Type: "oidc",
|
||||
DisplayName: "Google Auth",
|
||||
IsActive: true,
|
||||
ClientID: "client_id_123",
|
||||
ClientSecret: "client_secret_456",
|
||||
OpenIDDiscoveryURL: "https://accounts.google.com",
|
||||
}
|
||||
dbConn.Create(&source)
|
||||
|
||||
adminUser := &model.User{ID: 1001, Username: "admin", IsAdmin: true, SignKey: "admin_key"}
|
||||
router := setupTestRouter(adminUser)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/api/v1/admin/auth-sources", nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200 OK, got %d", w.Code)
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
var sources []model.AuthSource
|
||||
json.Unmarshal(dataBytes, &sources)
|
||||
|
||||
if len(sources) != 1 {
|
||||
t.Errorf("expected 1 auth source, got %d", len(sources))
|
||||
}
|
||||
if sources[0].Name != "google" {
|
||||
t.Errorf("expected name 'google', got '%s'", sources[0].Name)
|
||||
}
|
||||
// Verify sanitize removed the secret
|
||||
if sources[0].ClientSecret != "" {
|
||||
t.Error("client secret should be sanitized")
|
||||
}
|
||||
if !sources[0].ClientSecretConfigured {
|
||||
t.Error("client secret configured flag should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAuthSource(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
adminUser := &model.User{ID: 1001, Username: "admin", IsAdmin: true, SignKey: "admin_key"}
|
||||
router := setupTestRouter(adminUser)
|
||||
|
||||
t.Run("create successfully", func(t *testing.T) {
|
||||
reqPayload := AuthSourceRequest{
|
||||
Name: "github",
|
||||
Type: "oidc",
|
||||
DisplayName: "GitHub OIDC",
|
||||
IsActive: true,
|
||||
ClientID: "client_id_gh",
|
||||
ClientSecret: "client_secret_gh",
|
||||
OpenIDDiscoveryURL: "https://github.com",
|
||||
}
|
||||
body, _ := json.Marshal(reqPayload)
|
||||
req, _ := http.NewRequest("POST", "/api/v1/admin/auth-sources", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200 OK, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify database
|
||||
var src model.AuthSource
|
||||
dbConn.Where("name = ?", "github").First(&src)
|
||||
if src.ClientID != "client_id_gh" {
|
||||
t.Errorf("expected client_id_gh, got '%s'", src.ClientID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("create invalid validation failure", func(t *testing.T) {
|
||||
reqPayload := AuthSourceRequest{
|
||||
Name: "invalid name!",
|
||||
Type: "oidc",
|
||||
DisplayName: "Invalid",
|
||||
IsActive: true,
|
||||
ClientID: "client_id_val",
|
||||
ClientSecret: "client_secret_val",
|
||||
OpenIDDiscoveryURL: "https://discovery.url",
|
||||
}
|
||||
body, _ := json.Marshal(reqPayload)
|
||||
req, _ := http.NewRequest("POST", "/api/v1/admin/auth-sources", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 Bad Request, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateAuthSource(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
// Seed source
|
||||
source := model.AuthSource{
|
||||
ID: 1,
|
||||
Name: "microsoft",
|
||||
Type: "oidc",
|
||||
DisplayName: "Microsoft",
|
||||
IsActive: true,
|
||||
ClientID: "old_client_id",
|
||||
ClientSecret: "old_secret",
|
||||
OpenIDDiscoveryURL: "https://login.microsoftonline.com",
|
||||
}
|
||||
dbConn.Create(&source)
|
||||
|
||||
adminUser := &model.User{ID: 1001, Username: "admin", IsAdmin: true, SignKey: "admin_key"}
|
||||
router := setupTestRouter(adminUser)
|
||||
|
||||
t.Run("update keep client secret", func(t *testing.T) {
|
||||
reqPayload := AuthSourceRequest{
|
||||
Name: "microsoft",
|
||||
Type: "oidc",
|
||||
DisplayName: "Microsoft Updated",
|
||||
IsActive: true,
|
||||
ClientID: "new_client_id",
|
||||
ClientSecret: "", // empty implies keeping existing secret
|
||||
OpenIDDiscoveryURL: "https://login.microsoftonline.com",
|
||||
}
|
||||
body, _ := json.Marshal(reqPayload)
|
||||
req, _ := http.NewRequest("PUT", "/api/v1/admin/auth-sources/1", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 OK, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var src model.AuthSource
|
||||
dbConn.First(&src, 1)
|
||||
if src.DisplayName != "Microsoft Updated" {
|
||||
t.Errorf("expected display name update, got '%s'", src.DisplayName)
|
||||
}
|
||||
if src.ClientSecret != "old_secret" {
|
||||
t.Errorf("expected old secret to be preserved, got '%s'", src.ClientSecret)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("update new client secret", func(t *testing.T) {
|
||||
reqPayload := AuthSourceRequest{
|
||||
Name: "microsoft",
|
||||
Type: "oidc",
|
||||
DisplayName: "Microsoft Updated Again",
|
||||
IsActive: true,
|
||||
ClientID: "new_client_id",
|
||||
ClientSecret: "brand_new_secret",
|
||||
OpenIDDiscoveryURL: "https://login.microsoftonline.com",
|
||||
}
|
||||
body, _ := json.Marshal(reqPayload)
|
||||
req, _ := http.NewRequest("PUT", "/api/v1/admin/auth-sources/microsoft", bytes.NewBuffer(body)) // Using Name instead of ID
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 OK, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var src model.AuthSource
|
||||
dbConn.First(&src, 1)
|
||||
if src.ClientSecret != "brand_new_secret" {
|
||||
t.Errorf("expected secret update, got '%s'", src.ClientSecret)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestToggleAuthSource(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
source := model.AuthSource{
|
||||
ID: 1,
|
||||
Name: "test_source",
|
||||
Type: "oidc",
|
||||
DisplayName: "Test Source",
|
||||
IsActive: false,
|
||||
ClientID: "",
|
||||
ClientSecret: "",
|
||||
OpenIDDiscoveryURL: "https://test.discovery.url",
|
||||
}
|
||||
dbConn.Create(&source)
|
||||
|
||||
adminUser := &model.User{ID: 1001, Username: "admin", IsAdmin: true, SignKey: "admin_key"}
|
||||
router := setupTestRouter(adminUser)
|
||||
|
||||
t.Run("cannot activate without credentials", func(t *testing.T) {
|
||||
payload := ToggleAuthSourceRequest{IsActive: true}
|
||||
body, _ := json.Marshal(payload)
|
||||
req, _ := http.NewRequest("PUT", "/api/v1/admin/auth-sources/1/toggle", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 Bad Request when activating without client_id/secret, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("toggle success after setting credentials", func(t *testing.T) {
|
||||
// Set credentials first
|
||||
dbConn.Model(&model.AuthSource{}).Where("id = ?", 1).Updates(map[string]interface{}{
|
||||
"client_id": "id",
|
||||
"client_secret": "secret",
|
||||
})
|
||||
|
||||
payload := ToggleAuthSourceRequest{IsActive: true}
|
||||
body, _ := json.Marshal(payload)
|
||||
req, _ := http.NewRequest("PUT", "/api/v1/admin/auth-sources/1/toggle", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200 OK, got %d. Body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var src model.AuthSource
|
||||
dbConn.First(&src, 1)
|
||||
if !src.IsActive {
|
||||
t.Error("auth source should be activated")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteAuthSource(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
source := model.AuthSource{
|
||||
ID: 1,
|
||||
Name: "delete_me",
|
||||
Type: "oidc",
|
||||
DisplayName: "Delete Me",
|
||||
IsActive: true,
|
||||
ClientID: "id",
|
||||
ClientSecret: "secret",
|
||||
OpenIDDiscoveryURL: "https://delete.me",
|
||||
}
|
||||
dbConn.Create(&source)
|
||||
|
||||
externalAccount := model.ExternalAccount{
|
||||
ID: 10,
|
||||
AuthSourceID: 1,
|
||||
UserID: 50,
|
||||
ExternalID: "ext_50",
|
||||
}
|
||||
dbConn.Create(&externalAccount)
|
||||
|
||||
adminUser := &model.User{ID: 1001, Username: "admin", IsAdmin: true, SignKey: "admin_key"}
|
||||
router := setupTestRouter(adminUser)
|
||||
|
||||
req, _ := http.NewRequest("DELETE", "/api/v1/admin/auth-sources/1", nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200 OK, got %d", w.Code)
|
||||
}
|
||||
|
||||
// Verify AuthSource is deleted
|
||||
var srcCount int64
|
||||
dbConn.Model(&model.AuthSource{}).Where("id = ?", 1).Count(&srcCount)
|
||||
if srcCount != 0 {
|
||||
t.Error("AuthSource should be deleted from the database")
|
||||
}
|
||||
|
||||
// Verify ExternalAccount bindings are also deleted
|
||||
var extCount int64
|
||||
dbConn.Model(&model.ExternalAccount{}).Where("auth_source_id = ?", 1).Count(&extCount)
|
||||
if extCount != 0 {
|
||||
t.Error("related ExternalAccount bindings should be deleted")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user