passkey.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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. "backup_eligible": credential.BackupEligible,
  169. "backup_state": credential.BackupState,
  170. }
  171. if credential != nil {
  172. data["credential_aaguid"] = fmt.Sprintf("%x", credential.AAGUID)
  173. }
  174. c.JSON(http.StatusOK, gin.H{
  175. "success": true,
  176. "message": "",
  177. "data": data,
  178. })
  179. }
  180. func PasskeyLoginBegin(c *gin.Context) {
  181. if !system_setting.GetPasskeySettings().Enabled {
  182. c.JSON(http.StatusOK, gin.H{
  183. "success": false,
  184. "message": "管理员未启用 Passkey 登录",
  185. })
  186. return
  187. }
  188. wa, err := passkeysvc.BuildWebAuthn(c.Request)
  189. if err != nil {
  190. common.ApiError(c, err)
  191. return
  192. }
  193. assertion, sessionData, err := wa.BeginDiscoverableLogin()
  194. if err != nil {
  195. common.ApiError(c, err)
  196. return
  197. }
  198. if err := passkeysvc.SaveSessionData(c, passkeysvc.LoginSessionKey, sessionData); err != nil {
  199. common.ApiError(c, err)
  200. return
  201. }
  202. c.JSON(http.StatusOK, gin.H{
  203. "success": true,
  204. "message": "",
  205. "data": gin.H{
  206. "options": assertion,
  207. },
  208. })
  209. }
  210. func PasskeyLoginFinish(c *gin.Context) {
  211. if !system_setting.GetPasskeySettings().Enabled {
  212. c.JSON(http.StatusOK, gin.H{
  213. "success": false,
  214. "message": "管理员未启用 Passkey 登录",
  215. })
  216. return
  217. }
  218. wa, err := passkeysvc.BuildWebAuthn(c.Request)
  219. if err != nil {
  220. common.ApiError(c, err)
  221. return
  222. }
  223. sessionData, err := passkeysvc.PopSessionData(c, passkeysvc.LoginSessionKey)
  224. if err != nil {
  225. common.ApiError(c, err)
  226. return
  227. }
  228. handler := func(rawID, userHandle []byte) (webauthnlib.User, error) {
  229. // 首先通过凭证ID查找用户
  230. credential, err := model.GetPasskeyByCredentialID(rawID)
  231. if err != nil {
  232. return nil, fmt.Errorf("未找到 Passkey 凭证: %w", err)
  233. }
  234. // 通过凭证获取用户
  235. user := &model.User{Id: credential.UserID}
  236. if err := user.FillUserById(); err != nil {
  237. return nil, fmt.Errorf("用户信息获取失败: %w", err)
  238. }
  239. if user.Status != common.UserStatusEnabled {
  240. return nil, errors.New("该用户已被禁用")
  241. }
  242. // 验证用户句柄(如果提供的话)
  243. if len(userHandle) > 0 {
  244. if userID, parseErr := strconv.Atoi(string(userHandle)); parseErr == nil {
  245. if userID != user.Id {
  246. return nil, errors.New("用户句柄与凭证不匹配")
  247. }
  248. }
  249. // 如果解析失败,不做严格验证,因为某些情况下userHandle可能为空或格式不同
  250. }
  251. return passkeysvc.NewWebAuthnUser(user, credential), nil
  252. }
  253. waUser, credential, err := wa.FinishPasskeyLogin(handler, *sessionData, c.Request)
  254. if err != nil {
  255. common.ApiError(c, err)
  256. return
  257. }
  258. userWrapper, ok := waUser.(*passkeysvc.WebAuthnUser)
  259. if !ok {
  260. common.ApiErrorMsg(c, "Passkey 登录状态异常")
  261. return
  262. }
  263. modelUser := userWrapper.ModelUser()
  264. if modelUser == nil {
  265. common.ApiErrorMsg(c, "Passkey 登录状态异常")
  266. return
  267. }
  268. if modelUser.Status != common.UserStatusEnabled {
  269. common.ApiErrorMsg(c, "该用户已被禁用")
  270. return
  271. }
  272. // 更新凭证信息
  273. updatedCredential := model.NewPasskeyCredentialFromWebAuthn(modelUser.Id, credential)
  274. if updatedCredential == nil {
  275. common.ApiErrorMsg(c, "Passkey 凭证更新失败")
  276. return
  277. }
  278. now := time.Now()
  279. updatedCredential.LastUsedAt = &now
  280. if err := model.UpsertPasskeyCredential(updatedCredential); err != nil {
  281. common.ApiError(c, err)
  282. return
  283. }
  284. setupLogin(modelUser, c)
  285. return
  286. }
  287. func AdminResetPasskey(c *gin.Context) {
  288. id, err := strconv.Atoi(c.Param("id"))
  289. if err != nil {
  290. common.ApiErrorMsg(c, "无效的用户 ID")
  291. return
  292. }
  293. user := &model.User{Id: id}
  294. if err := user.FillUserById(); err != nil {
  295. common.ApiError(c, err)
  296. return
  297. }
  298. if _, err := model.GetPasskeyByUserID(user.Id); err != nil {
  299. if errors.Is(err, model.ErrPasskeyNotFound) {
  300. c.JSON(http.StatusOK, gin.H{
  301. "success": false,
  302. "message": "该用户尚未绑定 Passkey",
  303. })
  304. return
  305. }
  306. common.ApiError(c, err)
  307. return
  308. }
  309. if err := model.DeletePasskeyByUserID(user.Id); err != nil {
  310. common.ApiError(c, err)
  311. return
  312. }
  313. c.JSON(http.StatusOK, gin.H{
  314. "success": true,
  315. "message": "Passkey 已重置",
  316. })
  317. }
  318. func PasskeyVerifyBegin(c *gin.Context) {
  319. if !system_setting.GetPasskeySettings().Enabled {
  320. c.JSON(http.StatusOK, gin.H{
  321. "success": false,
  322. "message": "管理员未启用 Passkey 登录",
  323. })
  324. return
  325. }
  326. user, err := getSessionUser(c)
  327. if err != nil {
  328. c.JSON(http.StatusUnauthorized, gin.H{
  329. "success": false,
  330. "message": err.Error(),
  331. })
  332. return
  333. }
  334. credential, err := model.GetPasskeyByUserID(user.Id)
  335. if err != nil {
  336. c.JSON(http.StatusOK, gin.H{
  337. "success": false,
  338. "message": "该用户尚未绑定 Passkey",
  339. })
  340. return
  341. }
  342. wa, err := passkeysvc.BuildWebAuthn(c.Request)
  343. if err != nil {
  344. common.ApiError(c, err)
  345. return
  346. }
  347. waUser := passkeysvc.NewWebAuthnUser(user, credential)
  348. assertion, sessionData, err := wa.BeginLogin(waUser)
  349. if err != nil {
  350. common.ApiError(c, err)
  351. return
  352. }
  353. if err := passkeysvc.SaveSessionData(c, passkeysvc.VerifySessionKey, sessionData); err != nil {
  354. common.ApiError(c, err)
  355. return
  356. }
  357. c.JSON(http.StatusOK, gin.H{
  358. "success": true,
  359. "message": "",
  360. "data": gin.H{
  361. "options": assertion,
  362. },
  363. })
  364. }
  365. func PasskeyVerifyFinish(c *gin.Context) {
  366. if !system_setting.GetPasskeySettings().Enabled {
  367. c.JSON(http.StatusOK, gin.H{
  368. "success": false,
  369. "message": "管理员未启用 Passkey 登录",
  370. })
  371. return
  372. }
  373. user, err := getSessionUser(c)
  374. if err != nil {
  375. c.JSON(http.StatusUnauthorized, gin.H{
  376. "success": false,
  377. "message": err.Error(),
  378. })
  379. return
  380. }
  381. wa, err := passkeysvc.BuildWebAuthn(c.Request)
  382. if err != nil {
  383. common.ApiError(c, err)
  384. return
  385. }
  386. credential, err := model.GetPasskeyByUserID(user.Id)
  387. if err != nil {
  388. c.JSON(http.StatusOK, gin.H{
  389. "success": false,
  390. "message": "该用户尚未绑定 Passkey",
  391. })
  392. return
  393. }
  394. sessionData, err := passkeysvc.PopSessionData(c, passkeysvc.VerifySessionKey)
  395. if err != nil {
  396. common.ApiError(c, err)
  397. return
  398. }
  399. waUser := passkeysvc.NewWebAuthnUser(user, credential)
  400. _, err = wa.FinishLogin(waUser, *sessionData, c.Request)
  401. if err != nil {
  402. common.ApiError(c, err)
  403. return
  404. }
  405. // 更新凭证的最后使用时间
  406. now := time.Now()
  407. credential.LastUsedAt = &now
  408. if err := model.UpsertPasskeyCredential(credential); err != nil {
  409. common.ApiError(c, err)
  410. return
  411. }
  412. c.JSON(http.StatusOK, gin.H{
  413. "success": true,
  414. "message": "Passkey 验证成功",
  415. })
  416. }
  417. func getSessionUser(c *gin.Context) (*model.User, error) {
  418. session := sessions.Default(c)
  419. idRaw := session.Get("id")
  420. if idRaw == nil {
  421. return nil, errors.New("未登录")
  422. }
  423. id, ok := idRaw.(int)
  424. if !ok {
  425. return nil, errors.New("无效的会话信息")
  426. }
  427. user := &model.User{Id: id}
  428. if err := user.FillUserById(); err != nil {
  429. return nil, err
  430. }
  431. if user.Status != common.UserStatusEnabled {
  432. return nil, errors.New("该用户已被禁用")
  433. }
  434. return user, nil
  435. }