OpenListTeam / OpenListTeam/OpenList

[BUG] 解绑 SSO/OIDC 账号后重新登录报错:UNIQUE constraint failed: x_users.username (2067)

Open Beginner friendly
#2,872 2 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Go
Stars
24.7k
Forks
2.3k
Avg merge
1d 20h
Merged PRs (30d)
36

Description

请确认以下事项
  • 我已确认阅读并同意 AGPL-3.0 第15条
    本程序不提供任何明示或暗示的担保,使用风险由您自行承担。

  • 我已确认阅读并同意 AGPL-3.0 第16条
    无论何种情况,版权持有人或其他分发者均不对使用本程序所造成的任何损失承担责任。

  • 我确认我的描述清晰,语法礼貌,能帮助开发者快速定位问题,并符合社区规则。

  • 我已确认阅读了OpenList文档

  • 我已确认没有重复的问题或讨论。

  • 我已确认是OpenList的问题,而不是其他原因(例如 网络依赖操作)。

  • 我认为此问题必须由OpenList处理,而非第三方。

  • 我已确认这个问题在最新版本中没有被修复。

  • 我没有阅读这个清单,只是闭眼选中了所有的复选框,请关闭这个 Issue 。

OpenList 版本(必填)

v4.2.4

使用的存储驱动(必填)

中国移动云盘

问题描述(必填)

在使用 OIDC/SSO 登录/注册后,若在「个人资料」中解除单点登录绑定并保存,当再次使用同一个 SSO 账号登录时,服务端会抛出 400 错误:

{
  "code": 400,
  "message": "constraint failed: UNIQUE constraint failed: x_users.username (2067)",
  "data": null
}

[!NOTE]

  1. 此 OIDC Provider 为个人自建,但根据 message 信息,问题似乎出在 OpenList 侧
  2. OpenList 运行在 SQLite 下
SSO 配置
Image
复现
  1. 配置并开启单点登录,开启 SSO 自动注册
  2. 使用 OIDC 账号登录系统
  3. 进入「个人资料」设置页面,点击「解绑单点登录平台」并保存
  4. 退出登录后,再次点击通过相同的 OIDC 账号登录
  5. OIDC 页面显示 400 错误:UNIQUE constraint failed: x_users.username (2067)
预期行为

https://github.com/OpenListTeam/OpenList/blob/cf931d4760abbb737e07784b4a3318ab51129785/server/handles/ssologin.go#L161-L170

以上代码中的 user.Username = user.Username + "_" + userID 重试逻辑触发,添加 userID 后缀并继续尝试完成用户创建

个人分析

本人不熟悉 go 语言,以下分析仅供参考

SQLite 返回的错误字符串末尾带有扩展错误码 x_users.username (2067)strings.HasSuffix(err.Error(), "username" 为 false,未能进入重试。若 bug 确实存在,建议强化错误判定方式。

日志(必填)
[GIN] 2026/07/29 - 20:20:29 | 200 | 150.717µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 20:20:31 | 302 |  35.31ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 20:20:53 | 200 |  58.8ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:20:53 | 200 | 358.399µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:20:54 | 200 | 122.728µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 20:21:03 | 302 |  11.81ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=get_sso_id"
[GIN] 2026/07/29 - 20:21:04 | 200 |  59.22ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=get_sso_id&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:21:10 | 200 | 110.543µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 20:21:12 | 302 |   5.53ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 20:21:13 | 200 |  60.71ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:21:14 | 200 | 117.894µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:21:20 | 200 |  99.45µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 20:21:21 | 200 | 127.01µs | xxx.xxx.xxx.xxx | POST     "/api/auth/login/hash"
[GIN] 2026/07/29 - 20:21:21 | 200 | 115.199µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:21:23 | 200 | 431.081µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 20:26:02 | 200 | 135.962µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 20:26:04 | 302 |  28.98ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 20:26:14 | 200 |  64.34ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:26:14 | 200 | 114.665µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:26:37 | 200 | 80.728µs | xxx.xxx.xxx.xxx | GET      "/@manage"
[GIN] 2026/07/29 - 20:26:37 | 200 | 36.202µs | xxx.xxx.xxx.xxx | GET      "/api/public/archive_extensions"
[GIN] 2026/07/29 - 20:26:37 | 200 | 111.087µs | xxx.xxx.xxx.xxx | GET      "/api/public/settings"
[GIN] 2026/07/29 - 20:26:39 | 200 | 93.715µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:26:48 | 200 |   4.09ms | xxx.xxx.xxx.xxx | POST     "/api/me/update"
[GIN] 2026/07/29 - 20:26:52 | 302 |   13.8ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 20:26:52 | 200 |  59.28ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:26:52 | 302 | 248.853µs | xxx.xxx.xxx.xxx | GET      "/favicon.ico"
[GIN] 2026/07/29 - 20:26:58 | 302 |   5.36ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 20:27:01 | 200 |  60.53ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:29:08 | 200 | 369.688µs | xxx.xxx.xxx.xxx | POST     "/api/auth/login/hash"
[GIN] 2026/07/29 - 20:29:08 | 200 | 112.515µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:29:09 | 200 | 404.359µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 20:29:12 | 200 |   2.91ms | xxx.xxx.xxx.xxx | POST     "/api/admin/user/delete?id=3"
[GIN] 2026/07/29 - 20:29:13 | 200 | 416.35µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 20:29:15 | 200 | 106.419µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 20:29:17 | 302 |  27.57ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 20:29:19 | 200 |  62.49ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:29:20 | 200 | 452.693µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:29:20 | 200 | 124.584µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 20:29:25 | 200 | 101.022µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 20:29:27 | 200 | 123.159µs | xxx.xxx.xxx.xxx | POST     "/api/auth/login/hash"
[GIN] 2026/07/29 - 20:29:28 | 200 | 95.771µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:29:30 | 200 | 436.825µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 20:57:50 | 200 | 88.163µs | xxx.xxx.xxx.xxx | GET      "/"
INFO[2026-07-29 21:12:28] Start cache GC                               
[GIN] 2026/07/29 - 21:19:12 | 200 | 95.138µs | xxx.xxx.xxx.xxx | GET      "/"
[GIN] 2026/07/29 - 21:19:13 | 200 | 43.444µs | xxx.xxx.xxx.xxx | GET      "/api/public/archive_extensions"
[GIN] 2026/07/29 - 21:19:13 | 200 | 503.897µs | xxx.xxx.xxx.xxx | GET      "/api/public/settings"
[GIN] 2026/07/29 - 21:19:13 | 200 | 420.961µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 21:19:13 | 200 | 261.469µs | xxx.xxx.xxx.xxx | GET      "/manifest.json"
[GIN] 2026/07/29 - 21:19:14 | 200 | 572.061µs | xxx.xxx.xxx.xxx | GET      "/api/public/offline_download_tools"
[GIN] 2026/07/29 - 21:19:16 | 200 |    2.01s | xxx.xxx.xxx.xxx | POST     "/api/fs/get"
[GIN] 2026/07/29 - 21:19:16 | 200 | 437.027µs | xxx.xxx.xxx.xxx | POST     "/api/fs/list"
[GIN] 2026/07/29 - 21:19:19 | 200 |    3.04s | xxx.xxx.xxx.xxx | GET      "/p/README.md?sign=REDACTED&openlist_ts=1785331156131"
[GIN] 2026/07/29 - 21:19:36 | 200 | 107.153µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 21:19:38 | 200 | 414.933µs | xxx.xxx.xxx.xxx | GET      "/api/admin/setting/list?group=1"
[GIN] 2026/07/29 - 21:19:39 | 200 | 401.118µs | xxx.xxx.xxx.xxx | GET      "/api/admin/setting/list?group=2"
[GIN] 2026/07/29 - 21:19:41 | 200 | 133.167µs | xxx.xxx.xxx.xxx | GET      "/api/admin/setting/list?group=1"
[GIN] 2026/07/29 - 21:20:11 | 200 | 110.509µs | xxx.xxx.xxx.xxx | GET      "/api/admin/driver/names"
[GIN] 2026/07/29 - 21:20:11 | 200 | 756.693µs | xxx.xxx.xxx.xxx | GET      "/api/admin/storage/list"
[GIN] 2026/07/29 - 21:20:13 | 200 |   2.04ms | xxx.xxx.xxx.xxx | GET      "/api/admin/driver/list"
[GIN] 2026/07/29 - 21:20:29 | 200 | 178.972µs | xxx.xxx.xxx.xxx | GET      "/api/admin/driver/names"
[GIN] 2026/07/29 - 21:20:29 | 200 | 573.121µs | xxx.xxx.xxx.xxx | GET      "/api/admin/storage/list"
[GIN] 2026/07/29 - 21:20:54 | 200 | 105.747µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 21:21:00 | 200 | 142.997µs | xxx.xxx.xxx.xxx | POST     "/api/auth/login/hash"
[GIN] 2026/07/29 - 21:21:01 | 200 | 115.768µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 21:21:02 | 200 | 423.79µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 21:21:06 | 200 | 96.809µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 21:21:08 | 302 |  28.91ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 21:21:12 | 200 |  60.31ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 21:21:12 | 200 | 107.428µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 21:21:13 | 200 | 146.15µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 21:21:20 | 200 |   3.81ms | xxx.xxx.xxx.xxx | POST     "/api/me/update"
[GIN] 2026/07/29 - 21:21:23 | 302 |  12.29ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 21:21:24 | 200 |  59.72ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 21:26:14 | 200 | 134.153µs | xxx.xxx.xxx.xxx | POST     "/api/auth/login/hash"
[GIN] 2026/07/29 - 21:26:15 | 200 | 110.177µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 21:26:19 | 200 | 82.903µs | xxx.xxx.xxx.xxx | GET      "/"
[GIN] 2026/07/29 - 21:26:20 | 200 | 118.788µs | xxx.xxx.xxx.xxx | GET      "/api/public/settings"
[GIN] 2026/07/29 - 21:26:20 | 200 | 37.569µs | xxx.xxx.xxx.xxx | GET      "/api/public/archive_extensions"
[GIN] 2026/07/29 - 21:26:20 | 200 | 127.724µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 21:26:21 | 200 | 187.268µs | xxx.xxx.xxx.xxx | POST     "/api/fs/get"
[GIN] 2026/07/29 - 21:26:21 | 200 | 461.73µs | xxx.xxx.xxx.xxx | GET      "/api/public/offline_download_tools"
[GIN] 2026/07/29 - 21:26:22 | 200 | 208.756µs | xxx.xxx.xxx.xxx | POST     "/api/fs/list"
[GIN] 2026/07/29 - 21:26:34 | 200 |   11.59s | xxx.xxx.xxx.xxx | GET      "/p/README.md?sign=REDACTED&openlist_ts=1785331582433"

日志中的 OIDC 相关信息经过脱敏

配置文件内容(必填)
{
  "force": false,
  "site_url": "",
  "cdn": "",
  "jwt_secret": "REDACTED",
  "token_expires_in": 48,
  "database": {
    "type": "sqlite3",
    "host": "",
    "port": 0,
    "user": "",
    "password": "",
    "name": "",
    "db_file": "data/data.db",
    "table_prefix": "x_",
    "ssl_mode": "",
    "dsn": ""
  },
  "meilisearch": {
    "host": "http://localhost:7700",
    "api_key": "",
    "index": "openlist"
  },
  "scheme": {
    "address": "0.0.0.0",
    "http_port": 5244,
    "https_port": -1,
    "force_https": false,
    "cert_file": "",
    "key_file": "",
    "unix_file": "",
    "unix_file_perm": "",
    "enable_h2c": false,
    "enable_h3": false
  },
  "temp_dir": "data/temp",
  "bleve_dir": "data/bleve",
  "dist_dir": "",
  "log": {
    "enable": true,
    "name": "data/log/log.log",
    "max_size": 50,
    "max_backups": 30,
    "max_age": 28,
    "compress": false,
    "filter": {
      "enable": false,
      "filters": [
        {
          "cidr": "",
          "path": "/ping",
          "method": ""
        },
        {
          "cidr": "",
          "path": "",
          "method": "HEAD"
        },
        {
          "cidr": "",
          "path": "/dav/",
          "method": "PROPFIND"
        }
      ]
    }
  },
  "delayed_start": 0,
  "auto_memory_limit": 4,
  "min_free_memory": 0,
  "max_block_limit": 0,
  "max_connections": 0,
  "max_concurrency": 64,
  "tls_insecure_skip_verify": false,
  "tasks": {
    "download": {
      "workers": 5,
      "max_retry": 1,
      "task_persistant": false
    },
    "transfer": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": false
    },
    "upload": {
      "workers": 5,
      "max_retry": 0,
      "task_persistant": false
    },
    "copy": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": false
    },
    "move": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": false
    },
    "decompress": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": false
    },
    "decompress_upload": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": false
    },
    "allow_retry_canceled": false
  },
  "cors": {
    "allow_origins": [
      "*"
    ],
    "allow_methods": [
      "*"
    ],
    "allow_headers": [
      "*"
    ]
  },
  "s3": {
    "enable": false,
    "port": 5246,
    "ssl": false
  },
  "ftp": {
    "enable": false,
    "listen": ":5221",
    "find_pasv_port_attempts": 50,
    "active_transfer_port_non_20": false,
    "idle_timeout": 900,
    "connection_timeout": 30,
    "disable_active_mode": false,
    "default_transfer_binary": false,
    "enable_active_conn_ip_check": true,
    "enable_pasv_conn_ip_check": true
  },
  "sftp": {
    "enable": false,
    "listen": ":5222"
  },
  "mcp": {
    "enable": false
  },
  "last_launched_version": "v4.2.4",
  "proxy_address": ""
}
复现链接(可选)

No response

AI生成内容(可选)

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at server/handles/ssologin.go#L161-L170, then reproduce the SQLite OIDC flow by unbinding the account and logging in again. Check how the UNIQUE constraint error is matched when it includes the table name and error code; done means the existing retry behavior is reached and the same SSO account can log in without a 400 error.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, sqlite
Domain
authentication, backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.