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
|
|
|
}
|