fix: restore soft-deleted users on login
This commit is contained in:
@@ -139,6 +139,11 @@ func DiscordOAuth(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := user.RestoreIfDeleted("discord", c.ClientIP()); err != nil {
|
||||||
|
common.SysError(fmt.Sprintf("failed to restore user %d: %v", user.Id, err))
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if common.RegisterEnabled {
|
if common.RegisterEnabled {
|
||||||
if discordUser.ID != "" {
|
if discordUser.ID != "" {
|
||||||
|
|||||||
@@ -122,12 +122,9 @@ func GitHubOAuth(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// if user.Id == 0 , user has been deleted
|
if err := user.RestoreIfDeleted("github", c.ClientIP()); err != nil {
|
||||||
if user.Id == 0 {
|
common.SysError(fmt.Sprintf("failed to restore user %d: %v", user.Id, err))
|
||||||
c.JSON(http.StatusOK, gin.H{
|
common.ApiError(c, err)
|
||||||
"success": false,
|
|
||||||
"message": "用户已注销",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -212,11 +212,9 @@ func LinuxdoOAuth(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if user.Id == 0 {
|
if err := user.RestoreIfDeleted("linuxdo", c.ClientIP()); err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
common.SysError(fmt.Sprintf("failed to restore user %d: %v", user.Id, err))
|
||||||
"success": false,
|
common.ApiError(c, err)
|
||||||
"message": "用户已注销",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+7
-3
@@ -213,9 +213,9 @@ func findOrCreateOAuthUser(c *gin.Context, provider oauth.Provider, oauthUser *o
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Check if user has been deleted
|
if err := user.RestoreIfDeleted(provider.GetName(), c.ClientIP()); err != nil {
|
||||||
if user.Id == 0 {
|
common.SysError(fmt.Sprintf("[OAuth] Failed to restore user %d: %s", user.Id, err.Error()))
|
||||||
return nil, &OAuthUserDeletedError{}
|
return nil, err
|
||||||
}
|
}
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
@@ -227,6 +227,10 @@ func findOrCreateOAuthUser(c *gin.Context, provider oauth.Provider, oauthUser *o
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := user.RestoreIfDeleted(provider.GetName(), c.ClientIP()); err != nil {
|
||||||
|
common.SysError(fmt.Sprintf("[OAuth] Failed to restore user %d: %s", user.Id, err.Error()))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if user.Id != 0 {
|
if user.Id != 0 {
|
||||||
// Found user with legacy ID, migrate to new ID
|
// Found user with legacy ID, migrate to new ID
|
||||||
common.SysLog(fmt.Sprintf("[OAuth] Migrating user %d from legacy_id=%s to new_id=%s",
|
common.SysLog(fmt.Sprintf("[OAuth] Migrating user %d from legacy_id=%s to new_id=%s",
|
||||||
|
|||||||
@@ -141,6 +141,11 @@ func OidcAuth(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := user.RestoreIfDeleted("oidc", c.ClientIP()); err != nil {
|
||||||
|
common.SysError(fmt.Sprintf("failed to restore user %d: %v", user.Id, err))
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if common.RegisterEnabled {
|
if common.RegisterEnabled {
|
||||||
user.Email = oidcUser.Email
|
user.Email = oidcUser.Email
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ func TelegramLogin(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := user.RestoreIfDeleted("telegram", c.ClientIP()); err != nil {
|
||||||
|
common.SysError("failed to restore user: " + err.Error())
|
||||||
|
common.ApiError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
setupLogin(&user, c)
|
setupLogin(&user, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -51,7 +51,7 @@ func Login(c *gin.Context) {
|
|||||||
Username: username,
|
Username: username,
|
||||||
Password: password,
|
Password: password,
|
||||||
}
|
}
|
||||||
err = user.ValidateAndFill()
|
err = user.ValidateAndFill(c.ClientIP())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, model.ErrDatabase):
|
case errors.Is(err, model.ErrDatabase):
|
||||||
|
|||||||
@@ -82,11 +82,9 @@ func WeChatAuth(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if user.Id == 0 {
|
if err := user.RestoreIfDeleted("wechat", c.ClientIP()); err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
common.SysError(fmt.Sprintf("failed to restore user %d: %v", user.Id, err))
|
||||||
"success": false,
|
common.ApiError(c, err)
|
||||||
"message": "用户已注销",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$root = Split-Path -Parent $PSCommandPath
|
||||||
|
$backendPort = 3000
|
||||||
|
$frontendPort = 5173
|
||||||
|
|
||||||
|
function Test-TcpPort {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int]$Port
|
||||||
|
)
|
||||||
|
|
||||||
|
$client = [System.Net.Sockets.TcpClient]::new()
|
||||||
|
try {
|
||||||
|
$task = $client.ConnectAsync("127.0.0.1", $Port)
|
||||||
|
if (-not $task.Wait(500)) {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
return $client.Connected
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$client.Dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Assert-PortFree {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Name,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int]$Port
|
||||||
|
)
|
||||||
|
|
||||||
|
if (Test-TcpPort -Port $Port) {
|
||||||
|
Write-Host "[error] $Name 端口 $Port 已被占用,请先停止占用该端口的进程" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Wait-Port {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Name,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int]$Port,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[System.Diagnostics.Process]$Process,
|
||||||
|
[int]$TimeoutSeconds = 90
|
||||||
|
)
|
||||||
|
|
||||||
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||||
|
while ((Get-Date) -lt $deadline) {
|
||||||
|
if ($Process.HasExited) {
|
||||||
|
Write-Host "[error] $Name 进程已退出,退出码 $($Process.ExitCode),请查看对应窗口日志" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if (Test-TcpPort -Port $Port) {
|
||||||
|
Write-Host "[$Name] 已就绪 (port $Port)" -ForegroundColor Green
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "[error] $Name 未在 $TimeoutSeconds 秒内监听端口 $Port,请查看对应窗口日志" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 0. 初始化 PATH
|
||||||
|
$env:Path = "C:\Program Files\Go\bin;C:\Users\Chaos\.bun\bin;$env:Path"
|
||||||
|
|
||||||
|
# 1. 检查 .env
|
||||||
|
if (-not (Test-Path "$root\.env")) {
|
||||||
|
Write-Host "[setup] 复制 .env.example -> .env" -ForegroundColor Yellow
|
||||||
|
Copy-Item "$root\.env.example" "$root\.env"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. 检查前端依赖
|
||||||
|
if (-not (Test-Path "$root\web\default\node_modules")) {
|
||||||
|
Write-Host "[setup] 安装前端依赖..." -ForegroundColor Yellow
|
||||||
|
Set-Location "$root\web\default"
|
||||||
|
bun install
|
||||||
|
Set-Location $root
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "[error] bun install 失败" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 3. 创建 go:embed 所需目录
|
||||||
|
$embedDirs = @("web\default\dist", "web\classic\dist", "web\image-gen\dist")
|
||||||
|
foreach ($dir in $embedDirs) {
|
||||||
|
$fullPath = Join-Path $root $dir
|
||||||
|
if (-not (Test-Path $fullPath)) {
|
||||||
|
New-Item -ItemType Directory -Force -Path $fullPath | Out-Null
|
||||||
|
}
|
||||||
|
$indexFile = Join-Path $fullPath "index.html"
|
||||||
|
if (-not (Test-Path $indexFile)) {
|
||||||
|
Set-Content -Path $indexFile -Value "<!DOCTYPE html><html></html>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 4. 初始化 PATH
|
||||||
|
$goPath = "C:\Program Files\Go\bin"
|
||||||
|
$bunPath = "C:\Users\Chaos\.bun\bin"
|
||||||
|
$initPath = "`$env:Path = '$goPath;$bunPath;' + `$env:Path;"
|
||||||
|
|
||||||
|
# 5. 检查端口占用
|
||||||
|
Assert-PortFree -Name "Backend" -Port $backendPort
|
||||||
|
Assert-PortFree -Name "Frontend" -Port $frontendPort
|
||||||
|
|
||||||
|
# 6. 启动后端
|
||||||
|
Write-Host "[backend] 启动 API 服务 (port $backendPort)..." -ForegroundColor Green
|
||||||
|
$backendJob = Start-Process -FilePath "powershell" -ArgumentList "-NoExit", "-Command", "$initPath Set-Location '$root'; Write-Host '=== Backend :$backendPort ===' -ForegroundColor Cyan; go run main.go" -PassThru
|
||||||
|
|
||||||
|
# 7. 启动前端
|
||||||
|
Write-Host "[frontend] 启动前端开发服务 (port $frontendPort)..." -ForegroundColor Green
|
||||||
|
$frontendJob = Start-Process -FilePath "powershell" -ArgumentList "-NoExit", "-Command", "$initPath Set-Location '$root\web\default'; Write-Host '=== Frontend :$frontendPort ===' -ForegroundColor Magenta; bun run dev" -PassThru
|
||||||
|
|
||||||
|
# 8. 等待服务就绪
|
||||||
|
Wait-Port -Name "Backend" -Port $backendPort -Process $backendJob -TimeoutSeconds 90
|
||||||
|
Wait-Port -Name "Frontend" -Port $frontendPort -Process $frontendJob -TimeoutSeconds 60
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "==============================" -ForegroundColor White
|
||||||
|
Write-Host " Backend : http://localhost:$backendPort" -ForegroundColor Cyan
|
||||||
|
Write-Host " Frontend : http://localhost:$frontendPort" -ForegroundColor Magenta
|
||||||
|
Write-Host "==============================" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "关闭窗口即可停止服务" -ForegroundColor Gray
|
||||||
@@ -88,6 +88,33 @@ func GetLogByTokenId(tokenId int) (logs []*Log, err error) {
|
|||||||
return logs, err
|
return logs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RecordUserRestoreLog writes an audit-log entry whenever a soft-deleted user
|
||||||
|
// is automatically restored (e.g. by logging in again via password or OAuth).
|
||||||
|
// `source` describes the trigger, e.g. "github", "linuxdo", "telegram", "password".
|
||||||
|
// `callerIp` may be empty when the call originates from the model layer.
|
||||||
|
func RecordUserRestoreLog(userId int, source string, callerIp string) {
|
||||||
|
username, _ := GetUsernameById(userId, false)
|
||||||
|
other := map[string]interface{}{}
|
||||||
|
if source != "" {
|
||||||
|
other["restore_source"] = source
|
||||||
|
}
|
||||||
|
if callerIp != "" {
|
||||||
|
other["caller_ip"] = callerIp
|
||||||
|
}
|
||||||
|
log := &Log{
|
||||||
|
UserId: userId,
|
||||||
|
Username: username,
|
||||||
|
CreatedAt: common.GetTimestamp(),
|
||||||
|
Type: LogTypeSystem,
|
||||||
|
Content: fmt.Sprintf("软删除用户被自动恢复,来源 %s", source),
|
||||||
|
Ip: callerIp,
|
||||||
|
Other: common.MapToJsonStr(other),
|
||||||
|
}
|
||||||
|
if err := LOG_DB.Create(log).Error; err != nil {
|
||||||
|
common.SysLog("failed to record user restore log: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func RecordLog(userId int, logType int, content string) {
|
func RecordLog(userId int, logType int, content string) {
|
||||||
if logType == LogTypeConsume && !common.LogConsumeEnabled {
|
if logType == LogTypeConsume && !common.LogConsumeEnabled {
|
||||||
return
|
return
|
||||||
|
|||||||
+38
-13
@@ -589,18 +589,40 @@ func (user *User) HardDelete() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user *User) Restore() error {
|
||||||
|
if user.Id == 0 {
|
||||||
|
return errors.New("id 为空!")
|
||||||
|
}
|
||||||
|
err := DB.Unscoped().Model(user).Update("deleted_at", nil).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := invalidateUserCache(user.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.DeletedAt = gorm.DeletedAt{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) RestoreIfDeleted(source string, callerIp string) error {
|
||||||
|
if !user.DeletedAt.Valid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := user.Restore(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
RecordUserRestoreLog(user.Id, source, callerIp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateAndFill check password & user status
|
// ValidateAndFill check password & user status
|
||||||
func (user *User) ValidateAndFill() (err error) {
|
func (user *User) ValidateAndFill(callerIp string) (err error) {
|
||||||
// When querying with struct, GORM will only query with non-zero fields,
|
|
||||||
// that means if your field's value is 0, '', false or other zero values,
|
|
||||||
// it won't be used to build query conditions
|
|
||||||
password := user.Password
|
password := user.Password
|
||||||
username := strings.TrimSpace(user.Username)
|
username := strings.TrimSpace(user.Username)
|
||||||
if username == "" || password == "" {
|
if username == "" || password == "" {
|
||||||
return ErrUserEmptyCredentials
|
return ErrUserEmptyCredentials
|
||||||
}
|
}
|
||||||
// find by username or email
|
err = DB.Unscoped().Where("username = ? OR email = ?", username, username).First(user).Error
|
||||||
err = DB.Where("username = ? OR email = ?", username, username).First(user).Error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return ErrInvalidCredentials
|
return ErrInvalidCredentials
|
||||||
@@ -611,6 +633,9 @@ func (user *User) ValidateAndFill() (err error) {
|
|||||||
if !okay || user.Status != common.UserStatusEnabled {
|
if !okay || user.Status != common.UserStatusEnabled {
|
||||||
return ErrInvalidCredentials
|
return ErrInvalidCredentials
|
||||||
}
|
}
|
||||||
|
if err := user.RestoreIfDeleted("password", callerIp); err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrDatabase, err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,7 +659,7 @@ func (user *User) FillUserByGitHubId() error {
|
|||||||
if user.GitHubId == "" {
|
if user.GitHubId == "" {
|
||||||
return errors.New("GitHub id 为空!")
|
return errors.New("GitHub id 为空!")
|
||||||
}
|
}
|
||||||
DB.Where(User{GitHubId: user.GitHubId}).First(user)
|
DB.Unscoped().Where(User{GitHubId: user.GitHubId}).First(user)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,7 +675,7 @@ func (user *User) FillUserByDiscordId() error {
|
|||||||
if user.DiscordId == "" {
|
if user.DiscordId == "" {
|
||||||
return errors.New("discord id 为空!")
|
return errors.New("discord id 为空!")
|
||||||
}
|
}
|
||||||
DB.Where(User{DiscordId: user.DiscordId}).First(user)
|
DB.Unscoped().Where(User{DiscordId: user.DiscordId}).First(user)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,7 +683,7 @@ func (user *User) FillUserByOidcId() error {
|
|||||||
if user.OidcId == "" {
|
if user.OidcId == "" {
|
||||||
return errors.New("oidc id 为空!")
|
return errors.New("oidc id 为空!")
|
||||||
}
|
}
|
||||||
DB.Where(User{OidcId: user.OidcId}).First(user)
|
DB.Unscoped().Where(User{OidcId: user.OidcId}).First(user)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,7 +691,7 @@ func (user *User) FillUserByWeChatId() error {
|
|||||||
if user.WeChatId == "" {
|
if user.WeChatId == "" {
|
||||||
return errors.New("WeChat id 为空!")
|
return errors.New("WeChat id 为空!")
|
||||||
}
|
}
|
||||||
DB.Where(User{WeChatId: user.WeChatId}).First(user)
|
DB.Unscoped().Where(User{WeChatId: user.WeChatId}).First(user)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,7 +699,7 @@ func (user *User) FillUserByTelegramId() error {
|
|||||||
if user.TelegramId == "" {
|
if user.TelegramId == "" {
|
||||||
return errors.New("Telegram id 为空!")
|
return errors.New("Telegram id 为空!")
|
||||||
}
|
}
|
||||||
err := DB.Where(User{TelegramId: user.TelegramId}).First(user).Error
|
err := DB.Unscoped().Where(User{TelegramId: user.TelegramId}).First(user).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return errors.New("该 Telegram 账户未绑定")
|
return errors.New("该 Telegram 账户未绑定")
|
||||||
}
|
}
|
||||||
@@ -698,7 +723,7 @@ func IsDiscordIdAlreadyTaken(discordId string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsOidcIdAlreadyTaken(oidcId string) bool {
|
func IsOidcIdAlreadyTaken(oidcId string) bool {
|
||||||
return DB.Where("oidc_id = ?", oidcId).Find(&User{}).RowsAffected == 1
|
return DB.Unscoped().Where("oidc_id = ?", oidcId).Find(&User{}).RowsAffected == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsTelegramIdAlreadyTaken(telegramId string) bool {
|
func IsTelegramIdAlreadyTaken(telegramId string) bool {
|
||||||
@@ -1057,7 +1082,7 @@ func (user *User) FillUserByLinuxDOId() error {
|
|||||||
if user.LinuxDOId == "" {
|
if user.LinuxDOId == "" {
|
||||||
return errors.New("linux do id is empty")
|
return errors.New("linux do id is empty")
|
||||||
}
|
}
|
||||||
err := DB.Where("linux_do_id = ?", user.LinuxDOId).First(user).Error
|
err := DB.Unscoped().Where("linux_do_id = ?", user.LinuxDOId).First(user).Error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import (
|
|||||||
// UserOAuthBinding stores the binding relationship between users and custom OAuth providers
|
// UserOAuthBinding stores the binding relationship between users and custom OAuth providers
|
||||||
type UserOAuthBinding struct {
|
type UserOAuthBinding struct {
|
||||||
Id int `json:"id" gorm:"primaryKey"`
|
Id int `json:"id" gorm:"primaryKey"`
|
||||||
UserId int `json:"user_id" gorm:"not null;uniqueIndex:ux_user_provider"` // User ID - one binding per user per provider
|
UserId int `json:"user_id" gorm:"not null;uniqueIndex:ux_user_provider"` // User ID - one binding per user per provider
|
||||||
ProviderId int `json:"provider_id" gorm:"not null;uniqueIndex:ux_user_provider;uniqueIndex:ux_provider_userid"` // Custom OAuth provider ID
|
ProviderId int `json:"provider_id" gorm:"not null;uniqueIndex:ux_user_provider;uniqueIndex:ux_provider_userid"` // Custom OAuth provider ID
|
||||||
ProviderUserId string `json:"provider_user_id" gorm:"type:varchar(256);not null;uniqueIndex:ux_provider_userid"` // User ID from OAuth provider - one OAuth account per provider
|
ProviderUserId string `json:"provider_user_id" gorm:"type:varchar(256);not null;uniqueIndex:ux_provider_userid"` // User ID from OAuth provider - one OAuth account per provider
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ func GetUserByOAuthBinding(providerId int, providerUserId string) (*User, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var user User
|
var user User
|
||||||
err = DB.First(&user, binding.UserId).Error
|
err = DB.Unscoped().First(&user, binding.UserId).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
@@ -65,6 +65,7 @@ export default defineConfig(({ envMode }) => {
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
|
port: 5173,
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
proxy: devProxy,
|
proxy: devProxy,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user