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  }