puerta/internal/auth/webauthn.go

124 lines
3.2 KiB
Go
Raw Permalink Normal View History

2023-01-03 07:40:38 +00:00
// SPDX-License-Identifier: Apache-2.0
// Copyright © 2022 Roberto Hidalgo <nidito@un.rob.mx>
package auth
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"time"
2023-01-04 04:21:49 +00:00
"git.rob.mx/nidito/puerta/internal/errors"
"git.rob.mx/nidito/puerta/internal/user"
2023-01-03 07:40:38 +00:00
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/sirupsen/logrus"
"github.com/upper/db/v4"
)
const SessionNameWANAuth = "wan-auth"
const SessionNameWANRegister = "wan-register"
const HeaderNameWAN = "webauthn"
2023-01-04 04:21:49 +00:00
func webAuthnBeginRegistration(req *http.Request) error {
user := user.FromContext(req)
2023-01-03 07:40:38 +00:00
logrus.Infof("Starting webauthn registration for %s", user.Name)
2023-01-04 04:21:49 +00:00
options, sessionData, err := _wan.BeginRegistration(user)
2023-01-03 07:40:38 +00:00
if err != nil {
err = fmt.Errorf("error starting webauthn: %s", err)
logrus.Error(err)
return err
}
var b bytes.Buffer
if err := json.NewEncoder(&b).Encode(&sessionData); err != nil {
return err
}
2023-01-04 04:21:49 +00:00
_sess.Put(req.Context(), SessionNameWANRegister, b.Bytes())
return errors.WebAuthFlowChallenge{Flow: "register", Data: &options}
2023-01-03 07:40:38 +00:00
}
2023-01-04 04:21:49 +00:00
func webAuthnFinishRegistration(req *http.Request) error {
u := user.FromContext(req)
sd := _sess.PopBytes(req.Context(), SessionNameWANRegister)
2023-01-03 07:40:38 +00:00
if sd == nil {
return fmt.Errorf("error finishing webauthn registration: no session found for user")
}
var sessionData webauthn.SessionData
err := json.Unmarshal(sd, &sessionData)
if err != nil {
return err
}
2023-01-04 04:21:49 +00:00
cred, err := _wan.FinishRegistration(u, sessionData, req)
2023-01-03 07:40:38 +00:00
if err != nil {
return fmt.Errorf("error finishing webauthn registration: %s", err)
}
data, err := json.Marshal(cred)
if err != nil {
return fmt.Errorf("error encoding webauthn credential for storage: %s", err)
}
2023-01-04 04:21:49 +00:00
credential := &user.Credential{
UserID: u.ID,
2023-01-03 07:40:38 +00:00
Data: string(data),
}
2023-01-04 04:21:49 +00:00
_, err = _db.Collection("credential").Insert(credential)
2023-01-03 07:40:38 +00:00
return err
}
2023-01-04 04:21:49 +00:00
func webAuthnLogin(req *http.Request) error {
user := user.FromContext(req)
sd := _sess.PopBytes(req.Context(), SessionNameWANAuth)
2023-01-03 07:40:38 +00:00
if sd == nil {
logrus.Infof("Starting webauthn login flow for %s", user.Name)
2023-01-04 04:21:49 +00:00
options, sessionData, err := _wan.BeginLogin(user)
2023-01-03 07:40:38 +00:00
if err != nil {
return fmt.Errorf("error starting webauthn login: %s", err)
}
var b bytes.Buffer
if err := json.NewEncoder(&b).Encode(&sessionData); err != nil {
return fmt.Errorf("could not encode json: %s", err)
}
2023-01-04 04:21:49 +00:00
_sess.Put(req.Context(), SessionNameWANAuth, b.Bytes())
2023-01-03 07:40:38 +00:00
2023-01-04 04:21:49 +00:00
return errors.WebAuthFlowChallenge{Flow: "login", Data: &options}
2023-01-03 07:40:38 +00:00
}
var sessionData webauthn.SessionData
err := json.Unmarshal(sd, &sessionData)
if err != nil {
return err
}
challengeResponse := req.Header.Get(HeaderNameWAN)
if challengeResponse == "" {
return fmt.Errorf("missing webauthn header")
}
challengeBytes, err := base64.StdEncoding.DecodeString(challengeResponse)
if err != nil {
return fmt.Errorf("unparseable webauthn header value")
}
response, err := protocol.ParseCredentialRequestResponseBody(bytes.NewBuffer(challengeBytes))
if err != nil {
return fmt.Errorf("could not parse webauthn request into protocol: %w", err)
}
2023-01-04 04:21:49 +00:00
_, err = _wan.ValidateLogin(user, sessionData, response)
2023-01-03 07:40:38 +00:00
return err
}
2023-01-04 04:21:49 +00:00
func Cleanup() error {
return _db.Collection("session").Find(db.Cond{"Expires": db.Before(time.Now())}).Delete()
2023-01-03 07:40:38 +00:00
}