Files
pocket-id/backend/internal/webauthn/handler_test.go
2026-07-13 11:25:01 -05:00

110 lines
3.7 KiB
Go

package webauthn
import (
"bytes"
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/danielgtaylor/huma/v2"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
"github.com/pocket-id/pocket-id/backend/internal/model"
"github.com/pocket-id/pocket-id/backend/internal/utils/cookie"
httpapi "github.com/pocket-id/pocket-id/backend/internal/utils/huma"
testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing"
)
func TestRequestWithBodyReconstructsUnderlyingRequest(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
api := httpapi.New(router, router.Group("/"))
type input struct {
Body map[string]string
}
type output struct {
Body map[string]string
}
httpapi.Register(api, huma.Operation{
OperationID: "reconstruct-request",
Method: http.MethodPost,
Path: "/api/reconstruct",
}, func(ctx context.Context, _ *input) (*output, error) {
request := requestWithBody(ctx, []byte(`{"credential":"value"}`))
body, err := io.ReadAll(request.Body)
require.NoError(t, err)
require.True(t, bytes.Equal([]byte(`{"credential":"value"}`), body))
require.Equal(t, int64(len(body)), request.ContentLength)
return &output{Body: map[string]string{"status": "ok"}}, nil
})
request := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "/api/reconstruct", http.NoBody)
request.Header.Set("Content-Type", "application/json")
response := httptest.NewRecorder()
router.ServeHTTP(response, request)
require.Equal(t, http.StatusBadRequest, response.Code)
request = httptest.NewRequestWithContext(t.Context(), http.MethodPost, "/api/reconstruct", strings.NewReader(`{"input":"present"}`))
request.Header.Set("Content-Type", "application/json")
response = httptest.NewRecorder()
router.ServeHTTP(response, request)
require.Equal(t, http.StatusOK, response.Code)
}
func TestLogoutClearsAccessTokenCookie(t *testing.T) {
output, err := (&handler{}).logout(t.Context(), &httpapi.EmptyInput{})
require.NoError(t, err)
require.Len(t, output.SetCookie, 1)
require.Equal(t, -1, output.SetCookie[0].MaxAge)
require.Contains(t, output.SetCookie[0].String(), "Max-Age=0")
}
func TestReauthenticateFallsBackWithoutSessionCookie(t *testing.T) {
db := testutils.NewDatabaseForTest(t)
user := model.User{Base: model.Base{ID: "handler-reauth-user"}, Username: "handler-reauth-user"}
require.NoError(t, db.Create(&user).Error)
signer := newFakeSigner()
accessToken, err := signer.GenerateAccessToken(user, authenticationMethodPhishingResistant)
require.NoError(t, err)
gin.SetMode(gin.TestMode)
router := gin.New()
api := httpapi.New(router, router.Group("/"))
h := &handler{service: &Service{db: db, signer: signer}}
httpapi.Register(api, huma.Operation{
OperationID: "test-reauthenticate-fallback",
Method: http.MethodPost,
Path: "/api/test-reauthenticate-fallback",
DefaultStatus: http.StatusNoContent,
}, h.reauthenticate)
testCases := []struct {
name string
body io.Reader
}{
{name: "empty body"},
{name: "invalid assertion", body: strings.NewReader(`{"invalid":true}`)},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
request := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "/api/test-reauthenticate-fallback", testCase.body)
if testCase.body != nil {
request.Header.Set("Content-Type", "application/json")
}
request.AddCookie(cookie.NewAccessTokenCookie(60, accessToken))
response := httptest.NewRecorder()
router.ServeHTTP(response, request)
require.Equal(t, http.StatusNoContent, response.Code)
require.Contains(t, response.Header().Get("Set-Cookie"), cookie.ReauthenticationTokenCookieName+"=")
})
}
}