mirror of
https://gh-proxy.org/https://github.com/klinsmaya/new-api.git
synced 2026-11-08 01:17:09 +08:00
SMTP settings now only decide whether mail can be sent. Verification codes, password reset, email binding and account security mails depend on SMTP alone, and no notification switch affects them. Admins get a site-wide low balance reminder switch, on by default, next to the SMTP settings. The default threshold is entered as an amount. Users choose which notifications they receive. Low balance reminders are on for everyone by default. Admins can also turn channel notifications and upstream model update notices on or off. The root user receives channel notifications until it turns them off. Other admins receive them after they turn them on. A low balance reminder is sent once, when a charge takes the balance below the threshold. A top-up above the threshold arms it again. Channel notifications reach every admin who has them on, in that admin's language. Email binding codes and account security mails follow the account's language. Saving notification settings no longer clears the language, the sidebar layout or the billing preference. Existing settings keep their behavior, and old clients that do not send the new fields keep the stored switches. Webhook, Bark and Gotify payloads are unchanged.
603 lines
18 KiB
Go
603 lines
18 KiB
Go
package controller
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/i18n"
|
|
"github.com/QuantumNous/new-api/middleware"
|
|
"github.com/QuantumNous/new-api/model"
|
|
"github.com/QuantumNous/new-api/oauth"
|
|
"github.com/QuantumNous/new-api/service"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// CustomOAuthProviderResponse is the response structure for custom OAuth providers
|
|
// It excludes sensitive fields like client_secret
|
|
type CustomOAuthProviderResponse struct {
|
|
Id int `json:"id"`
|
|
Name string `json:"name"`
|
|
Slug string `json:"slug"`
|
|
Icon string `json:"icon"`
|
|
Enabled bool `json:"enabled"`
|
|
ClientId string `json:"client_id"`
|
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
|
TokenEndpoint string `json:"token_endpoint"`
|
|
UserInfoEndpoint string `json:"user_info_endpoint"`
|
|
Scopes string `json:"scopes"`
|
|
UserIdField string `json:"user_id_field"`
|
|
UsernameField string `json:"username_field"`
|
|
DisplayNameField string `json:"display_name_field"`
|
|
EmailField string `json:"email_field"`
|
|
WellKnown string `json:"well_known"`
|
|
AuthStyle int `json:"auth_style"`
|
|
AccessPolicy string `json:"access_policy"`
|
|
AccessDeniedMessage string `json:"access_denied_message"`
|
|
}
|
|
|
|
type UserOAuthBindingResponse struct {
|
|
ProviderId int `json:"provider_id"`
|
|
ProviderName string `json:"provider_name"`
|
|
ProviderSlug string `json:"provider_slug"`
|
|
ProviderIcon string `json:"provider_icon"`
|
|
ProviderUserId string `json:"provider_user_id"`
|
|
}
|
|
|
|
func toCustomOAuthProviderResponse(p *model.CustomOAuthProvider) *CustomOAuthProviderResponse {
|
|
return &CustomOAuthProviderResponse{
|
|
Id: p.Id,
|
|
Name: p.Name,
|
|
Slug: p.Slug,
|
|
Icon: p.Icon,
|
|
Enabled: p.Enabled,
|
|
ClientId: p.ClientId,
|
|
AuthorizationEndpoint: p.AuthorizationEndpoint,
|
|
TokenEndpoint: p.TokenEndpoint,
|
|
UserInfoEndpoint: p.UserInfoEndpoint,
|
|
Scopes: p.Scopes,
|
|
UserIdField: p.UserIdField,
|
|
UsernameField: p.UsernameField,
|
|
DisplayNameField: p.DisplayNameField,
|
|
EmailField: p.EmailField,
|
|
WellKnown: p.WellKnown,
|
|
AuthStyle: p.AuthStyle,
|
|
AccessPolicy: p.AccessPolicy,
|
|
AccessDeniedMessage: p.AccessDeniedMessage,
|
|
}
|
|
}
|
|
|
|
// GetCustomOAuthProviders returns all custom OAuth providers
|
|
func GetCustomOAuthProviders(c *gin.Context) {
|
|
providers, err := model.GetAllCustomOAuthProviders()
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
response := make([]*CustomOAuthProviderResponse, len(providers))
|
|
for i, p := range providers {
|
|
response[i] = toCustomOAuthProviderResponse(p)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": response,
|
|
})
|
|
}
|
|
|
|
// GetCustomOAuthProvider returns a single custom OAuth provider by ID
|
|
func GetCustomOAuthProvider(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil {
|
|
common.ApiErrorT(c, "Invalid ID")
|
|
return
|
|
}
|
|
|
|
provider, err := model.GetCustomOAuthProviderById(id)
|
|
if err != nil {
|
|
common.ApiErrorT(c, "OAuth provider not found")
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": toCustomOAuthProviderResponse(provider),
|
|
})
|
|
}
|
|
|
|
// CreateCustomOAuthProviderRequest is the request structure for creating a custom OAuth provider
|
|
type CreateCustomOAuthProviderRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Slug string `json:"slug" binding:"required"`
|
|
Icon string `json:"icon"`
|
|
Enabled bool `json:"enabled"`
|
|
ClientId string `json:"client_id" binding:"required"`
|
|
ClientSecret string `json:"client_secret" binding:"required"`
|
|
AuthorizationEndpoint string `json:"authorization_endpoint" binding:"required"`
|
|
TokenEndpoint string `json:"token_endpoint" binding:"required"`
|
|
UserInfoEndpoint string `json:"user_info_endpoint" binding:"required"`
|
|
Scopes string `json:"scopes"`
|
|
UserIdField string `json:"user_id_field"`
|
|
UsernameField string `json:"username_field"`
|
|
DisplayNameField string `json:"display_name_field"`
|
|
EmailField string `json:"email_field"`
|
|
WellKnown string `json:"well_known"`
|
|
AuthStyle int `json:"auth_style"`
|
|
AccessPolicy string `json:"access_policy"`
|
|
AccessDeniedMessage string `json:"access_denied_message"`
|
|
}
|
|
|
|
type FetchCustomOAuthDiscoveryRequest struct {
|
|
WellKnownURL string `json:"well_known_url"`
|
|
IssuerURL string `json:"issuer_url"`
|
|
}
|
|
|
|
// FetchCustomOAuthDiscovery fetches OIDC discovery document via backend (root-only route)
|
|
func FetchCustomOAuthDiscovery(c *gin.Context) {
|
|
var req FetchCustomOAuthDiscoveryRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ApiErrorT(c, "Invalid request parameters: {{error}}", map[string]any{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
wellKnownURL := strings.TrimSpace(req.WellKnownURL)
|
|
issuerURL := strings.TrimSpace(req.IssuerURL)
|
|
|
|
if wellKnownURL == "" && issuerURL == "" {
|
|
common.ApiErrorT(c, "Enter the Discovery URL or Issuer URL first")
|
|
return
|
|
}
|
|
|
|
targetURL := wellKnownURL
|
|
if targetURL == "" {
|
|
targetURL = strings.TrimRight(issuerURL, "/") + "/.well-known/openid-configuration"
|
|
}
|
|
targetURL = strings.TrimSpace(targetURL)
|
|
|
|
parsedURL, err := url.Parse(targetURL)
|
|
if err != nil || parsedURL.Host == "" || (parsedURL.Scheme != "http" && parsedURL.Scheme != "https") {
|
|
common.ApiErrorT(c, "Invalid Discovery URL. Only http and https are supported")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 20*time.Second)
|
|
defer cancel()
|
|
|
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil)
|
|
if err != nil {
|
|
common.ApiErrorT(c, "Failed to create the Discovery request: {{error}}", map[string]any{"error": err.Error()})
|
|
return
|
|
}
|
|
httpReq.Header.Set("Accept", "application/json")
|
|
|
|
client := &http.Client{Timeout: 20 * time.Second}
|
|
resp, err := client.Do(httpReq)
|
|
if err != nil {
|
|
common.ApiErrorT(c, "Failed to fetch the Discovery configuration: {{error}}", map[string]any{"error": err.Error()})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
|
|
message := strings.TrimSpace(string(body))
|
|
if message == "" {
|
|
message = resp.Status
|
|
}
|
|
common.ApiErrorT(c, "Failed to fetch the Discovery configuration: {{error}}", map[string]any{"error": message})
|
|
return
|
|
}
|
|
|
|
var discovery map[string]any
|
|
if err = common.DecodeJson(resp.Body, &discovery); err != nil {
|
|
common.ApiErrorT(c, "Failed to parse the Discovery configuration: {{error}}", map[string]any{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": gin.H{
|
|
"well_known_url": targetURL,
|
|
"discovery": discovery,
|
|
},
|
|
})
|
|
}
|
|
|
|
// CreateCustomOAuthProvider creates a new custom OAuth provider
|
|
func CreateCustomOAuthProvider(c *gin.Context) {
|
|
var req CreateCustomOAuthProviderRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ApiErrorT(c, "Invalid request parameters: {{error}}", map[string]any{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Check if slug is already taken
|
|
if model.IsSlugTaken(req.Slug, 0) {
|
|
common.ApiErrorT(c, "This slug is already in use")
|
|
return
|
|
}
|
|
|
|
// Check if slug conflicts with built-in providers
|
|
if oauth.IsProviderRegistered(req.Slug) && !oauth.IsCustomProvider(req.Slug) {
|
|
common.ApiErrorT(c, "This slug conflicts with a built-in OAuth provider")
|
|
return
|
|
}
|
|
|
|
provider := &model.CustomOAuthProvider{
|
|
Name: req.Name,
|
|
Slug: req.Slug,
|
|
Icon: req.Icon,
|
|
Enabled: req.Enabled,
|
|
ClientId: req.ClientId,
|
|
ClientSecret: req.ClientSecret,
|
|
AuthorizationEndpoint: req.AuthorizationEndpoint,
|
|
TokenEndpoint: req.TokenEndpoint,
|
|
UserInfoEndpoint: req.UserInfoEndpoint,
|
|
Scopes: req.Scopes,
|
|
UserIdField: req.UserIdField,
|
|
UsernameField: req.UsernameField,
|
|
DisplayNameField: req.DisplayNameField,
|
|
EmailField: req.EmailField,
|
|
WellKnown: req.WellKnown,
|
|
AuthStyle: req.AuthStyle,
|
|
AccessPolicy: req.AccessPolicy,
|
|
AccessDeniedMessage: req.AccessDeniedMessage,
|
|
}
|
|
|
|
if err := model.CreateCustomOAuthProvider(provider); err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
// Register the provider in the OAuth registry
|
|
oauth.RegisterOrUpdateCustomProvider(provider)
|
|
|
|
common.ApiSuccessT(c, "Provider created successfully", toCustomOAuthProviderResponse(provider))
|
|
}
|
|
|
|
// UpdateCustomOAuthProviderRequest is the request structure for updating a custom OAuth provider
|
|
type UpdateCustomOAuthProviderRequest struct {
|
|
Name string `json:"name"`
|
|
Slug string `json:"slug"`
|
|
Icon *string `json:"icon"` // Optional: if nil, keep existing
|
|
Enabled *bool `json:"enabled"` // Optional: if nil, keep existing
|
|
ClientId string `json:"client_id"`
|
|
ClientSecret string `json:"client_secret"` // Optional: if empty, keep existing
|
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
|
TokenEndpoint string `json:"token_endpoint"`
|
|
UserInfoEndpoint string `json:"user_info_endpoint"`
|
|
Scopes string `json:"scopes"`
|
|
UserIdField string `json:"user_id_field"`
|
|
UsernameField string `json:"username_field"`
|
|
DisplayNameField string `json:"display_name_field"`
|
|
EmailField string `json:"email_field"`
|
|
WellKnown *string `json:"well_known"` // Optional: if nil, keep existing
|
|
AuthStyle *int `json:"auth_style"` // Optional: if nil, keep existing
|
|
AccessPolicy *string `json:"access_policy"` // Optional: if nil, keep existing
|
|
AccessDeniedMessage *string `json:"access_denied_message"` // Optional: if nil, keep existing
|
|
}
|
|
|
|
// UpdateCustomOAuthProvider updates an existing custom OAuth provider
|
|
func UpdateCustomOAuthProvider(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil {
|
|
common.ApiErrorT(c, "Invalid ID")
|
|
return
|
|
}
|
|
|
|
var req UpdateCustomOAuthProviderRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ApiErrorT(c, "Invalid request parameters: {{error}}", map[string]any{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Get existing provider
|
|
provider, err := model.GetCustomOAuthProviderById(id)
|
|
if err != nil {
|
|
common.ApiErrorT(c, "OAuth provider not found")
|
|
return
|
|
}
|
|
|
|
oldSlug := provider.Slug
|
|
|
|
// Check if new slug is taken by another provider
|
|
if req.Slug != "" && req.Slug != provider.Slug {
|
|
if model.IsSlugTaken(req.Slug, id) {
|
|
common.ApiErrorT(c, "This slug is already in use")
|
|
return
|
|
}
|
|
// Check if slug conflicts with built-in providers
|
|
if oauth.IsProviderRegistered(req.Slug) && !oauth.IsCustomProvider(req.Slug) {
|
|
common.ApiErrorT(c, "This slug conflicts with a built-in OAuth provider")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Update fields
|
|
if req.Name != "" {
|
|
provider.Name = req.Name
|
|
}
|
|
if req.Slug != "" {
|
|
provider.Slug = req.Slug
|
|
}
|
|
if req.Icon != nil {
|
|
provider.Icon = *req.Icon
|
|
}
|
|
if req.Enabled != nil {
|
|
provider.Enabled = *req.Enabled
|
|
}
|
|
if req.ClientId != "" {
|
|
provider.ClientId = req.ClientId
|
|
}
|
|
if req.ClientSecret != "" {
|
|
provider.ClientSecret = req.ClientSecret
|
|
}
|
|
if req.AuthorizationEndpoint != "" {
|
|
provider.AuthorizationEndpoint = req.AuthorizationEndpoint
|
|
}
|
|
if req.TokenEndpoint != "" {
|
|
provider.TokenEndpoint = req.TokenEndpoint
|
|
}
|
|
if req.UserInfoEndpoint != "" {
|
|
provider.UserInfoEndpoint = req.UserInfoEndpoint
|
|
}
|
|
if req.Scopes != "" {
|
|
provider.Scopes = req.Scopes
|
|
}
|
|
if req.UserIdField != "" {
|
|
provider.UserIdField = req.UserIdField
|
|
}
|
|
if req.UsernameField != "" {
|
|
provider.UsernameField = req.UsernameField
|
|
}
|
|
if req.DisplayNameField != "" {
|
|
provider.DisplayNameField = req.DisplayNameField
|
|
}
|
|
if req.EmailField != "" {
|
|
provider.EmailField = req.EmailField
|
|
}
|
|
if req.WellKnown != nil {
|
|
provider.WellKnown = *req.WellKnown
|
|
}
|
|
if req.AuthStyle != nil {
|
|
provider.AuthStyle = *req.AuthStyle
|
|
}
|
|
if req.AccessPolicy != nil {
|
|
provider.AccessPolicy = *req.AccessPolicy
|
|
}
|
|
if req.AccessDeniedMessage != nil {
|
|
provider.AccessDeniedMessage = *req.AccessDeniedMessage
|
|
}
|
|
|
|
if err := model.UpdateCustomOAuthProvider(provider); err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
// Update the provider in the OAuth registry
|
|
if oldSlug != provider.Slug {
|
|
oauth.UnregisterCustomProvider(oldSlug)
|
|
}
|
|
oauth.RegisterOrUpdateCustomProvider(provider)
|
|
|
|
common.ApiSuccessT(c, "Provider updated successfully", toCustomOAuthProviderResponse(provider))
|
|
}
|
|
|
|
// DeleteCustomOAuthProvider deletes a custom OAuth provider
|
|
func DeleteCustomOAuthProvider(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil {
|
|
common.ApiErrorT(c, "Invalid ID")
|
|
return
|
|
}
|
|
|
|
// Get existing provider to get slug
|
|
provider, err := model.GetCustomOAuthProviderById(id)
|
|
if err != nil {
|
|
common.ApiErrorT(c, "OAuth provider not found")
|
|
return
|
|
}
|
|
|
|
// Check if there are any user bindings
|
|
count, err := model.GetBindingCountByProviderId(id)
|
|
if err != nil {
|
|
common.SysError(common.LogText("Failed to get binding count for provider %s: %s", strconv.Itoa(id), err.Error()))
|
|
common.ApiErrorT(c, "Failed to check user bindings, please try again later")
|
|
return
|
|
}
|
|
if count > 0 {
|
|
common.ApiErrorT(c, "This OAuth provider still has linked users and cannot be deleted. Unbind all users first.")
|
|
return
|
|
}
|
|
|
|
if err := model.DeleteCustomOAuthProvider(id); err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
// Unregister the provider from the OAuth registry
|
|
oauth.UnregisterCustomProvider(provider.Slug)
|
|
|
|
common.ApiSuccessT(c, "Provider deleted successfully", nil)
|
|
}
|
|
|
|
func buildUserOAuthBindingsResponse(userId int) ([]UserOAuthBindingResponse, error) {
|
|
bindings, err := model.GetUserOAuthBindingsByUserId(userId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response := make([]UserOAuthBindingResponse, 0, len(bindings))
|
|
for _, binding := range bindings {
|
|
provider, err := model.GetCustomOAuthProviderById(binding.ProviderId)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
response = append(response, UserOAuthBindingResponse{
|
|
ProviderId: binding.ProviderId,
|
|
ProviderName: provider.Name,
|
|
ProviderSlug: provider.Slug,
|
|
ProviderIcon: provider.Icon,
|
|
ProviderUserId: binding.ProviderUserId,
|
|
})
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// GetUserOAuthBindings returns all OAuth bindings for the current user
|
|
func GetUserOAuthBindings(c *gin.Context) {
|
|
userId := c.GetInt("id")
|
|
if userId == 0 {
|
|
common.ApiErrorT(c, "Not logged in")
|
|
return
|
|
}
|
|
|
|
response, err := buildUserOAuthBindingsResponse(userId)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": response,
|
|
})
|
|
}
|
|
|
|
func GetUserOAuthBindingsByAdmin(c *gin.Context) {
|
|
userIdStr := c.Param("id")
|
|
userId, err := strconv.Atoi(userIdStr)
|
|
if err != nil {
|
|
common.ApiErrorMsg(c, "invalid user id")
|
|
return
|
|
}
|
|
|
|
targetUser, err := model.GetUserById(userId, false)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
myRole := c.GetInt("role")
|
|
if !canManageTargetRole(myRole, targetUser.Role) {
|
|
common.ApiErrorMsg(c, "no permission")
|
|
return
|
|
}
|
|
|
|
response, err := buildUserOAuthBindingsResponse(userId)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": response,
|
|
})
|
|
}
|
|
|
|
// UnbindCustomOAuth unbinds a custom OAuth provider from the current user
|
|
func UnbindCustomOAuth(c *gin.Context) {
|
|
identity, ok := middleware.GetStepUpIdentity(c)
|
|
if !ok {
|
|
writeSecurityOperationError(c, service.ErrAuthTokenInvalid)
|
|
return
|
|
}
|
|
|
|
providerIdStr := c.Param("provider_id")
|
|
providerId, err := strconv.Atoi(providerIdStr)
|
|
if err != nil || providerId <= 0 {
|
|
common.ApiErrorT(c, "Invalid provider ID")
|
|
return
|
|
}
|
|
|
|
succeeded, notificationFailed := false, false
|
|
defer func() {
|
|
recordUserSecurityAudit(c, identity.UserID, "user.binding_unbind", map[string]any{"provider_id": providerId, "success": succeeded, "notification_failed": notificationFailed})
|
|
}()
|
|
context, err := common.Marshal(service.AccountUnbindingContext{ProviderID: providerId})
|
|
if err != nil {
|
|
writeSecurityOperationError(c, err)
|
|
return
|
|
}
|
|
if middleware.RequireSecurityProof(c, service.VerificationOperation{Scope: service.VerificationScopeAccountUnbind, Context: context}) == nil {
|
|
return
|
|
}
|
|
if err := service.UnbindAccountOAuth(identity, providerId); err != nil {
|
|
writeSecurityOperationError(c, err)
|
|
return
|
|
}
|
|
succeeded = true
|
|
user, err := model.GetUserById(identity.UserID, false)
|
|
if err != nil {
|
|
writeSecurityOperationError(c, err)
|
|
return
|
|
}
|
|
notificationFailed = service.NotifyAccountSecurityChange(service.AccountMailLanguage(c, user.Id), user.Email, i18n.MsgSecurityEventLoginUnlinked) != nil
|
|
|
|
common.ApiSuccessT(c, "Unbound successfully", gin.H{"notification_warning": notificationFailed})
|
|
}
|
|
|
|
func UnbindCustomOAuthByAdmin(c *gin.Context) {
|
|
userIdStr := c.Param("id")
|
|
userId, err := strconv.Atoi(userIdStr)
|
|
if err != nil {
|
|
common.ApiErrorMsg(c, "invalid user id")
|
|
return
|
|
}
|
|
|
|
targetUser, err := model.GetUserById(userId, false)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
myRole := c.GetInt("role")
|
|
if !canManageTargetRole(myRole, targetUser.Role) {
|
|
common.ApiErrorMsg(c, "no permission")
|
|
return
|
|
}
|
|
|
|
providerIdStr := c.Param("provider_id")
|
|
providerId, err := strconv.Atoi(providerIdStr)
|
|
if err != nil || providerId <= 0 {
|
|
common.ApiErrorT(c, "Invalid provider ID")
|
|
return
|
|
}
|
|
authorization := requireAdminUserProof(c, service.VerificationScopeAdminUserBindingClear, service.AdminUserBindingContext{UserID: userId, ProviderID: providerId})
|
|
if authorization == nil {
|
|
return
|
|
}
|
|
|
|
if err := model.DeleteUserOAuthBinding(userId, providerId); err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
recordManageAuditFor(c, userId, "user.binding_clear", map[string]any{
|
|
"bindingType": "custom_oauth",
|
|
"provider_id": providerId,
|
|
"username": targetUser.Username,
|
|
"verification_method": authorization.Method,
|
|
})
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "success",
|
|
})
|
|
}
|