passkey.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. package controller
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "strconv"
  7. "time"
  8. "one-api/common"
  9. "one-api/model"
  10. passkeysvc "one-api/service/passkey"
  11. "one-api/setting/system_setting"
  12. "github.com/gin-contrib/sessions"
  13. "github.com/gin-gonic/gin"
  14. "github.com/go-webauthn/webauthn/protocol"
  15. webauthnlib "github.com/go-webauthn/webauthn/webauthn"
  16. )
  17. func PasskeyRegisterBegin(c *gin.Context) {
  18. if !system_setting.GetPasskeySettings().Enabled {
  19. c.JSON(http.StatusOK, gin.H{
  20. "success": false,
  21. "message": "管理员未启用 Passkey 登录",
  22. })
  23. return
  24. }
  25. user, err := getSessionUser(c)
  26. if err != nil {
  27. c.JSON(http.StatusUnauthorized, gin.H{
  28. "success": false,
  29. "message": err.Error(),
  30. })
  31. return
  32. }
  33. credential, err := model.GetPasskeyByUserID(user.Id)
  34. if err != nil && !errors.Is(err, model.ErrPasskeyNotFound) {
  35. common.ApiError(c, err)
  36. return
  37. }
  38. if errors.Is(err, model.ErrPasskeyNotFound) {
  39. credential = nil
  40. }
  41. wa, err := passkeysvc.BuildWebAuthn(c.Request)
  42. if err != nil {
  43. common.ApiError(c, err)
  44. return
  45. }
  46. waUser := passkeysvc.NewWebAuthnUser(user, credential)
  47. var options []webauthnlib.RegistrationOption
  48. if credential != nil {
  49. descriptor := credential.ToWebAuthnCredential().Descriptor()
  50. options = append(options, webauthnlib.WithExclusions([]protocol.CredentialDescriptor{descriptor}))
  51. }
  52. creation, sessionData, err := wa.BeginRegistration(waUser, options...)
  53. if err != nil {
  54. common.ApiError(c, err)
  55. return
  56. }
  57. if err := passkeysvc.SaveSessionData(c, passkeysvc.RegistrationSessionKey, sessionData); err != nil {
  58. common.ApiError(c, err)
  59. return
  60. }
  61. c.JSON(http.StatusOK, gin.H{
  62. "success": true,
  63. "message": "",
  64. "data": gin.H{
  65. "options": creation,
  66. },
  67. })
  68. }
  69. func PasskeyRegisterFinish(c *gin.Context) {
  70. if !system_setting.GetPasskeySettings().Enabled {
  71. c.JSON(http.StatusOK, gin.H{
  72. "success": false,
  73. "message": "管理员未启用 Passkey 登录",
  74. })
  75. return
  76. }
  77. user, err := getSessionUser(c)
  78. if err != nil {
  79. c.JSON(http.StatusUnauthorized, gin.H{
  80. "success": false,
  81. "message": err.Error(),
  82. })
  83. return
  84. }
  85. wa, err := passkeysvc.BuildWebAuthn(c.Request)
  86. if err != nil {
  87. common.ApiError(c, err)
  88. return
  89. }
  90. credentialRecord, err := model.GetPasskeyByUserID(user.Id)
  91. if err != nil && !errors.Is(err, model.ErrPasskeyNotFound) {
  92. common.ApiError(c, err)
  93. return
  94. }
  95. if errors.Is(err, model.ErrPasskeyNotFound) {
  96. credentialRecord = nil
  97. }
  98. sessionData, err := passkeysvc.PopSessionData(c, passkeysvc.RegistrationSessionKey)
  99. if err != nil {
  100. common.ApiError(c, err)
  101. return
  102. }
  103. waUser := passkeysvc.NewWebAuthnUser(user, credentialRecord)
  104. credential, err := wa.FinishRegistration(waUser, *sessionData, c.Request)
  105. if err != nil {
  106. common.ApiError(c, err)
  107. return
  108. }
  109. passkeyCredential := model.NewPasskeyCredentialFromWebAuthn(user.Id, credential)
  110. if passkeyCredential == nil {
  111. common.ApiErrorMsg(c, "无法创建 Passkey 凭证")
  112. return
  113. }
  114. if err := model.UpsertPasskeyCredential(passkeyCredential); err != nil {
  115. common.ApiError(c, err)
  116. return
  117. }
  118. c.JSON(http.StatusOK, gin.H{
  119. "success": true,
  120. "message": "Passkey 注册成功",
  121. })
  122. }
  123. func PasskeyDelete(c *gin.Context) {
  124. user, err := getSessionUser(c)
  125. if err != nil {
  126. c.JSON(http.StatusUnauthorized, gin.H{
  127. "success": false,
  128. "message": err.Error(),
  129. })
  130. return
  131. }
  132. if err := model.DeletePasskeyByUserID(user.Id); err != nil {
  133. common.ApiError(c, err)
  134. return
  135. }
  136. c.JSON(http.StatusOK, gin.H{
  137. "success": true,
  138. "message": "Passkey 已解绑",
  139. })
  140. }
  141. func PasskeyStatus(c *gin.Context) {
  142. user, err := getSessionUser(c)
  143. if err != nil {
  144. c.JSON(http.StatusUnauthorized, gin.H{
  145. "success": false,
  146. "message": err.Error(),
  147. })
  148. return
  149. }
  150. credential, err := model.GetPasskeyByUserID(user.Id)
  151. if errors.Is(err, model.ErrPasskeyNotFound) {
  152. c.JSON(http.StatusOK, gin.H{
  153. "success": true,
  154. "message": "",
  155. "data": gin.H{
  156. "enabled": false,
  157. },
  158. })
  159. return
  160. }
  161. if err != nil {
  162. common.ApiError(c, err)
  163. return
  164. }
  165. data := gin.H{
  166. "enabled": true,
  167. "last_used_at": credential.LastUsedAt,
  168. }
  169. c.JSON(http.StatusOK, gin.H{
  170. "success": true,
  171. "message": "",
  172. "data": data,
  173. })
  174. }
  175. func PasskeyLoginBegin(c *gin.Context) {
  176. if !system_setting.GetPasskeySettings().Enabled {
  177. c.JSON(http.StatusOK, gin.H{
  178. "success": false,
  179. "message": "管理员未启用 Passkey 登录",
  180. })
  181. return
  182. }
  183. wa, err := passkeysvc.BuildWebAuthn(c.Request)
  184. if err != nil {
  185. common.ApiError(c, err)
  186. return
  187. }
  188. assertion, sessionData, err := wa.BeginDiscoverableLogin()
  189. if err != nil {
  190. common.ApiError(c, err)
  191. return
  192. }
  193. if err := passkeysvc.SaveSessionData(c, passkeysvc.LoginSessionKey, sessionData); err != nil {
  194. common.ApiError(c, err)
  195. return
  196. }
  197. c.JSON(http.StatusOK, gin.H{
  198. "success": true,
  199. "message": "",
  200. "data": gin.H{
  201. "options": assertion,
  202. },
  203. })
  204. }
  205. func PasskeyLoginFinish(c *gin.Context) {
  206. if !system_setting.GetPasskeySettings().Enabled {
  207. c.JSON(http.StatusOK, gin.H{
  208. "success": false,
  209. "message": "管理员未启用 Passkey 登录",
  210. })
  211. return
  212. }
  213. wa, err := passkeysvc.BuildWebAuthn(c.Request)
  214. if err != nil {
  215. common.ApiError(c, err)
  216. return
  217. }
  218. sessionData, err := passkeysvc.PopSessionData(c, passkeysvc.LoginSessionKey)
  219. if err != nil {
  220. common.ApiError(c, err)
  221. return
  222. }
  223. handler := func(rawID, userHandle []byte) (webauthnlib.User, error) {
  224. // 首先通过凭证ID查找用户
  225. credential, err := model.GetPasskeyByCredentialID(rawID)
  226. if err != nil {
  227. return nil, fmt.Errorf("未找到 Passkey 凭证: %w", err)
  228. }
  229. // 通过凭证获取用户
  230. user := &model.User{Id: credential.UserID}
  231. if err := user.FillUserById(); err != nil {
  232. return nil, fmt.Errorf("用户信息获取失败: %w", err)
  233. }
  234. if user.Status != common.UserStatusEnabled {
  235. return nil, errors.New("该用户已被禁用")
  236. }
  237. if len(userHandle) > 0 {
  238. userID, parseErr := strconv.Atoi(string(userHandle))
  239. if parseErr != nil {
  240. // 记录异常但继续验证,因为某些客户端可能使用非数字格式
  241. common.SysLog(fmt.Sprintf("PasskeyLogin: userHandle parse error for credential, length: %d", len(userHandle)))
  242. } else if userID != user.Id {
  243. return nil, errors.New("用户句柄与凭证不匹配")
  244. }
  245. }
  246. return passkeysvc.NewWebAuthnUser(user, credential), nil
  247. }
  248. waUser, credential, err := wa.FinishPasskeyLogin(handler, *sessionData, c.Request)
  249. if err != nil {
  250. common.ApiError(c, err)
  251. return
  252. }
  253. userWrapper, ok := waUser.(*passkeysvc.WebAuthnUser)
  254. if !ok {
  255. common.ApiErrorMsg(c, "Passkey 登录状态异常")
  256. return
  257. }
  258. modelUser := userWrapper.ModelUser()
  259. if modelUser == nil {
  260. common.ApiErrorMsg(c, "Passkey 登录状态异常")
  261. return
  262. }
  263. if modelUser.Status != common.UserStatusEnabled {
  264. common.ApiErrorMsg(c, "该用户已被禁用")
  265. return
  266. }
  267. // 更新凭证信息
  268. updatedCredential := model.NewPasskeyCredentialFromWebAuthn(modelUser.Id, credential)
  269. if updatedCredential == nil {
  270. common.ApiErrorMsg(c, "Passkey 凭证更新失败")
  271. return
  272. }
  273. now := time.Now()
  274. updatedCredential.LastUsedAt = &now
  275. if err := model.UpsertPasskeyCredential(updatedCredential); err != nil {
  276. common.ApiError(c, err)
  277. return
  278. }
  279. setupLogin(modelUser, c)
  280. return
  281. }
  282. func AdminResetPasskey(c *gin.Context) {
  283. id, err := strconv.Atoi(c.Param("id"))
  284. if err != nil {
  285. common.ApiErrorMsg(c, "无效的用户 ID")
  286. return
  287. }
  288. user := &model.User{Id: id}
  289. if err := user.FillUserById(); err != nil {
  290. common.ApiError(c, err)
  291. return
  292. }
  293. if _, err := model.GetPasskeyByUserID(user.Id); err != nil {
  294. if errors.Is(err, model.ErrPasskeyNotFound) {
  295. c.JSON(http.StatusOK, gin.H{
  296. "success": false,
  297. "message": "该用户尚未绑定 Passkey",
  298. })
  299. return
  300. }
  301. common.ApiError(c, err)
  302. return
  303. }
  304. if err := model.DeletePasskeyByUserID(user.Id); err != nil {
  305. common.ApiError(c, err)
  306. return
  307. }
  308. c.JSON(http.StatusOK, gin.H{
  309. "success": true,
  310. "message": "Passkey 已重置",
  311. })
  312. }
  313. func PasskeyVerifyBegin(c *gin.Context) {
  314. if !system_setting.GetPasskeySettings().Enabled {
  315. c.JSON(http.StatusOK, gin.H{
  316. "success": false,
  317. "message": "管理员未启用 Passkey 登录",
  318. })
  319. return
  320. }
  321. user, err := getSessionUser(c)
  322. if err != nil {
  323. c.JSON(http.StatusUnauthorized, gin.H{
  324. "success": false,
  325. "message": err.Error(),
  326. })
  327. return
  328. }
  329. credential, err := model.GetPasskeyByUserID(user.Id)
  330. if err != nil {
  331. c.JSON(http.StatusOK, gin.H{
  332. "success": false,
  333. "message": "该用户尚未绑定 Passkey",
  334. })
  335. return
  336. }
  337. wa, err := passkeysvc.BuildWebAuthn(c.Request)
  338. if err != nil {
  339. common.ApiError(c, err)
  340. return
  341. }
  342. waUser := passkeysvc.NewWebAuthnUser(user, credential)
  343. assertion, sessionData, err := wa.BeginLogin(waUser)
  344. if err != nil {
  345. common.ApiError(c, err)
  346. return
  347. }
  348. if err := passkeysvc.SaveSessionData(c, passkeysvc.VerifySessionKey, sessionData); err != nil {
  349. common.ApiError(c, err)
  350. return
  351. }
  352. c.JSON(http.StatusOK, gin.H{
  353. "success": true,
  354. "message": "",
  355. "data": gin.H{
  356. "options": assertion,
  357. },
  358. })
  359. }
  360. func PasskeyVerifyFinish(c *gin.Context) {
  361. if !system_setting.GetPasskeySettings().Enabled {
  362. c.JSON(http.StatusOK, gin.H{
  363. "success": false,
  364. "message": "管理员未启用 Passkey 登录",
  365. })
  366. return
  367. }
  368. user, err := getSessionUser(c)
  369. if err != nil {
  370. c.JSON(http.StatusUnauthorized, gin.H{
  371. "success": false,
  372. "message": err.Error(),
  373. })
  374. return
  375. }
  376. wa, err := passkeysvc.BuildWebAuthn(c.Request)
  377. if err != nil {
  378. common.ApiError(c, err)
  379. return
  380. }
  381. credential, err := model.GetPasskeyByUserID(user.Id)
  382. if err != nil {
  383. c.JSON(http.StatusOK, gin.H{
  384. "success": false,
  385. "message": "该用户尚未绑定 Passkey",
  386. })
  387. return
  388. }
  389. sessionData, err := passkeysvc.PopSessionData(c, passkeysvc.VerifySessionKey)
  390. if err != nil {
  391. common.ApiError(c, err)
  392. return
  393. }
  394. waUser := passkeysvc.NewWebAuthnUser(user, credential)
  395. _, err = wa.FinishLogin(waUser, *sessionData, c.Request)
  396. if err != nil {
  397. common.ApiError(c, err)
  398. return
  399. }
  400. // 更新凭证的最后使用时间
  401. now := time.Now()
  402. credential.LastUsedAt = &now
  403. if err := model.UpsertPasskeyCredential(credential); err != nil {
  404. common.ApiError(c, err)
  405. return
  406. }
  407. c.JSON(http.StatusOK, gin.H{
  408. "success": true,
  409. "message": "Passkey 验证成功",
  410. })
  411. }
  412. func getSessionUser(c *gin.Context) (*model.User, error) {
  413. session := sessions.Default(c)
  414. idRaw := session.Get("id")
  415. if idRaw == nil {
  416. return nil, errors.New("未登录")
  417. }
  418. id, ok := idRaw.(int)
  419. if !ok {
  420. return nil, errors.New("无效的会话信息")
  421. }
  422. user := &model.User{Id: id}
  423. if err := user.FillUserById(); err != nil {
  424. return nil, err
  425. }
  426. if user.Status != common.UserStatusEnabled {
  427. return nil, errors.New("该用户已被禁用")
  428. }
  429. return user, nil
  430. }