mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-19 16:01:16 +00:00
87f9bf58b0
Add passkey enrollment and login wiring, including a Safari-safe explicit login path that prefetches the WebAuthn challenge before the click and calls the credential ceremony immediately from the user gesture.
64 lines
1.7 KiB
Go
64 lines
1.7 KiB
Go
package passkey
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"github.com/getsentry/sentry-go"
|
|
"github.com/go-webauthn/webauthn/webauthn"
|
|
"github.com/google/uuid"
|
|
"github.com/redis/go-redis/v9"
|
|
"github.com/warmbly/warmbly/internal/errx"
|
|
)
|
|
|
|
// Challenge/session data lives in Redis between a Begin and Finish call,
|
|
// keyed by the user (registration — already authenticated) or by an opaque
|
|
// session id (discoverable login — no user yet). It is single-use and
|
|
// short-lived; the challenge itself is the unguessable secret.
|
|
|
|
func registrationKey(userID uuid.UUID) string {
|
|
return "webauthn_reg:" + userID.String()
|
|
}
|
|
|
|
func loginKey(sessionID uuid.UUID) string {
|
|
return "webauthn_login:" + sessionID.String()
|
|
}
|
|
|
|
func (s *service) saveSession(ctx context.Context, key string, data *webauthn.SessionData) *errx.Error {
|
|
raw, err := json.Marshal(data)
|
|
if err != nil {
|
|
sentry.CaptureException(err)
|
|
return errx.InternalError()
|
|
}
|
|
|
|
if err := s.cache.Set(ctx, key, raw, CeremonyTTL).Err(); err != nil {
|
|
sentry.CaptureException(err)
|
|
return errx.InternalError()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// takeSession reads and atomically deletes the stored SessionData, enforcing
|
|
// single-use of the challenge (replay protection). A missing key means the
|
|
// ceremony expired or was already consumed.
|
|
func (s *service) takeSession(ctx context.Context, key string) (*webauthn.SessionData, *errx.Error) {
|
|
raw, err := s.cache.GetDel(ctx, key).Bytes()
|
|
if err != nil {
|
|
if errors.Is(err, redis.Nil) {
|
|
return nil, errx.ErrPasskeySession
|
|
}
|
|
sentry.CaptureException(err)
|
|
return nil, errx.InternalError()
|
|
}
|
|
|
|
var data webauthn.SessionData
|
|
if err := json.Unmarshal(raw, &data); err != nil {
|
|
sentry.CaptureException(err)
|
|
return nil, errx.InternalError()
|
|
}
|
|
|
|
return &data, nil
|
|
}
|