code.gitea.io/gitea@v1.21.7/routers/web/user/setting/security/webauthn.go (about) 1 // Copyright 2018 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package security 5 6 import ( 7 "errors" 8 "net/http" 9 "strconv" 10 "time" 11 12 "code.gitea.io/gitea/models/auth" 13 wa "code.gitea.io/gitea/modules/auth/webauthn" 14 "code.gitea.io/gitea/modules/context" 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/setting" 17 "code.gitea.io/gitea/modules/web" 18 "code.gitea.io/gitea/services/forms" 19 20 "github.com/go-webauthn/webauthn/protocol" 21 "github.com/go-webauthn/webauthn/webauthn" 22 ) 23 24 // WebAuthnRegister initializes the webauthn registration procedure 25 func WebAuthnRegister(ctx *context.Context) { 26 form := web.GetForm(ctx).(*forms.WebauthnRegistrationForm) 27 if form.Name == "" { 28 // Set name to the hexadecimal of the current time 29 form.Name = strconv.FormatInt(time.Now().UnixNano(), 16) 30 } 31 32 cred, err := auth.GetWebAuthnCredentialByName(ctx, ctx.Doer.ID, form.Name) 33 if err != nil && !auth.IsErrWebAuthnCredentialNotExist(err) { 34 ctx.ServerError("GetWebAuthnCredentialsByUID", err) 35 return 36 } 37 if cred != nil { 38 ctx.Error(http.StatusConflict, "Name already taken") 39 return 40 } 41 42 _ = ctx.Session.Delete("webauthnRegistration") 43 if err := ctx.Session.Set("webauthnName", form.Name); err != nil { 44 ctx.ServerError("Unable to set session key for webauthnName", err) 45 return 46 } 47 48 credentialOptions, sessionData, err := wa.WebAuthn.BeginRegistration((*wa.User)(ctx.Doer)) 49 if err != nil { 50 ctx.ServerError("Unable to BeginRegistration", err) 51 return 52 } 53 54 // Save the session data as marshaled JSON 55 if err = ctx.Session.Set("webauthnRegistration", sessionData); err != nil { 56 ctx.ServerError("Unable to set session", err) 57 return 58 } 59 60 ctx.JSON(http.StatusOK, credentialOptions) 61 } 62 63 // WebauthnRegisterPost receives the response of the security key 64 func WebauthnRegisterPost(ctx *context.Context) { 65 name, ok := ctx.Session.Get("webauthnName").(string) 66 if !ok || name == "" { 67 ctx.ServerError("Get webauthnName", errors.New("no webauthnName")) 68 return 69 } 70 71 // Load the session data 72 sessionData, ok := ctx.Session.Get("webauthnRegistration").(*webauthn.SessionData) 73 if !ok || sessionData == nil { 74 ctx.ServerError("Get registration", errors.New("no registration")) 75 return 76 } 77 defer func() { 78 _ = ctx.Session.Delete("webauthnRegistration") 79 }() 80 81 // Verify that the challenge succeeded 82 cred, err := wa.WebAuthn.FinishRegistration((*wa.User)(ctx.Doer), *sessionData, ctx.Req) 83 if err != nil { 84 if pErr, ok := err.(*protocol.Error); ok { 85 log.Error("Unable to finish registration due to error: %v\nDevInfo: %s", pErr, pErr.DevInfo) 86 } 87 ctx.ServerError("CreateCredential", err) 88 return 89 } 90 91 dbCred, err := auth.GetWebAuthnCredentialByName(ctx, ctx.Doer.ID, name) 92 if err != nil && !auth.IsErrWebAuthnCredentialNotExist(err) { 93 ctx.ServerError("GetWebAuthnCredentialsByUID", err) 94 return 95 } 96 if dbCred != nil { 97 ctx.Error(http.StatusConflict, "Name already taken") 98 return 99 } 100 101 // Create the credential 102 _, err = auth.CreateCredential(ctx, ctx.Doer.ID, name, cred) 103 if err != nil { 104 ctx.ServerError("CreateCredential", err) 105 return 106 } 107 _ = ctx.Session.Delete("webauthnName") 108 109 ctx.JSON(http.StatusCreated, cred) 110 } 111 112 // WebauthnDelete deletes an security key by id 113 func WebauthnDelete(ctx *context.Context) { 114 form := web.GetForm(ctx).(*forms.WebauthnDeleteForm) 115 if _, err := auth.DeleteCredential(ctx, form.ID, ctx.Doer.ID); err != nil { 116 ctx.ServerError("GetWebAuthnCredentialByID", err) 117 return 118 } 119 ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security") 120 }