| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- package controller
- import (
- "errors"
- "fmt"
- "net/http"
- "strconv"
- "time"
- "one-api/common"
- "one-api/model"
- passkeysvc "one-api/service/passkey"
- "one-api/setting/system_setting"
- "github.com/gin-contrib/sessions"
- "github.com/gin-gonic/gin"
- "github.com/go-webauthn/webauthn/protocol"
- webauthnlib "github.com/go-webauthn/webauthn/webauthn"
- )
- func PasskeyRegisterBegin(c *gin.Context) {
- if !system_setting.GetPasskeySettings().Enabled {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "管理员未启用 Passkey 登录",
- })
- return
- }
- user, err := getSessionUser(c)
- if err != nil {
- c.JSON(http.StatusUnauthorized, gin.H{
- "success": false,
- "message": err.Error(),
- })
- return
- }
- credential, err := model.GetPasskeyByUserID(user.Id)
- if err != nil && !errors.Is(err, model.ErrPasskeyNotFound) {
- common.ApiError(c, err)
- return
- }
- if errors.Is(err, model.ErrPasskeyNotFound) {
- credential = nil
- }
- wa, err := passkeysvc.BuildWebAuthn(c.Request)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- waUser := passkeysvc.NewWebAuthnUser(user, credential)
- var options []webauthnlib.RegistrationOption
- if credential != nil {
- descriptor := credential.ToWebAuthnCredential().Descriptor()
- options = append(options, webauthnlib.WithExclusions([]protocol.CredentialDescriptor{descriptor}))
- }
- creation, sessionData, err := wa.BeginRegistration(waUser, options...)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- if err := passkeysvc.SaveSessionData(c, passkeysvc.RegistrationSessionKey, sessionData); err != nil {
- common.ApiError(c, err)
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "",
- "data": gin.H{
- "options": creation,
- },
- })
- }
- func PasskeyRegisterFinish(c *gin.Context) {
- if !system_setting.GetPasskeySettings().Enabled {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "管理员未启用 Passkey 登录",
- })
- return
- }
- user, err := getSessionUser(c)
- if err != nil {
- c.JSON(http.StatusUnauthorized, gin.H{
- "success": false,
- "message": err.Error(),
- })
- return
- }
- wa, err := passkeysvc.BuildWebAuthn(c.Request)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- credentialRecord, err := model.GetPasskeyByUserID(user.Id)
- if err != nil && !errors.Is(err, model.ErrPasskeyNotFound) {
- common.ApiError(c, err)
- return
- }
- if errors.Is(err, model.ErrPasskeyNotFound) {
- credentialRecord = nil
- }
- sessionData, err := passkeysvc.PopSessionData(c, passkeysvc.RegistrationSessionKey)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- waUser := passkeysvc.NewWebAuthnUser(user, credentialRecord)
- credential, err := wa.FinishRegistration(waUser, *sessionData, c.Request)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- passkeyCredential := model.NewPasskeyCredentialFromWebAuthn(user.Id, credential)
- if passkeyCredential == nil {
- common.ApiErrorMsg(c, "无法创建 Passkey 凭证")
- return
- }
- if err := model.UpsertPasskeyCredential(passkeyCredential); err != nil {
- common.ApiError(c, err)
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "Passkey 注册成功",
- })
- }
- func PasskeyDelete(c *gin.Context) {
- user, err := getSessionUser(c)
- if err != nil {
- c.JSON(http.StatusUnauthorized, gin.H{
- "success": false,
- "message": err.Error(),
- })
- return
- }
- if err := model.DeletePasskeyByUserID(user.Id); err != nil {
- common.ApiError(c, err)
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "Passkey 已解绑",
- })
- }
- func PasskeyStatus(c *gin.Context) {
- user, err := getSessionUser(c)
- if err != nil {
- c.JSON(http.StatusUnauthorized, gin.H{
- "success": false,
- "message": err.Error(),
- })
- return
- }
- credential, err := model.GetPasskeyByUserID(user.Id)
- if errors.Is(err, model.ErrPasskeyNotFound) {
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "",
- "data": gin.H{
- "enabled": false,
- },
- })
- return
- }
- if err != nil {
- common.ApiError(c, err)
- return
- }
- data := gin.H{
- "enabled": true,
- "last_used_at": credential.LastUsedAt,
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "",
- "data": data,
- })
- }
- func PasskeyLoginBegin(c *gin.Context) {
- if !system_setting.GetPasskeySettings().Enabled {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "管理员未启用 Passkey 登录",
- })
- return
- }
- wa, err := passkeysvc.BuildWebAuthn(c.Request)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- assertion, sessionData, err := wa.BeginDiscoverableLogin()
- if err != nil {
- common.ApiError(c, err)
- return
- }
- if err := passkeysvc.SaveSessionData(c, passkeysvc.LoginSessionKey, sessionData); err != nil {
- common.ApiError(c, err)
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "",
- "data": gin.H{
- "options": assertion,
- },
- })
- }
- func PasskeyLoginFinish(c *gin.Context) {
- if !system_setting.GetPasskeySettings().Enabled {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "管理员未启用 Passkey 登录",
- })
- return
- }
- wa, err := passkeysvc.BuildWebAuthn(c.Request)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- sessionData, err := passkeysvc.PopSessionData(c, passkeysvc.LoginSessionKey)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- handler := func(rawID, userHandle []byte) (webauthnlib.User, error) {
- // 首先通过凭证ID查找用户
- credential, err := model.GetPasskeyByCredentialID(rawID)
- if err != nil {
- return nil, fmt.Errorf("未找到 Passkey 凭证: %w", err)
- }
- // 通过凭证获取用户
- user := &model.User{Id: credential.UserID}
- if err := user.FillUserById(); err != nil {
- return nil, fmt.Errorf("用户信息获取失败: %w", err)
- }
- if user.Status != common.UserStatusEnabled {
- return nil, errors.New("该用户已被禁用")
- }
- if len(userHandle) > 0 {
- userID, parseErr := strconv.Atoi(string(userHandle))
- if parseErr != nil {
- // 记录异常但继续验证,因为某些客户端可能使用非数字格式
- common.SysLog(fmt.Sprintf("PasskeyLogin: userHandle parse error for credential, length: %d", len(userHandle)))
- } else if userID != user.Id {
- return nil, errors.New("用户句柄与凭证不匹配")
- }
- }
- return passkeysvc.NewWebAuthnUser(user, credential), nil
- }
- waUser, credential, err := wa.FinishPasskeyLogin(handler, *sessionData, c.Request)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- userWrapper, ok := waUser.(*passkeysvc.WebAuthnUser)
- if !ok {
- common.ApiErrorMsg(c, "Passkey 登录状态异常")
- return
- }
- modelUser := userWrapper.ModelUser()
- if modelUser == nil {
- common.ApiErrorMsg(c, "Passkey 登录状态异常")
- return
- }
- if modelUser.Status != common.UserStatusEnabled {
- common.ApiErrorMsg(c, "该用户已被禁用")
- return
- }
- // 更新凭证信息
- updatedCredential := model.NewPasskeyCredentialFromWebAuthn(modelUser.Id, credential)
- if updatedCredential == nil {
- common.ApiErrorMsg(c, "Passkey 凭证更新失败")
- return
- }
- now := time.Now()
- updatedCredential.LastUsedAt = &now
- if err := model.UpsertPasskeyCredential(updatedCredential); err != nil {
- common.ApiError(c, err)
- return
- }
- setupLogin(modelUser, c)
- return
- }
- func AdminResetPasskey(c *gin.Context) {
- id, err := strconv.Atoi(c.Param("id"))
- if err != nil {
- common.ApiErrorMsg(c, "无效的用户 ID")
- return
- }
- user := &model.User{Id: id}
- if err := user.FillUserById(); err != nil {
- common.ApiError(c, err)
- return
- }
- if _, err := model.GetPasskeyByUserID(user.Id); err != nil {
- if errors.Is(err, model.ErrPasskeyNotFound) {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "该用户尚未绑定 Passkey",
- })
- return
- }
- common.ApiError(c, err)
- return
- }
- if err := model.DeletePasskeyByUserID(user.Id); err != nil {
- common.ApiError(c, err)
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "Passkey 已重置",
- })
- }
- func PasskeyVerifyBegin(c *gin.Context) {
- if !system_setting.GetPasskeySettings().Enabled {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "管理员未启用 Passkey 登录",
- })
- return
- }
- user, err := getSessionUser(c)
- if err != nil {
- c.JSON(http.StatusUnauthorized, gin.H{
- "success": false,
- "message": err.Error(),
- })
- return
- }
- credential, err := model.GetPasskeyByUserID(user.Id)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "该用户尚未绑定 Passkey",
- })
- return
- }
- wa, err := passkeysvc.BuildWebAuthn(c.Request)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- waUser := passkeysvc.NewWebAuthnUser(user, credential)
- assertion, sessionData, err := wa.BeginLogin(waUser)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- if err := passkeysvc.SaveSessionData(c, passkeysvc.VerifySessionKey, sessionData); err != nil {
- common.ApiError(c, err)
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "",
- "data": gin.H{
- "options": assertion,
- },
- })
- }
- func PasskeyVerifyFinish(c *gin.Context) {
- if !system_setting.GetPasskeySettings().Enabled {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "管理员未启用 Passkey 登录",
- })
- return
- }
- user, err := getSessionUser(c)
- if err != nil {
- c.JSON(http.StatusUnauthorized, gin.H{
- "success": false,
- "message": err.Error(),
- })
- return
- }
- wa, err := passkeysvc.BuildWebAuthn(c.Request)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- credential, err := model.GetPasskeyByUserID(user.Id)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "该用户尚未绑定 Passkey",
- })
- return
- }
- sessionData, err := passkeysvc.PopSessionData(c, passkeysvc.VerifySessionKey)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- waUser := passkeysvc.NewWebAuthnUser(user, credential)
- _, err = wa.FinishLogin(waUser, *sessionData, c.Request)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- // 更新凭证的最后使用时间
- now := time.Now()
- credential.LastUsedAt = &now
- if err := model.UpsertPasskeyCredential(credential); err != nil {
- common.ApiError(c, err)
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "Passkey 验证成功",
- })
- }
- func getSessionUser(c *gin.Context) (*model.User, error) {
- session := sessions.Default(c)
- idRaw := session.Get("id")
- if idRaw == nil {
- return nil, errors.New("未登录")
- }
- id, ok := idRaw.(int)
- if !ok {
- return nil, errors.New("无效的会话信息")
- }
- user := &model.User{Id: id}
- if err := user.FillUserById(); err != nil {
- return nil, err
- }
- if user.Status != common.UserStatusEnabled {
- return nil, errors.New("该用户已被禁用")
- }
- return user, nil
- }
|