Files

209 lines
5.4 KiB
Go

package webauthn
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-webauthn/webauthn/protocol"
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
"github.com/pocket-id/pocket-id/backend/internal/apperror"
"github.com/pocket-id/pocket-id/backend/internal/dto"
"github.com/pocket-id/pocket-id/backend/internal/httpserver"
"github.com/pocket-id/pocket-id/backend/internal/utils/cookie"
)
type handler struct {
service *Service
appConfig appconfig.AppConfigResolver
}
func newHandler(service *Service, appConfig appconfig.AppConfigResolver) *handler {
return &handler{
service: service,
appConfig: appConfig,
}
}
func (h *handler) beginRegistration(c *gin.Context) error {
dbConfig, err := h.appConfig.GetConfig(c.Request.Context())
if err != nil {
return fmt.Errorf("error loading app configuration: %w", err)
}
userID := c.GetString("userID")
options, err := h.service.BeginRegistration(c.Request.Context(), dbConfig, userID)
if err != nil {
return err
}
cookie.AddSessionIdCookie(c, int(options.Timeout.Seconds()), options.SessionID)
c.JSON(http.StatusOK, options.Response)
return nil
}
func (h *handler) verifyRegistration(c *gin.Context) error {
dbConfig, err := h.appConfig.GetConfig(c.Request.Context())
if err != nil {
return fmt.Errorf("error loading app configuration: %w", err)
}
sessionID, err := c.Cookie(cookie.SessionIdCookieName)
if err != nil {
return apperror.MissingSessionID()
}
userID := c.GetString("userID")
credential, err := h.service.VerifyRegistration(c.Request.Context(), dbConfig, sessionID, userID, c.Request, c.ClientIP())
if err != nil {
return err
}
var credentialDto dto.WebauthnCredentialDto
if err := dto.MapStruct(credential, &credentialDto); err != nil {
return err
}
c.JSON(http.StatusOK, credentialDto)
return nil
}
func (h *handler) beginLogin(c *gin.Context) error {
dbConfig, err := h.appConfig.GetConfig(c.Request.Context())
if err != nil {
return fmt.Errorf("error loading app configuration: %w", err)
}
options, err := h.service.BeginLogin(c.Request.Context(), dbConfig)
if err != nil {
return err
}
cookie.AddSessionIdCookie(c, int(options.Timeout.Seconds()), options.SessionID)
c.JSON(http.StatusOK, options.Response)
return nil
}
func (h *handler) verifyLogin(c *gin.Context) error {
dbConfig, err := h.appConfig.GetConfig(c.Request.Context())
if err != nil {
return fmt.Errorf("error loading app configuration: %w", err)
}
sessionID, err := c.Cookie(cookie.SessionIdCookieName)
if err != nil {
return apperror.MissingSessionID()
}
credentialAssertionData, err := protocol.ParseCredentialRequestResponseBody(c.Request.Body)
if err != nil {
return apperror.InvalidWebAuthnResponse(err)
}
user, token, err := h.service.VerifyLogin(c.Request.Context(), dbConfig, sessionID, credentialAssertionData, c.ClientIP(), c.Request.UserAgent())
if err != nil {
return err
}
var userDto dto.UserDto
if err := dto.MapStruct(user, &userDto); err != nil {
return err
}
maxAge := int(dbConfig.SessionDuration.AsDurationMinutes().Seconds())
cookie.AddAccessTokenCookie(c, maxAge, token)
c.JSON(http.StatusOK, userDto)
return nil
}
func (h *handler) listCredentials(c *gin.Context) error {
userID := c.GetString("userID")
credentials, err := h.service.ListCredentials(c.Request.Context(), userID)
if err != nil {
return err
}
var credentialDtos []dto.WebauthnCredentialDto
if err := dto.MapStructList(credentials, &credentialDtos); err != nil {
return err
}
c.JSON(http.StatusOK, credentialDtos)
return nil
}
func (h *handler) deleteCredential(c *gin.Context) error {
userID := c.GetString("userID")
credentialID := c.Param("id")
clientIP := c.ClientIP()
userAgent := c.Request.UserAgent()
err := h.service.DeleteCredential(c.Request.Context(), userID, credentialID, clientIP, userAgent, userID)
if err != nil {
return err
}
c.Status(http.StatusNoContent)
return nil
}
func (h *handler) updateCredential(c *gin.Context) error {
userID := c.GetString("userID")
credentialID := c.Param("id")
var input dto.WebauthnCredentialUpdateDto
if err := httpserver.BindJSON(c, &input); err != nil {
return err
}
credential, err := h.service.UpdateCredential(c.Request.Context(), userID, credentialID, input.Name)
if err != nil {
return err
}
var credentialDto dto.WebauthnCredentialDto
if err := dto.MapStruct(credential, &credentialDto); err != nil {
return err
}
c.JSON(http.StatusOK, credentialDto)
return nil
}
func (h *handler) logout(c *gin.Context) error {
cookie.AddAccessTokenCookie(c, 0, "")
c.Status(http.StatusNoContent)
return nil
}
func (h *handler) reauthenticate(c *gin.Context) error {
sessionID, err := c.Cookie(cookie.SessionIdCookieName)
if err != nil {
return apperror.MissingSessionID()
}
var token string
// Try to create a reauthentication token with WebAuthn
credentialAssertionData, err := protocol.ParseCredentialRequestResponseBody(c.Request.Body)
if err == nil {
token, err = h.service.CreateReauthenticationTokenWithWebauthn(c.Request.Context(), sessionID, credentialAssertionData)
if err != nil {
return err
}
} else {
// If WebAuthn fails, try to create a reauthentication token with the access token
accessToken, _ := c.Cookie(cookie.AccessTokenCookieName)
token, err = h.service.CreateReauthenticationTokenWithAccessToken(c.Request.Context(), accessToken)
if err != nil {
return err
}
}
cookie.AddReauthenticationTokenCookie(c, token)
c.Status(http.StatusNoContent)
return nil
}