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 }