From 0c41872cd474f8213a1f8c7f2aab4dfee8d792bf Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Mon, 23 Feb 2026 20:34:25 +0100 Subject: [PATCH 01/15] fix: disallow API key renewal and creation with API key authentication (#1334) --- backend/internal/common/errors.go | 7 ++ .../internal/controller/api_key_controller.go | 9 +- .../internal/middleware/auth_middleware.go | 28 +++++ .../middleware/auth_middleware_test.go | 104 ++++++++++++++++++ backend/internal/service/api_key_service.go | 3 + 5 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 backend/internal/middleware/auth_middleware_test.go diff --git a/backend/internal/common/errors.go b/backend/internal/common/errors.go index 13fb9a13..b97f2af0 100644 --- a/backend/internal/common/errors.go +++ b/backend/internal/common/errors.go @@ -280,6 +280,13 @@ func (e *APIKeyExpirationDateError) Error() string { } func (e *APIKeyExpirationDateError) HttpStatusCode() int { return http.StatusBadRequest } +type APIKeyAuthNotAllowedError struct{} + +func (e *APIKeyAuthNotAllowedError) Error() string { + return "API key authentication is not allowed for this endpoint" +} +func (e *APIKeyAuthNotAllowedError) HttpStatusCode() int { return http.StatusForbidden } + type OidcInvalidRefreshTokenError struct{} func (e *OidcInvalidRefreshTokenError) Error() string { diff --git a/backend/internal/controller/api_key_controller.go b/backend/internal/controller/api_key_controller.go index 3b364682..0162b96b 100644 --- a/backend/internal/controller/api_key_controller.go +++ b/backend/internal/controller/api_key_controller.go @@ -26,12 +26,11 @@ func NewApiKeyController(group *gin.RouterGroup, authMiddleware *middleware.Auth uc := &ApiKeyController{apiKeyService: apiKeyService} apiKeyGroup := group.Group("/api-keys") - apiKeyGroup.Use(authMiddleware.WithAdminNotRequired().Add()) { - apiKeyGroup.GET("", uc.listApiKeysHandler) - apiKeyGroup.POST("", uc.createApiKeyHandler) - apiKeyGroup.POST("/:id/renew", uc.renewApiKeyHandler) - apiKeyGroup.DELETE("/:id", uc.revokeApiKeyHandler) + apiKeyGroup.GET("", authMiddleware.WithAdminNotRequired().Add(), uc.listApiKeysHandler) + apiKeyGroup.POST("", authMiddleware.WithAdminNotRequired().WithApiKeyAuthDisabled().Add(), uc.createApiKeyHandler) + apiKeyGroup.POST("/:id/renew", authMiddleware.WithAdminNotRequired().WithApiKeyAuthDisabled().Add(), uc.renewApiKeyHandler) + apiKeyGroup.DELETE("/:id", authMiddleware.WithAdminNotRequired().Add(), uc.revokeApiKeyHandler) } } diff --git a/backend/internal/middleware/auth_middleware.go b/backend/internal/middleware/auth_middleware.go index c66b54f1..3af9ce17 100644 --- a/backend/internal/middleware/auth_middleware.go +++ b/backend/internal/middleware/auth_middleware.go @@ -18,6 +18,7 @@ type AuthMiddleware struct { type AuthOptions struct { AdminRequired bool SuccessOptional bool + AllowApiKeyAuth bool } func NewAuthMiddleware( @@ -31,6 +32,7 @@ func NewAuthMiddleware( options: AuthOptions{ AdminRequired: true, SuccessOptional: false, + AllowApiKeyAuth: true, }, } } @@ -59,6 +61,17 @@ func (m *AuthMiddleware) WithSuccessOptional() *AuthMiddleware { return clone } +// WithApiKeyAuthDisabled disables API key authentication fallback and requires JWT auth. +func (m *AuthMiddleware) WithApiKeyAuthDisabled() *AuthMiddleware { + clone := &AuthMiddleware{ + apiKeyMiddleware: m.apiKeyMiddleware, + jwtMiddleware: m.jwtMiddleware, + options: m.options, + } + clone.options.AllowApiKeyAuth = false + return clone +} + func (m *AuthMiddleware) Add() gin.HandlerFunc { return func(c *gin.Context) { userID, isAdmin, err := m.jwtMiddleware.Verify(c, m.options.AdminRequired) @@ -79,6 +92,21 @@ func (m *AuthMiddleware) Add() gin.HandlerFunc { return } + if !m.options.AllowApiKeyAuth { + if m.options.SuccessOptional { + c.Next() + return + } + + c.Abort() + if c.GetHeader("X-API-Key") != "" { + _ = c.Error(&common.APIKeyAuthNotAllowedError{}) + return + } + _ = c.Error(err) + return + } + // JWT auth failed, try API key auth userID, isAdmin, err = m.apiKeyMiddleware.Verify(c, m.options.AdminRequired) if err == nil { diff --git a/backend/internal/middleware/auth_middleware_test.go b/backend/internal/middleware/auth_middleware_test.go new file mode 100644 index 00000000..e3fb0ee6 --- /dev/null +++ b/backend/internal/middleware/auth_middleware_test.go @@ -0,0 +1,104 @@ +package middleware + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" + "gorm.io/gorm" + + "github.com/pocket-id/pocket-id/backend/internal/common" + "github.com/pocket-id/pocket-id/backend/internal/dto" + "github.com/pocket-id/pocket-id/backend/internal/model" + datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" + "github.com/pocket-id/pocket-id/backend/internal/service" + testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing" +) + +func TestWithApiKeyAuthDisabled(t *testing.T) { + gin.SetMode(gin.TestMode) + + originalEnvConfig := common.EnvConfig + defer func() { + common.EnvConfig = originalEnvConfig + }() + common.EnvConfig.AppURL = "https://test.example.com" + common.EnvConfig.EncryptionKey = []byte("0123456789abcdef0123456789abcdef") + + db := testutils.NewDatabaseForTest(t) + + appConfigService, err := service.NewAppConfigService(t.Context(), db) + require.NoError(t, err) + + jwtService, err := service.NewJwtService(t.Context(), db, appConfigService) + require.NoError(t, err) + + userService := service.NewUserService(db, jwtService, nil, nil, appConfigService, nil, nil, nil, nil) + apiKeyService, err := service.NewApiKeyService(t.Context(), db, nil) + require.NoError(t, err) + + authMiddleware := NewAuthMiddleware(apiKeyService, userService, jwtService) + + user := createUserForAuthMiddlewareTest(t, db) + jwtToken, err := jwtService.GenerateAccessToken(user) + require.NoError(t, err) + + _, apiKeyToken, err := apiKeyService.CreateApiKey(t.Context(), user.ID, dto.ApiKeyCreateDto{ + Name: "Middleware API Key", + ExpiresAt: datatype.DateTime(time.Now().Add(24 * time.Hour)), + }) + require.NoError(t, err) + + router := gin.New() + router.Use(NewErrorHandlerMiddleware().Add()) + router.GET("/api/protected", authMiddleware.WithAdminNotRequired().WithApiKeyAuthDisabled().Add(), func(c *gin.Context) { + c.Status(http.StatusNoContent) + }) + + t.Run("rejects API key auth when API key auth is disabled", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/api/protected", nil) + req.Header.Set("X-API-Key", apiKeyToken) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + require.Equal(t, http.StatusForbidden, recorder.Code) + + var body map[string]string + err := json.Unmarshal(recorder.Body.Bytes(), &body) + require.NoError(t, err) + require.Equal(t, "API key authentication is not allowed for this endpoint", body["error"]) + }) + + t.Run("allows JWT auth when API key auth is disabled", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/api/protected", nil) + req.Header.Set("Authorization", "Bearer "+jwtToken) + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + require.Equal(t, http.StatusNoContent, recorder.Code) + }) +} + +func createUserForAuthMiddlewareTest(t *testing.T, db *gorm.DB) model.User { + t.Helper() + + email := "auth@example.com" + user := model.User{ + Username: "auth-user", + Email: &email, + FirstName: "Auth", + LastName: "User", + DisplayName: "Auth User", + } + + err := db.Create(&user).Error + require.NoError(t, err) + + return user +} diff --git a/backend/internal/service/api_key_service.go b/backend/internal/service/api_key_service.go index 0d175a92..3c29d6e9 100644 --- a/backend/internal/service/api_key_service.go +++ b/backend/internal/service/api_key_service.go @@ -77,6 +77,9 @@ func (s *ApiKeyService) CreateApiKey(ctx context.Context, userID string, input d Create(&apiKey). Error if err != nil { + if errors.Is(err, gorm.ErrDuplicatedKey) { + return model.ApiKey{}, "", &common.AlreadyInUseError{Property: "API key name"} + } return model.ApiKey{}, "", err } From 522a4eee002cca906581df61a8557f77f8e4af4e Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Mon, 23 Feb 2026 20:36:00 +0100 Subject: [PATCH 02/15] chore(translations): update translations via Crowdin (#1335) --- frontend/messages/uk.json | 44 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/frontend/messages/uk.json b/frontend/messages/uk.json index 147f46f7..b6025d7f 100644 --- a/frontend/messages/uk.json +++ b/frontend/messages/uk.json @@ -457,59 +457,59 @@ "custom_client_id_description": "Встановіть власний ідентифікатор клієнта, якщо це вимагається вашим застосунком. В іншому випадку залиште поле порожнім, щоб згенерувати випадковий.", "generated": "Створено", "administration": "Адміністрування", - "group_rdn_attribute_description": "Атрибут, що використовується в розрізнювальному імені групи (DN).", + "group_rdn_attribute_description": "Атрибут, який використовується в DN (Distinguished Name) групи.", "display_name_attribute": "Атрибут імені для відображення", "display_name": "Ім'я для відображення", "configure_application_images": "Налаштування зображень застосунку", - "ui_config_disabled_info_title": "Конфігурація інтерфейсу користувача вимкнена", - "ui_config_disabled_info_description": "Конфігурація інтерфейсу користувача вимкнена, оскільки налаштування конфігурації програми керуються через змінні середовища. Деякі налаштування можуть бути недоступними для редагування.", + "ui_config_disabled_info_title": "Налаштування через UI вимкнено", + "ui_config_disabled_info_description": "Налаштування через UI вимкнено, оскільки параметри конфігурації застосунку керуються через змінні середовища. Деякі налаштування можуть бути недоступні для редагування.", "logo_from_url_description": "Вставте пряму URL-адресу зображення (svg, png, webp). Знайдіть іконки на Selfh.st Icons або Dashboard Icons.", "invalid_url": "Недійсна URL-адреса", "require_user_email": "Потрібна адреса електронної пошти", - "require_user_email_description": "Вимагає від користувачів наявність адреси електронної пошти. Якщо ця опція вимкнена, користувачі без адреси електронної пошти не зможуть користуватися функціями, для яких потрібна адреса електронної пошти.", + "require_user_email_description": "Вимагає наявності електронної адреси у користувачів. Якщо вимкнено, користувачі без електронної адреси не зможуть користуватися функціями, які її вимагають.", "view": "Перегляд", - "toggle_columns": "Перемикання стовпців", - "locale": "Локаль", + "toggle_columns": "Налаштувати стовпці", + "locale": "Мова", "ldap_id": "LDAP-ідентифікатор", - "reauthentication": "Повторна аутентифікація", + "reauthentication": "Повторна автентифікація", "clear_filters": "Очистити фільтри", "default_profile_picture": "Стандартне зображення профілю", "light": "Світла", "dark": "Темна", "system": "Системна", "signup_token_user_groups_description": "Автоматично призначати ці групи користувачам, які реєструються за допомогою цього токена.", - "allowed_oidc_clients": "Дозволені клієнти OIDC", - "allowed_oidc_clients_description": "Виберіть клієнти OIDC, до яких члени цієї групи користувачів мають право входити.", + "allowed_oidc_clients": "Дозволені OIDC-клієнти", + "allowed_oidc_clients_description": "Оберіть OIDC-клієнти, до яких дозволено вхід членам цієї групи користувачів.", "unrestrict_oidc_client": "Не обмежувати {clientName}", - "confirm_unrestrict_oidc_client_description": "Ви впевнені, що хочете зняти обмеження з клієнта OIDC {clientName}? Це призведе до видалення всіх групових призначень для цього клієнта, і будь-який користувач зможе увійти в систему.", - "allowed_oidc_clients_updated_successfully": "Дозволені клієнти OIDC успішно оновлені", + "confirm_unrestrict_oidc_client_description": "Ви впевнені, що хочете зняти обмеження з OIDC-клієнта {clientName}? Це видалить усі призначення груп для цього клієнта, і будь-який користувач зможе виконати вхід.", + "allowed_oidc_clients_updated_successfully": "Дозволені OIDC-клієнти успішно оновлено", "yes": "Так", "no": "Ні", "restricted": "Обмежений", - "scim_provisioning": "Надання SCIM", - "scim_provisioning_description": "SCIM-провізінінг дозволяє автоматично надавати та скасовувати доступ користувачам і групам з вашого клієнта OIDC. Дізнайтеся більше в документації.", + "scim_provisioning": "Синхронізація SCIM", + "scim_provisioning_description": "Постачання користувачів через SCIM дозволяє автоматично додавати та видаляти користувачів і групи у вашому OIDC-клієнті. Дізнайтеся більше у документації.", "scim_endpoint": "Кінцева точка SCIM", "scim_token": "Токен SCIM", "last_successful_sync_at": "Остання успішна синхронізація: {time}", - "scim_configuration_updated_successfully": "Конфігурація SCIM успішно оновлена.", - "scim_enabled_successfully": "SCIM успішно увімкнено.", - "scim_disabled_successfully": "SCIM успішно вимкнено.", - "disable_scim_provisioning": "Вимкнути надання SCIM", - "disable_scim_provisioning_confirm_description": "Ви впевнені, що хочете вимкнути надання доступу SCIM для {clientName}? Це зупинить всі автоматичні процеси надання та скасування доступу для користувачів і груп.", + "scim_configuration_updated_successfully": "Конфігурацію SCIM успішно оновлено.", + "scim_enabled_successfully": "Синхронізація SCIM успішно увімкнено.", + "scim_disabled_successfully": "Синхронізація SCIM успішно вимкнено.", + "disable_scim_provisioning": "Вимкнути SCIM синхронізацію", + "disable_scim_provisioning_confirm_description": "Ви впевнені, що хочете вимкнути постачання користувачів через SCIM для {clientName}? Це зупинить автоматичне додавання та видалення користувачів і груп.", "scim_sync_failed": "Синхронізація SCIM не вдалася. Перевірте журнали сервера для отримання додаткової інформації.", "scim_sync_successful": "Синхронізація SCIM успішно завершена.", "save_and_sync": "Зберегти та синхронізувати", - "scim_save_changes_description": "Перед початком синхронізації SCIM необхідно зберегти зміни. Чи хочете ви зберегти зараз?", + "scim_save_changes_description": "Необхідно зберегти зміни перед запуском синхронізації SCIM. Бажаєте зберегти зараз?", "scopes": "Області застосування", "issuer_url": "URL емітента", - "smtp_field_required_when_other_provided": "Необхідно, якщо вказано будь-яке налаштування SMTP", - "smtp_field_required_when_email_enabled": "Необхідно, якщо увімкнено сповіщення електронною поштою", + "smtp_field_required_when_other_provided": "Обов'язково, якщо вказано будь-який параметр SMTP", + "smtp_field_required_when_email_enabled": "Обов'язково, якщо увімкнено сповіщення електронною поштою", "renew": "Оновити", "renew_api_key": "Оновити API-ключ", "renew_api_key_description": "Оновлення API-ключа призведе до створення нового ключа. Обов'язково оновіть усі інтеграції, що використовують цей ключ.", "api_key_renewed": "API-ключ оновлено", "app_config_home_page": "Головна сторінка", - "app_config_home_page_description": "Сторінка, на яку перенаправляють користувачів після входу в систему.", + "app_config_home_page_description": "Сторінка, на яку користувачі перенаправляються після входу.", "email_verification_warning": "Підтвердьте свою адресу електронної пошти", "email_verification_warning_description": "Ваша електронна адреса ще не підтверджена. Будь ласка, підтвердьте її якомога швидше.", "email_verification": "Перевірка електронної адреси", From 375f0a0c34d2884c7c9cd134fa0d88799d21cf02 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Mon, 23 Feb 2026 20:36:19 +0100 Subject: [PATCH 03/15] release: 2.3.0 --- .version | 2 +- CHANGELOG.md | 41 ++++++++++++++ frontend/package.json | 122 +++++++++++++++++++++--------------------- 3 files changed, 103 insertions(+), 62 deletions(-) diff --git a/.version b/.version index ccbccc3d..276cbf9e 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.2.0 +2.3.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 72f15441..715ee6ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,44 @@ +## v2.3.0 + +### Bug Fixes + +- ENCRYPTION_KEY needed for version and help commands ([#1256](https://github.com/pocket-id/pocket-id/pull/1256) by @kmendell) +- prevent deletion of OIDC provider logo for non admin/anonymous users ([#1267](https://github.com/pocket-id/pocket-id/pull/1267) by @HiMoritz) +- add `type="url"` to url inputs ([bb7b0d5](https://github.com/pocket-id/pocket-id/commit/bb7b0d56084df49b6a003cc3eaf076884e2cbf60) by @stonith404) +- increase rate limit for frontend and api requests ([aab7e36](https://github.com/pocket-id/pocket-id/commit/aab7e364e85f1ce13950da93cc50324328cdd96d) by @stonith404) +- decode URL-encoded client ID and secret in Basic auth ([#1263](https://github.com/pocket-id/pocket-id/pull/1263) by @ypomortsev) +- token endpoint must not accept params as query string args ([#1321](https://github.com/pocket-id/pocket-id/pull/1321) by @ItalyPaleAle) +- left align input error messages ([b3fe143](https://github.com/pocket-id/pocket-id/commit/b3fe14313684f9d8c389ed93ea8e479e3681b5c6) by @stonith404) +- disallow API key renewal and creation with API key authentication ([#1334](https://github.com/pocket-id/pocket-id/pull/1334) by @stonith404) + +### Features + +- add VERSION_CHECK_DISABLED environment variable ([#1254](https://github.com/pocket-id/pocket-id/pull/1254) by @dihmandrake) +- add support for HTTP/2 ([56afebc](https://github.com/pocket-id/pocket-id/commit/56afebc242be7ed14b58185425d6445bf18f640a) by @stonith404) +- manageability of uncompressed geolite db file ([#1234](https://github.com/pocket-id/pocket-id/pull/1234) by @gucheen) +- add JWT ID for generated tokens ([#1322](https://github.com/pocket-id/pocket-id/pull/1322) by @imnotjames) +- current version api endpoint ([#1310](https://github.com/pocket-id/pocket-id/pull/1310) by @kmendell) + +### Other + +- bump @sveltejs/kit from 2.49.2 to 2.49.5 in the npm_and_yarn group across 1 directory ([#1240](https://github.com/pocket-id/pocket-id/pull/1240) by @dependabot[bot]) +- bump svelte from 5.46.1 to 5.46.4 in the npm_and_yarn group across 1 directory ([#1242](https://github.com/pocket-id/pocket-id/pull/1242) by @dependabot[bot]) +- bump devalue to 5.6.2 ([9dbc02e](https://github.com/pocket-id/pocket-id/commit/9dbc02e56871b2de6a39c443e1455efc26a949f7) by @kmendell) +- upgrade deps ([4811625](https://github.com/pocket-id/pocket-id/commit/4811625cdd64b47ea67b7a9b03396e455896ccd6) by @kmendell) +- add Estonian files ([53ef61a](https://github.com/pocket-id/pocket-id/commit/53ef61a3e5c4b77edec49d41ab94302bfec84269) by @kmendell) +- update AAGUIDs ([#1257](https://github.com/pocket-id/pocket-id/pull/1257) by @github-actions[bot]) +- add Norwegian language files ([80558c5](https://github.com/pocket-id/pocket-id/commit/80558c562533e7b4d658d5baa4221d8cd209b47d) by @stonith404) +- run formatter ([60825c5](https://github.com/pocket-id/pocket-id/commit/60825c5743b0e233ab622fd4d0ea04eb7ab59529) by @kmendell) +- bump axios from 1.13.2 to 1.13.5 in the npm_and_yarn group across 1 directory ([#1309](https://github.com/pocket-id/pocket-id/pull/1309) by @dependabot[bot]) +- update dependenicies ([94a4897](https://github.com/pocket-id/pocket-id/commit/94a48977ba24e099b6221838d620c365eb1d4bf4) by @kmendell) +- update AAGUIDs ([#1316](https://github.com/pocket-id/pocket-id/pull/1316) by @github-actions[bot]) +- bump svelte from 5.46.4 to 5.51.5 in the npm_and_yarn group across 1 directory ([#1324](https://github.com/pocket-id/pocket-id/pull/1324) by @dependabot[bot]) +- bump @sveltejs/kit from 2.49.5 to 2.52.2 in the npm_and_yarn group across 1 directory ([#1327](https://github.com/pocket-id/pocket-id/pull/1327) by @dependabot[bot]) +- upgrade dependencies ([0678699](https://github.com/pocket-id/pocket-id/commit/0678699d0cce5448c425b2c16bedab5fc242cbf0) by @stonith404) +- upgrade to node 24 and go 1.26.0 ([#1328](https://github.com/pocket-id/pocket-id/pull/1328) by @kmendell) + +**Full Changelog**: https://github.com/pocket-id/pocket-id/compare/v2.2.0...v2.3.0 + ## v2.2.0 ### Bug Fixes diff --git a/frontend/package.json b/frontend/package.json index 1fa96a18..6b69742d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,63 +1,63 @@ { - "name": "pocket-id-frontend", - "version": "2.2.0", - "private": true, - "type": "module", - "scripts": { - "preinstall": "npx only-allow pnpm", - "dev": "vite dev --port 3000", - "build": "vite build", - "preview": "vite preview --port 3000", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "prettier --check . && eslint .", - "format": "prettier --write ." - }, - "dependencies": { - "@simplewebauthn/browser": "^13.2.2", - "@tailwindcss/vite": "^4.2.0", - "axios": "^1.13.5", - "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "jose": "^6.1.3", - "qrcode": "^1.5.4", - "runed": "^0.37.1", - "sveltekit-superforms": "^2.30.0", - "tailwind-merge": "^3.5.0", - "zod": "^4.3.6" - }, - "devDependencies": { - "@inlang/paraglide-js": "^2.12.0", - "@inlang/plugin-m-function-matcher": "^2.2.1", - "@inlang/plugin-message-format": "^4.3.0", - "@internationalized/date": "^3.11.0", - "@lucide/svelte": "^0.559.0", - "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.53.0", - "@sveltejs/vite-plugin-svelte": "^6.2.4", - "@types/eslint": "^9.6.1", - "@types/node": "^24.10.13", - "@types/qrcode": "^1.5.6", - "bits-ui": "^2.16.2", - "eslint": "^9.39.3", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-svelte": "^3.15.0", - "formsnap": "^2.0.1", - "globals": "^16.5.0", - "mode-watcher": "^1.1.0", - "prettier": "^3.8.1", - "prettier-plugin-svelte": "^3.5.0", - "prettier-plugin-tailwindcss": "^0.7.2", - "rollup": "^4.59.0", - "svelte": "^5.53.2", - "svelte-check": "^4.4.3", - "svelte-sonner": "^1.0.7", - "tailwind-variants": "^3.2.2", - "tailwindcss": "^4.2.0", - "tslib": "^2.8.1", - "tw-animate-css": "^1.4.0", - "typescript": "^5.9.3", - "typescript-eslint": "^8.56.0", - "vite": "^7.3.1" - } + "name": "pocket-id-frontend", + "version": "2.3.0", + "private": true, + "type": "module", + "scripts": { + "preinstall": "npx only-allow pnpm", + "dev": "vite dev --port 3000", + "build": "vite build", + "preview": "vite preview --port 3000", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --check . && eslint .", + "format": "prettier --write ." + }, + "dependencies": { + "@simplewebauthn/browser": "^13.2.2", + "@tailwindcss/vite": "^4.2.0", + "axios": "^1.13.5", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "jose": "^6.1.3", + "qrcode": "^1.5.4", + "runed": "^0.37.1", + "sveltekit-superforms": "^2.30.0", + "tailwind-merge": "^3.5.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "@inlang/paraglide-js": "^2.12.0", + "@inlang/plugin-m-function-matcher": "^2.2.1", + "@inlang/plugin-message-format": "^4.3.0", + "@internationalized/date": "^3.11.0", + "@lucide/svelte": "^0.559.0", + "@sveltejs/adapter-static": "^3.0.10", + "@sveltejs/kit": "^2.53.0", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@types/eslint": "^9.6.1", + "@types/node": "^24.10.13", + "@types/qrcode": "^1.5.6", + "bits-ui": "^2.16.2", + "eslint": "^9.39.3", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.15.0", + "formsnap": "^2.0.1", + "globals": "^16.5.0", + "mode-watcher": "^1.1.0", + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.5.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "rollup": "^4.59.0", + "svelte": "^5.53.2", + "svelte-check": "^4.4.3", + "svelte-sonner": "^1.0.7", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.2.0", + "tslib": "^2.8.1", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.56.0", + "vite": "^7.3.1" + } } From 7d2a9b3345f681de4fc3ee80d4e31e6f7463296c Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Fri, 27 Feb 2026 15:22:33 +0100 Subject: [PATCH 04/15] chore(translations): update translations via Crowdin (#1336) --- frontend/messages/uk.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/messages/uk.json b/frontend/messages/uk.json index b6025d7f..c4e7955c 100644 --- a/frontend/messages/uk.json +++ b/frontend/messages/uk.json @@ -504,22 +504,22 @@ "issuer_url": "URL емітента", "smtp_field_required_when_other_provided": "Обов'язково, якщо вказано будь-який параметр SMTP", "smtp_field_required_when_email_enabled": "Обов'язково, якщо увімкнено сповіщення електронною поштою", - "renew": "Оновити", - "renew_api_key": "Оновити API-ключ", - "renew_api_key_description": "Оновлення API-ключа призведе до створення нового ключа. Обов'язково оновіть усі інтеграції, що використовують цей ключ.", - "api_key_renewed": "API-ключ оновлено", + "renew": "Поновити", + "renew_api_key": "Поновити API-ключ", + "renew_api_key_description": "Поновлення API-ключа згенерує новий ключ. Переконайтеся, що ви оновили всі інтеграції, які його використовують.", + "api_key_renewed": "API-ключ поновлено", "app_config_home_page": "Головна сторінка", "app_config_home_page_description": "Сторінка, на яку користувачі перенаправляються після входу.", "email_verification_warning": "Підтвердьте свою адресу електронної пошти", "email_verification_warning_description": "Ваша електронна адреса ще не підтверджена. Будь ласка, підтвердьте її якомога швидше.", - "email_verification": "Перевірка електронної адреси", - "email_verification_description": "Надсилайте користувачам підтверджувальний лист електронною поштою, коли вони реєструються або змінюють свою адресу електронної пошти.", + "email_verification": "Підтвердження електронної пошти", + "email_verification_description": "Надсилати лист підтвердження користувачам під час реєстрації або зміни електронної адреси.", "email_verification_success_title": "Електронна адреса успішно підтверджена", "email_verification_success_description": "Ваша електронна адреса була успішно підтверджена.", "email_verification_error_title": "Перевірка електронної адреси не вдалася", - "mark_as_unverified": "Позначити як неперевірене", - "mark_as_verified": "Позначити як перевірене", - "email_verification_sent": "Електронний лист для підтвердження надіслано успішно.", - "emails_verified_by_default": "Електронні листи перевіряються за замовчуванням", - "emails_verified_by_default_description": "Якщо ця опція увімкнена, адреси електронної пошти користувачів будуть позначатися як підтверджені за замовчуванням під час реєстрації або при зміні адреси електронної пошти." + "mark_as_unverified": "Позначити як непідтверджену", + "mark_as_verified": "Позначити як підтверджену", + "email_verification_sent": "Електронний лист для підтвердження успішно надіслано.", + "emails_verified_by_default": "Електронні адреси підтверджені за замовчуванням", + "emails_verified_by_default_description": "Якщо увімкнено, електронні адреси користувачів будуть автоматично позначатися як підтверджені під час реєстрації або зміни електронної адреси." } From d98db79d5e7eb6cf9ec9b5aa98381d2a9500f811 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 13:52:27 +0100 Subject: [PATCH 05/15] chore(deps-dev): bump svelte from 5.53.2 to 5.53.5 in the npm_and_yarn group across 1 directory (#1348) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package.json | 2 +- pnpm-lock.yaml | 211 +++++++++++++++++++++++++----------------- 2 files changed, 127 insertions(+), 86 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 6b69742d..fe84b27b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -49,7 +49,7 @@ "prettier-plugin-svelte": "^3.5.0", "prettier-plugin-tailwindcss": "^0.7.2", "rollup": "^4.59.0", - "svelte": "^5.53.2", + "svelte": "^5.53.5", "svelte-check": "^4.4.3", "svelte-sonner": "^1.0.7", "tailwind-variants": "^3.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f752283..0d67b80f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,10 +77,10 @@ importers: version: 1.5.4 runed: specifier: ^0.37.1 - version: 0.37.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(zod@4.3.6) + version: 0.37.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(zod@4.3.6) sveltekit-superforms: specifier: ^2.30.0 - version: 2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.2)(typescript@5.9.3) + version: 2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3) tailwind-merge: specifier: ^3.5.0 version: 3.5.0 @@ -102,16 +102,16 @@ importers: version: 3.11.0 '@lucide/svelte': specifier: ^0.559.0 - version: 0.559.0(svelte@5.53.2) + version: 0.559.0(svelte@5.53.5) '@sveltejs/adapter-static': specifier: ^3.0.10 - version: 3.0.10(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))) + version: 3.0.10(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))) '@sveltejs/kit': specifier: ^2.53.0 - version: 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + version: 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 - version: 6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + version: 6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@types/eslint': specifier: ^9.6.1 version: 9.6.1 @@ -123,7 +123,7 @@ importers: version: 1.5.6 bits-ui: specifier: ^2.16.2 - version: 2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2) + version: 2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) eslint: specifier: ^9.39.3 version: 9.39.3(jiti@2.6.1) @@ -132,37 +132,37 @@ importers: version: 10.1.8(eslint@9.39.3(jiti@2.6.1)) eslint-plugin-svelte: specifier: ^3.15.0 - version: 3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.2) + version: 3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.5) formsnap: specifier: ^2.0.1 - version: 2.0.1(svelte@5.53.2)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.2)(typescript@5.9.3)) + version: 2.0.1(svelte@5.53.5)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3)) globals: specifier: ^16.5.0 version: 16.5.0 mode-watcher: specifier: ^1.1.0 - version: 1.1.0(svelte@5.53.2) + version: 1.1.0(svelte@5.53.5) prettier: specifier: ^3.8.1 version: 3.8.1 prettier-plugin-svelte: specifier: ^3.5.0 - version: 3.5.0(prettier@3.8.1)(svelte@5.53.2) + version: 3.5.0(prettier@3.8.1)(svelte@5.53.5) prettier-plugin-tailwindcss: specifier: ^0.7.2 - version: 0.7.2(prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.2))(prettier@3.8.1) + version: 0.7.2(prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.5))(prettier@3.8.1) rollup: specifier: ^4.59.0 version: 4.59.0 svelte: - specifier: ^5.53.2 - version: 5.53.2 + specifier: ^5.53.5 + version: 5.53.5 svelte-check: specifier: ^4.4.3 - version: 4.4.3(picomatch@4.0.3)(svelte@5.53.2)(typescript@5.9.3) + version: 4.4.3(picomatch@4.0.3)(svelte@5.53.5)(typescript@5.9.3) svelte-sonner: specifier: ^1.0.7 - version: 1.0.7(svelte@5.53.2) + version: 1.0.7(svelte@5.53.5) tailwind-variants: specifier: ^3.2.2 version: 3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.0) @@ -832,89 +832,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -1010,24 +1026,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@16.1.6': resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@16.1.6': resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@16.1.6': resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@16.1.6': resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} @@ -1246,66 +1266,79 @@ packages: resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.59.0': resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.59.0': resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.59.0': resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.59.0': resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.59.0': resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.59.0': resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.59.0': resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.59.0': resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.59.0': resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.59.0': resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.59.0': resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.59.0': resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.59.0': resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} @@ -1450,24 +1483,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.0': resolution: {integrity: sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.0': resolution: {integrity: sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.0': resolution: {integrity: sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.0': resolution: {integrity: sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw==} @@ -1681,8 +1718,8 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + aria-query@5.3.1: + resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} engines: {node: '>= 0.4'} arkregex@0.0.5: @@ -2409,24 +2446,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} @@ -3068,8 +3109,8 @@ packages: peerDependencies: svelte: ^5.0.0 - svelte@5.53.2: - resolution: {integrity: sha512-yGONuIrcl/BMmqbm6/52Q/NYzfkta7uVlos5NSzGTfNJTTFtPPzra6rAQoQIwAqupeM3s9uuTf5PvioeiCdg9g==} + svelte@5.53.5: + resolution: {integrity: sha512-YkqERnF05g8KLdDZwZrF8/i1eSbj6Eoat8Jjr2IfruZz9StLuBqo8sfCSzjosNKd+ZrQ8DkKZDjpO5y3ht1Pow==} engines: {node: '>=18'} sveltekit-superforms@2.30.0: @@ -3910,9 +3951,9 @@ snapshots: '@lix-js/server-protocol-schema@0.1.1': {} - '@lucide/svelte@0.559.0(svelte@5.53.2)': + '@lucide/svelte@0.559.0(svelte@5.53.5)': dependencies: - svelte: 5.53.2 + svelte: 5.53.5 '@next/env@16.1.6': {} @@ -4191,15 +4232,15 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))': dependencies: - '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) - '@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': + '@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 1.1.1 @@ -4210,25 +4251,25 @@ snapshots: mrmime: 2.0.1 set-cookie-parser: 3.0.1 sirv: 3.0.2 - svelte: 5.53.2 + svelte: 5.53.5 vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) optionalDependencies: typescript: 5.9.3 - '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) obug: 2.1.1 - svelte: 5.53.2 + svelte: 5.53.5 vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) - '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 - svelte: 5.53.2 + svelte: 5.53.5 vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) vitefu: 1.1.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) @@ -4522,7 +4563,7 @@ snapshots: argparse@2.0.1: {} - aria-query@5.3.2: {} + aria-query@5.3.1: {} arkregex@0.0.5: dependencies: @@ -4563,15 +4604,15 @@ snapshots: baseline-browser-mapping@2.9.19: {} - bits-ui@2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2): + bits-ui@2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5): dependencies: '@floating-ui/core': 1.7.4 '@floating-ui/dom': 1.7.5 '@internationalized/date': 3.11.0 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2) - svelte: 5.53.2 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2) + runed: 0.35.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) + svelte: 5.53.5 + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) tabbable: 6.4.0 transitivePeerDependencies: - '@sveltejs/kit' @@ -4915,7 +4956,7 @@ snapshots: dependencies: eslint: 9.39.3(jiti@2.6.1) - eslint-plugin-svelte@3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.2): + eslint-plugin-svelte@3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.5): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) '@jridgewell/sourcemap-codec': 1.5.5 @@ -4927,9 +4968,9 @@ snapshots: postcss-load-config: 3.1.4(postcss@8.5.6) postcss-safe-parser: 7.0.1(postcss@8.5.6) semver: 7.7.4 - svelte-eslint-parser: 1.4.1(svelte@5.53.2) + svelte-eslint-parser: 1.4.1(svelte@5.53.5) optionalDependencies: - svelte: 5.53.2 + svelte: 5.53.5 transitivePeerDependencies: - ts-node @@ -5063,11 +5104,11 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 - formsnap@2.0.1(svelte@5.53.2)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.2)(typescript@5.9.3)): + formsnap@2.0.1(svelte@5.53.5)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3)): dependencies: - svelte: 5.53.2 - svelte-toolbelt: 0.5.0(svelte@5.53.2) - sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.2)(typescript@5.9.3) + svelte: 5.53.5 + svelte-toolbelt: 0.5.0(svelte@5.53.5) + sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3) fsevents@2.3.2: optional: true @@ -5365,11 +5406,11 @@ snapshots: minipass@7.1.2: {} - mode-watcher@1.1.0(svelte@5.53.2): + mode-watcher@1.1.0(svelte@5.53.5): dependencies: - runed: 0.25.0(svelte@5.53.2) - svelte: 5.53.2 - svelte-toolbelt: 0.7.1(svelte@5.53.2) + runed: 0.25.0(svelte@5.53.5) + svelte: 5.53.5 + svelte-toolbelt: 0.7.1(svelte@5.53.5) mri@1.2.0: {} @@ -5544,16 +5585,16 @@ snapshots: prelude-ls@1.2.1: {} - prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.2): + prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.5): dependencies: prettier: 3.8.1 - svelte: 5.53.2 + svelte: 5.53.5 - prettier-plugin-tailwindcss@0.7.2(prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.2))(prettier@3.8.1): + prettier-plugin-tailwindcss@0.7.2(prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.5))(prettier@3.8.1): dependencies: prettier: 3.8.1 optionalDependencies: - prettier-plugin-svelte: 3.5.0(prettier@3.8.1)(svelte@5.53.2) + prettier-plugin-svelte: 3.5.0(prettier@3.8.1)(svelte@5.53.5) prettier@3.7.4: {} @@ -5661,38 +5702,38 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 - runed@0.23.4(svelte@5.53.2): + runed@0.23.4(svelte@5.53.5): dependencies: esm-env: 1.2.2 - svelte: 5.53.2 + svelte: 5.53.5 - runed@0.25.0(svelte@5.53.2): + runed@0.25.0(svelte@5.53.5): dependencies: esm-env: 1.2.2 - svelte: 5.53.2 + svelte: 5.53.5 - runed@0.28.0(svelte@5.53.2): + runed@0.28.0(svelte@5.53.5): dependencies: esm-env: 1.2.2 - svelte: 5.53.2 + svelte: 5.53.5 - runed@0.35.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2): + runed@0.35.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 - svelte: 5.53.2 + svelte: 5.53.5 optionalDependencies: - '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) - runed@0.37.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(zod@4.3.6): + runed@0.37.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(zod@4.3.6): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 - svelte: 5.53.2 + svelte: 5.53.5 optionalDependencies: - '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) zod: 4.3.6 sade@1.8.1: @@ -5853,19 +5894,19 @@ snapshots: dependencies: has-flag: 4.0.0 - svelte-check@4.4.3(picomatch@4.0.3)(svelte@5.53.2)(typescript@5.9.3): + svelte-check@4.4.3(picomatch@4.0.3)(svelte@5.53.5)(typescript@5.9.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.3) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.53.2 + svelte: 5.53.5 typescript: 5.9.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.4.1(svelte@5.53.2): + svelte-eslint-parser@1.4.1(svelte@5.53.5): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -5874,36 +5915,36 @@ snapshots: postcss-scss: 4.0.9(postcss@8.5.6) postcss-selector-parser: 7.1.1 optionalDependencies: - svelte: 5.53.2 + svelte: 5.53.5 - svelte-sonner@1.0.7(svelte@5.53.2): + svelte-sonner@1.0.7(svelte@5.53.5): dependencies: - runed: 0.28.0(svelte@5.53.2) - svelte: 5.53.2 + runed: 0.28.0(svelte@5.53.5) + svelte: 5.53.5 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2) + runed: 0.35.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) style-to-object: 1.0.14 - svelte: 5.53.2 + svelte: 5.53.5 transitivePeerDependencies: - '@sveltejs/kit' - svelte-toolbelt@0.5.0(svelte@5.53.2): + svelte-toolbelt@0.5.0(svelte@5.53.5): dependencies: clsx: 2.1.1 style-to-object: 1.0.14 - svelte: 5.53.2 + svelte: 5.53.5 - svelte-toolbelt@0.7.1(svelte@5.53.2): + svelte-toolbelt@0.7.1(svelte@5.53.5): dependencies: clsx: 2.1.1 - runed: 0.23.4(svelte@5.53.2) + runed: 0.23.4(svelte@5.53.5) style-to-object: 1.0.14 - svelte: 5.53.2 + svelte: 5.53.5 - svelte@5.53.2: + svelte@5.53.5: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 @@ -5911,7 +5952,7 @@ snapshots: '@types/estree': 1.0.8 '@types/trusted-types': 2.0.7 acorn: 8.16.0 - aria-query: 5.3.2 + aria-query: 5.3.1 axobject-query: 4.1.0 clsx: 2.1.1 devalue: 5.6.3 @@ -5922,12 +5963,12 @@ snapshots: magic-string: 0.30.21 zimmerframe: 1.1.4 - sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.2)(typescript@5.9.3): + sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3): dependencies: - '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.2)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) devalue: 5.6.3 memoize-weak: 1.0.2 - svelte: 5.53.2 + svelte: 5.53.5 ts-deepmerge: 7.0.3 optionalDependencies: '@exodus/schemasafe': 1.3.0 From 3a339e33191c31b68bf57db907f800d9de5ffbc8 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Sat, 28 Feb 2026 14:08:35 +0100 Subject: [PATCH 06/15] fix: improve wildcard matching by using `go-urlpattern` (#1332) --- backend/go.mod | 3 + backend/go.sum | 42 +++ backend/internal/dto/validations.go | 19 +- backend/internal/utils/callback_url_util.go | 238 ++++++------- .../internal/utils/callback_url_util_test.go | 322 ++++-------------- 5 files changed, 242 insertions(+), 382 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index 9013dc1e..28b6a327 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -12,6 +12,7 @@ require ( github.com/cenkalti/backoff/v5 v5.0.3 github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec github.com/disintegration/imaging v1.6.2 + github.com/dunglas/go-urlpattern v0.0.0-20241020164140-716dfa1c80b1 github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 github.com/emersion/go-smtp v0.24.0 github.com/gin-contrib/slog v1.2.0 @@ -74,6 +75,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.14.3 // indirect github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect @@ -123,6 +125,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/nlnwa/whatwg-url v0.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect diff --git a/backend/go.sum b/backend/go.sum index 49d3a7a9..4486468e 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -46,6 +46,9 @@ github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0= github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.14.3 h1:Gd2c8lSNf9pKXom5JtD7AaKO8o7fGQ2LtFj1436qilA= +github.com/bits-and-blooms/bitset v1.14.3/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= @@ -88,6 +91,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dunglas/go-urlpattern v0.0.0-20241020164140-716dfa1c80b1 h1:RW22Y3QjGrb97NUA8yupdFcaqg//+hMI2fZrETBvQ4s= +github.com/dunglas/go-urlpattern v0.0.0-20241020164140-716dfa1c80b1/go.mod h1:mnVcdqOeYg0HvT6veRo7wINa1mJ+lC/R4ig2lWcapSI= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk= @@ -257,6 +262,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nlnwa/whatwg-url v0.5.0 h1:l71cqfqG44+VCQZQX3wD4bwheFWicPxuwaCimLEfpDo= +github.com/nlnwa/whatwg-url v0.5.0/go.mod h1:X/ejnFFVbaOWdSul+cnlsSHviCzGZJdvPkgc9zD8IY8= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -320,6 +327,7 @@ github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADT github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/bridges/otelslog v0.15.0 h1:yOYhGNPZseueTTvWp5iBD3/CthrmvayUXYEX862dDi4= @@ -385,6 +393,9 @@ golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y= golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= @@ -392,34 +403,65 @@ golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkN golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc= golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= diff --git a/backend/internal/dto/validations.go b/backend/internal/dto/validations.go index 8aca787a..135706fa 100644 --- a/backend/internal/dto/validations.go +++ b/backend/internal/dto/validations.go @@ -1,9 +1,7 @@ package dto import ( - "net/url" "regexp" - "strings" "time" "github.com/pocket-id/pocket-id/backend/internal/utils" @@ -67,19 +65,6 @@ func ValidateClientID(clientID string) bool { // ValidateCallbackURL validates callback URLs with support for wildcards func ValidateCallbackURL(raw string) bool { - // Don't validate if it contains a wildcard - if strings.Contains(raw, "*") { - return true - } - - u, err := url.Parse(raw) - if err != nil { - return false - } - - if !u.IsAbs() { - return false - } - - return true + err := utils.ValidateCallbackURLPattern(raw) + return err == nil } diff --git a/backend/internal/utils/callback_url_util.go b/backend/internal/utils/callback_url_util.go index 4ce7e98b..f4c3306b 100644 --- a/backend/internal/utils/callback_url_util.go +++ b/backend/internal/utils/callback_url_util.go @@ -1,14 +1,31 @@ package utils import ( + "log/slog" "net" "net/url" "path" - "regexp" + "strconv" "strings" + + "github.com/dunglas/go-urlpattern" ) -// GetCallbackURLFromList returns the first callback URL that matches the input callback URL +// ValidateCallbackURLPattern checks if the given callback URL pattern +// is valid according to the rules defined in this package. +func ValidateCallbackURLPattern(pattern string) error { + if pattern == "*" { + return nil + } + + pattern, _, _ = strings.Cut(pattern, "#") + pattern = normalizeToURLPatternStandard(pattern) + + _, err := urlpattern.New(pattern, "", nil) + return err +} + +// GetCallbackURLFromList returns the first callback URL that matches the input callback URL. func GetCallbackURLFromList(urls []string, inputCallbackURL string) (callbackURL string, err error) { // Special case for Loopback Interface Redirection. Quoting from RFC 8252 section 7.3: // https://datatracker.ietf.org/doc/html/rfc8252#section-7.3 @@ -24,7 +41,12 @@ func GetCallbackURLFromList(urls []string, inputCallbackURL string) (callbackURL host := u.Hostname() ip := net.ParseIP(host) if host == "localhost" || (ip != nil && ip.IsLoopback()) { - u.Host = host + // For IPv6 loopback hosts, brackets are required when serializing without a port. + if strings.Contains(host, ":") { + u.Host = "[" + host + "]" + } else { + u.Host = host + } loopbackCallbackURLWithoutPort = u.String() } } @@ -64,143 +86,129 @@ func matchCallbackURL(pattern string, inputCallbackURL string) (matches bool, er return true, nil } - // Strip fragment part + // Strip fragment part. // The endpoint URI MUST NOT include a fragment component. // https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2 pattern, _, _ = strings.Cut(pattern, "#") inputCallbackURL, _, _ = strings.Cut(inputCallbackURL, "#") // Store and strip query part - var patternQuery url.Values - if i := strings.Index(pattern, "?"); i >= 0 { - patternQuery, err = url.ParseQuery(pattern[i+1:]) - if err != nil { - return false, err - } - pattern = pattern[:i] - } - var inputQuery url.Values - if i := strings.Index(inputCallbackURL, "?"); i >= 0 { - inputQuery, err = url.ParseQuery(inputCallbackURL[i+1:]) - if err != nil { - return false, err - } - inputCallbackURL = inputCallbackURL[:i] - } - - // Split both pattern and input parts - patternParts, patternPath := splitParts(pattern) - inputParts, inputPath := splitParts(inputCallbackURL) - - // Verify everything except the path and query parameters - if len(patternParts) != len(inputParts) { - return false, nil - } - - for i, patternPart := range patternParts { - matched, err := path.Match(patternPart, inputParts[i]) - if err != nil || !matched { - return false, err - } - } - - // Verify path with wildcard support - matched, err := matchPath(patternPath, inputPath) - if err != nil || !matched { + pattern, patternQuery, err := extractQueryParams(pattern) + if err != nil { return false, err } - // Verify query parameters - if len(patternQuery) != len(inputQuery) { + inputCallbackURL, inputQuery, err := extractQueryParams(inputCallbackURL) + if err != nil { + return false, err + } + + pattern = normalizeToURLPatternStandard(pattern) + + // Validate query params + v := validateQueryParams(patternQuery, inputQuery) + if !v { return false, nil } + // Validate the rest of the URL using urlpattern + p, err := urlpattern.New(pattern, "", nil) + if err != nil { + //nolint:nilerr + slog.Warn("invalid callback URL pattern, skipping", "pattern", pattern, "error", err) + return false, nil + } + + return p.Test(inputCallbackURL, ""), nil +} + +// normalizeToURLPatternStandard converts patterns with single asterisk wildcards and globstar wildcards +// into a format that can be parsed by the urlpattern package, which uses :param for single segment wildcards +// and ** for multi-segment wildcards. +func normalizeToURLPatternStandard(pattern string) string { + patternBase, patternPath := extractPath(pattern) + + var result strings.Builder + for i := 0; i < len(patternPath); i++ { + if patternPath[i] == '*' { + // Replace globstar with a single asterisk + if i+1 < len(patternPath) && patternPath[i+1] == '*' { + result.WriteString("*") + i++ // skip next * + } else { + // Replace single asterisk with :p{index} + result.WriteString(":p") + result.WriteString(strconv.Itoa(i)) + } + } else { + result.WriteByte(patternPath[i]) + } + } + patternPath = result.String() + + return patternBase + patternPath +} + +func extractPath(url string) (base string, path string) { + pathStart := -1 + + // Look for scheme:// first + if i := strings.Index(url, "://"); i >= 0 { + // Look for the next slash after scheme:// + rest := url[i+3:] + if j := strings.IndexByte(rest, '/'); j >= 0 { + pathStart = i + 3 + j + } + } else { + // Otherwise, first slash is path start + pathStart = strings.IndexByte(url, '/') + } + + if pathStart >= 0 { + path = url[pathStart:] + base = url[:pathStart] + } else { + path = "" + base = url + } + + return base, path +} + +func extractQueryParams(rawUrl string) (base string, query url.Values, err error) { + if i := strings.IndexByte(rawUrl, '?'); i >= 0 { + query, err = url.ParseQuery(rawUrl[i+1:]) + if err != nil { + return "", nil, err + } + rawUrl = rawUrl[:i] + } + + return rawUrl, query, nil +} + +func validateQueryParams(patternQuery, inputQuery url.Values) bool { + if len(patternQuery) != len(inputQuery) { + return false + } + for patternKey, patternValues := range patternQuery { inputValues, exists := inputQuery[patternKey] if !exists { - return false, nil + return false } if len(patternValues) != len(inputValues) { - return false, nil + return false } for i := range patternValues { matched, err := path.Match(patternValues[i], inputValues[i]) if err != nil || !matched { - return false, err + return false } } } - return true, nil -} - -// matchPath matches the input path against the pattern with wildcard support -// Supported wildcards: -// -// '*' matches any sequence of characters except '/' -// '**' matches any sequence of characters including '/' -func matchPath(pattern string, input string) (matches bool, err error) { - var regexPattern strings.Builder - regexPattern.WriteString("^") - - runes := []rune(pattern) - n := len(runes) - - for i := 0; i < n; { - switch runes[i] { - case '*': - // Check if it's a ** (globstar) - if i+1 < n && runes[i+1] == '*' { - // globstar = .* (match slashes too) - regexPattern.WriteString(".*") - i += 2 - } else { - // single * = [^/]* (no slash) - regexPattern.WriteString(`[^/]*`) - i++ - } - default: - regexPattern.WriteString(regexp.QuoteMeta(string(runes[i]))) - i++ - } - } - - regexPattern.WriteString("$") - - matched, err := regexp.MatchString(regexPattern.String(), input) - return matched, err -} - -// splitParts splits the URL into parts by special characters and returns the path separately -func splitParts(s string) (parts []string, path string) { - split := func(r rune) bool { - return r == ':' || r == '/' || r == '[' || r == ']' || r == '@' || r == '.' - } - - pathStart := -1 - - // Look for scheme:// first - if i := strings.Index(s, "://"); i >= 0 { - // Look for the next slash after scheme:// - rest := s[i+3:] - if j := strings.IndexRune(rest, '/'); j >= 0 { - pathStart = i + 3 + j - } - } else { - // Otherwise, first slash is path start - pathStart = strings.IndexRune(s, '/') - } - - if pathStart >= 0 { - path = s[pathStart:] - base := s[:pathStart] - parts = strings.FieldsFunc(base, split) - } else { - parts = strings.FieldsFunc(s, split) - path = "" - } - - return parts, path + return true } diff --git a/backend/internal/utils/callback_url_util_test.go b/backend/internal/utils/callback_url_util_test.go index 959a8789..c3423dde 100644 --- a/backend/internal/utils/callback_url_util_test.go +++ b/backend/internal/utils/callback_url_util_test.go @@ -7,6 +7,77 @@ import ( "github.com/stretchr/testify/require" ) +func TestValidateCallbackURLPattern(t *testing.T) { + tests := []struct { + name string + pattern string + shouldError bool + }{ + { + name: "exact URL", + pattern: "https://example.com/callback", + shouldError: false, + }, + { + name: "wildcard scheme", + pattern: "*://example.com/callback", + shouldError: false, + }, + { + name: "wildcard port", + pattern: "https://example.com:*/callback", + shouldError: false, + }, + { + name: "partial wildcard port", + pattern: "https://example.com:80*/callback", + shouldError: false, + }, + { + name: "wildcard userinfo", + pattern: "https://user:*@example.com/callback", + shouldError: false, + }, + { + name: "glob wildcard", + pattern: "*", + shouldError: false, + }, + { + name: "relative URL", + pattern: "/callback", + shouldError: true, + }, + { + name: "missing scheme separator", + pattern: "https//example.com/callback", + shouldError: true, + }, + { + name: "malformed wildcard host glob", + pattern: "https://exa[mple.com/callback", + shouldError: true, + }, + { + name: "malformed authority", + pattern: "https://[::1/callback", + shouldError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateCallbackURLPattern(tt.pattern) + if tt.shouldError { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + func TestMatchCallbackURL(t *testing.T) { tests := []struct { name string @@ -187,12 +258,6 @@ func TestMatchCallbackURL(t *testing.T) { "https://example.com/callback", false, }, - { - "unexpected credentials", - "https://example.com/callback", - "https://user:pass@example.com/callback", - false, - }, { "wildcard password", "https://user:*@example.com/callback", @@ -347,7 +412,7 @@ func TestMatchCallbackURL(t *testing.T) { "backslash instead of forward slash", "https://example.com/callback", "https://example.com\\callback", - false, + true, }, { "double slash in hostname (protocol smuggling)", @@ -553,246 +618,3 @@ func TestGetCallbackURLFromList_MultiplePatterns(t *testing.T) { }) } } - -func TestMatchPath(t *testing.T) { - tests := []struct { - name string - pattern string - input string - shouldMatch bool - }{ - // Exact matches - { - name: "exact match", - pattern: "/callback", - input: "/callback", - shouldMatch: true, - }, - { - name: "exact mismatch", - pattern: "/callback", - input: "/other", - shouldMatch: false, - }, - { - name: "empty paths", - pattern: "", - input: "", - shouldMatch: true, - }, - - // Single wildcard (*) - { - name: "single wildcard matches segment", - pattern: "/api/*/callback", - input: "/api/v1/callback", - shouldMatch: true, - }, - { - name: "single wildcard doesn't match multiple segments", - pattern: "/api/*/callback", - input: "/api/v1/v2/callback", - shouldMatch: false, - }, - { - name: "single wildcard at end", - pattern: "/callback/*", - input: "/callback/test", - shouldMatch: true, - }, - { - name: "single wildcard at start", - pattern: "/*/callback", - input: "/api/callback", - shouldMatch: true, - }, - { - name: "multiple single wildcards", - pattern: "/*/test/*", - input: "/api/test/callback", - shouldMatch: true, - }, - { - name: "partial wildcard prefix", - pattern: "/test*", - input: "/testing", - shouldMatch: true, - }, - { - name: "partial wildcard suffix", - pattern: "/*-callback", - input: "/oauth-callback", - shouldMatch: true, - }, - { - name: "partial wildcard middle", - pattern: "/api-*-v1", - input: "/api-internal-v1", - shouldMatch: true, - }, - - // Double wildcard (**) - { - name: "double wildcard matches multiple segments", - pattern: "/api/**/callback", - input: "/api/v1/v2/v3/callback", - shouldMatch: true, - }, - { - name: "double wildcard matches single segment", - pattern: "/api/**/callback", - input: "/api/v1/callback", - shouldMatch: true, - }, - { - name: "double wildcard doesn't match when pattern has extra slashes", - pattern: "/api/**/callback", - input: "/api/callback", - shouldMatch: false, - }, - { - name: "double wildcard at end", - pattern: "/api/**", - input: "/api/v1/v2/callback", - shouldMatch: true, - }, - { - name: "double wildcard in middle", - pattern: "/api/**/v2/**/callback", - input: "/api/v1/v2/v3/v4/callback", - shouldMatch: true, - }, - - // Complex patterns - { - name: "mix of single and double wildcards", - pattern: "/*/api/**/callback", - input: "/app/api/v1/v2/callback", - shouldMatch: true, - }, - { - name: "wildcard with special characters", - pattern: "/callback-*", - input: "/callback-123", - shouldMatch: true, - }, - { - name: "path with query-like string (no special handling)", - pattern: "/callback?code=*", - input: "/callback?code=abc", - shouldMatch: true, - }, - - // Edge cases - { - name: "single wildcard matches empty segment", - pattern: "/api/*/callback", - input: "/api//callback", - shouldMatch: true, - }, - { - name: "pattern longer than input", - pattern: "/api/v1/callback", - input: "/api", - shouldMatch: false, - }, - { - name: "input longer than pattern", - pattern: "/api", - input: "/api/v1/callback", - shouldMatch: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - matches, err := matchPath(tt.pattern, tt.input) - require.NoError(t, err) - assert.Equal(t, tt.shouldMatch, matches) - }) - } -} - -func TestSplitParts(t *testing.T) { - tests := []struct { - name string - input string - expectedParts []string - expectedPath string - }{ - { - name: "simple https URL", - input: "https://example.com/callback", - expectedParts: []string{"https", "example", "com"}, - expectedPath: "/callback", - }, - { - name: "URL with port", - input: "https://example.com:8080/callback", - expectedParts: []string{"https", "example", "com", "8080"}, - expectedPath: "/callback", - }, - { - name: "URL with subdomain", - input: "https://api.example.com/callback", - expectedParts: []string{"https", "api", "example", "com"}, - expectedPath: "/callback", - }, - { - name: "URL with credentials", - input: "https://user:pass@example.com/callback", - expectedParts: []string{"https", "user", "pass", "example", "com"}, - expectedPath: "/callback", - }, - { - name: "URL without path", - input: "https://example.com", - expectedParts: []string{"https", "example", "com"}, - expectedPath: "", - }, - { - name: "URL with deep path", - input: "https://example.com/api/v1/callback", - expectedParts: []string{"https", "example", "com"}, - expectedPath: "/api/v1/callback", - }, - { - name: "URL with path and query", - input: "https://example.com/callback?code=123", - expectedParts: []string{"https", "example", "com"}, - expectedPath: "/callback?code=123", - }, - { - name: "URL with trailing slash", - input: "https://example.com/", - expectedParts: []string{"https", "example", "com"}, - expectedPath: "/", - }, - { - name: "URL with multiple subdomains", - input: "https://api.v1.staging.example.com/callback", - expectedParts: []string{"https", "api", "v1", "staging", "example", "com"}, - expectedPath: "/callback", - }, - { - name: "URL with port and credentials", - input: "https://user:pass@example.com:8080/callback", - expectedParts: []string{"https", "user", "pass", "example", "com", "8080"}, - expectedPath: "/callback", - }, - { - name: "scheme with authority separator but no slash", - input: "http://example.com", - expectedParts: []string{"http", "example", "com"}, - expectedPath: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - parts, path := splitParts(tt.input) - assert.Equal(t, tt.expectedParts, parts, "parts mismatch") - assert.Equal(t, tt.expectedPath, path, "path mismatch") - }) - } -} From 590e495c1d5f349004f092b4992f437f903f40f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 09:04:45 -0600 Subject: [PATCH 07/15] chore(deps-dev): bump @sveltejs/kit from 2.53.0 to 2.53.3 in the npm_and_yarn group across 1 directory (#1349) --- frontend/package.json | 2 +- pnpm-lock.yaml | 50 +++++++++++++++++++++---------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index fe84b27b..383c0e54 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,7 +33,7 @@ "@internationalized/date": "^3.11.0", "@lucide/svelte": "^0.559.0", "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.53.0", + "@sveltejs/kit": "^2.53.3", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@types/eslint": "^9.6.1", "@types/node": "^24.10.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d67b80f..f6e490f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,10 +77,10 @@ importers: version: 1.5.4 runed: specifier: ^0.37.1 - version: 0.37.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(zod@4.3.6) + version: 0.37.1(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(zod@4.3.6) sveltekit-superforms: specifier: ^2.30.0 - version: 2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3) + version: 2.30.0(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3) tailwind-merge: specifier: ^3.5.0 version: 3.5.0 @@ -105,10 +105,10 @@ importers: version: 0.559.0(svelte@5.53.5) '@sveltejs/adapter-static': specifier: ^3.0.10 - version: 3.0.10(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))) + version: 3.0.10(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))) '@sveltejs/kit': - specifier: ^2.53.0 - version: 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + specifier: ^2.53.3 + version: 2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 version: 6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) @@ -123,7 +123,7 @@ importers: version: 1.5.6 bits-ui: specifier: ^2.16.2 - version: 2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) + version: 2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) eslint: specifier: ^9.39.3 version: 9.39.3(jiti@2.6.1) @@ -135,7 +135,7 @@ importers: version: 3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.5) formsnap: specifier: ^2.0.1 - version: 2.0.1(svelte@5.53.5)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3)) + version: 2.0.1(svelte@5.53.5)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3)) globals: specifier: ^16.5.0 version: 16.5.0 @@ -1408,8 +1408,8 @@ packages: peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/kit@2.53.0': - resolution: {integrity: sha512-Brh/9h8QEg7rWIj+Nnz/2sC49NUeS8g3Qd9H5dTO3EbWG8vCEUl06jE+r5jQVDMHdr1swmCkwZkONFsWelGTpQ==} + '@sveltejs/kit@2.53.3': + resolution: {integrity: sha512-tshOeBUid2v5LAblUpatIdFm5Cyykbw2EiKWOunAAX0A/oJaR7DOdC9wLR5Qqh9zUf3QUISA2m9A3suBdQSYQg==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -4232,11 +4232,11 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))': dependencies: - '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) - '@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': + '@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) @@ -4604,15 +4604,15 @@ snapshots: baseline-browser-mapping@2.9.19: {} - bits-ui@2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5): + bits-ui@2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5): dependencies: '@floating-ui/core': 1.7.4 '@floating-ui/dom': 1.7.5 '@internationalized/date': 3.11.0 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) + runed: 0.35.1(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) svelte: 5.53.5 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) tabbable: 6.4.0 transitivePeerDependencies: - '@sveltejs/kit' @@ -5104,11 +5104,11 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 - formsnap@2.0.1(svelte@5.53.5)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3)): + formsnap@2.0.1(svelte@5.53.5)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3)): dependencies: svelte: 5.53.5 svelte-toolbelt: 0.5.0(svelte@5.53.5) - sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3) + sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3) fsevents@2.3.2: optional: true @@ -5717,23 +5717,23 @@ snapshots: esm-env: 1.2.2 svelte: 5.53.5 - runed@0.35.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5): + runed@0.35.1(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 svelte: 5.53.5 optionalDependencies: - '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) - runed@0.37.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(zod@4.3.6): + runed@0.37.1(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(zod@4.3.6): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 svelte: 5.53.5 optionalDependencies: - '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) zod: 4.3.6 sade@1.8.1: @@ -5922,10 +5922,10 @@ snapshots: runed: 0.28.0(svelte@5.53.5) svelte: 5.53.5 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) + runed: 0.35.1(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) style-to-object: 1.0.14 svelte: 5.53.5 transitivePeerDependencies: @@ -5963,9 +5963,9 @@ snapshots: magic-string: 0.30.21 zimmerframe: 1.1.4 - sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3): + sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3): dependencies: - '@sveltejs/kit': 2.53.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) devalue: 5.6.3 memoize-weak: 1.0.2 svelte: 5.53.5 From 4d22c2dbcfdb905def00a9f4d14f81a0b7b2a588 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Sun, 1 Mar 2026 09:48:20 -0800 Subject: [PATCH 08/15] =?UTF-8?q?fix:=20federated=20client=20credentials?= =?UTF-8?q?=20not=20working=20if=20sub=20=E2=89=A0=20client=5Fid=20(#1342)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backend/internal/common/errors.go | 31 +++++++----- .../internal/controller/oidc_controller.go | 11 ++++- backend/internal/dto/oidc_dto.go | 3 +- backend/internal/service/oidc_service.go | 49 ++++--------------- backend/internal/service/oidc_service_test.go | 44 +++++++++++++++++ frontend/messages/cs.json | 1 - frontend/messages/da.json | 1 - frontend/messages/de.json | 1 - frontend/messages/en.json | 2 +- frontend/messages/es.json | 1 - frontend/messages/et.json | 1 - frontend/messages/fi.json | 1 - frontend/messages/fr.json | 1 - frontend/messages/it.json | 1 - frontend/messages/ja.json | 1 - frontend/messages/ko.json | 1 - frontend/messages/nl.json | 1 - frontend/messages/no.json | 1 - frontend/messages/pl.json | 1 - frontend/messages/pt-BR.json | 1 - frontend/messages/ru.json | 1 - frontend/messages/sv.json | 1 - frontend/messages/tr.json | 1 - frontend/messages/uk.json | 1 - frontend/messages/vi.json | 1 - frontend/messages/zh-CN.json | 1 - frontend/messages/zh-TW.json | 1 - tests/specs/oidc.spec.ts | 2 + 28 files changed, 85 insertions(+), 78 deletions(-) diff --git a/backend/internal/common/errors.go b/backend/internal/common/errors.go index b97f2af0..9a0b41b0 100644 --- a/backend/internal/common/errors.go +++ b/backend/internal/common/errors.go @@ -20,7 +20,7 @@ type AlreadyInUseError struct { func (e *AlreadyInUseError) Error() string { return e.Property + " is already in use" } -func (e *AlreadyInUseError) HttpStatusCode() int { return 400 } +func (e *AlreadyInUseError) HttpStatusCode() int { return http.StatusBadRequest } func (e *AlreadyInUseError) Is(target error) bool { // Ignore the field property when checking if an error is of the type AlreadyInUseError @@ -31,26 +31,26 @@ func (e *AlreadyInUseError) Is(target error) bool { type SetupAlreadyCompletedError struct{} func (e *SetupAlreadyCompletedError) Error() string { return "setup already completed" } -func (e *SetupAlreadyCompletedError) HttpStatusCode() int { return 400 } +func (e *SetupAlreadyCompletedError) HttpStatusCode() int { return http.StatusConflict } type TokenInvalidOrExpiredError struct{} func (e *TokenInvalidOrExpiredError) Error() string { return "token is invalid or expired" } -func (e *TokenInvalidOrExpiredError) HttpStatusCode() int { return 400 } +func (e *TokenInvalidOrExpiredError) HttpStatusCode() int { return http.StatusUnauthorized } type DeviceCodeInvalid struct{} func (e *DeviceCodeInvalid) Error() string { return "one time access code must be used on the device it was generated for" } -func (e *DeviceCodeInvalid) HttpStatusCode() int { return 400 } +func (e *DeviceCodeInvalid) HttpStatusCode() int { return http.StatusUnauthorized } type TokenInvalidError struct{} func (e *TokenInvalidError) Error() string { return "Token is invalid" } -func (e *TokenInvalidError) HttpStatusCode() int { return 400 } +func (e *TokenInvalidError) HttpStatusCode() int { return http.StatusUnauthorized } type OidcMissingAuthorizationError struct{} @@ -60,46 +60,51 @@ func (e *OidcMissingAuthorizationError) HttpStatusCode() int { return http.Statu type OidcGrantTypeNotSupportedError struct{} func (e *OidcGrantTypeNotSupportedError) Error() string { return "grant type not supported" } -func (e *OidcGrantTypeNotSupportedError) HttpStatusCode() int { return 400 } +func (e *OidcGrantTypeNotSupportedError) HttpStatusCode() int { return http.StatusBadRequest } type OidcMissingClientCredentialsError struct{} func (e *OidcMissingClientCredentialsError) Error() string { return "client id or secret not provided" } -func (e *OidcMissingClientCredentialsError) HttpStatusCode() int { return 400 } +func (e *OidcMissingClientCredentialsError) HttpStatusCode() int { return http.StatusBadRequest } type OidcClientSecretInvalidError struct{} func (e *OidcClientSecretInvalidError) Error() string { return "invalid client secret" } -func (e *OidcClientSecretInvalidError) HttpStatusCode() int { return 400 } +func (e *OidcClientSecretInvalidError) HttpStatusCode() int { return http.StatusUnauthorized } type OidcClientAssertionInvalidError struct{} func (e *OidcClientAssertionInvalidError) Error() string { return "invalid client assertion" } -func (e *OidcClientAssertionInvalidError) HttpStatusCode() int { return 400 } +func (e *OidcClientAssertionInvalidError) HttpStatusCode() int { return http.StatusUnauthorized } type OidcInvalidAuthorizationCodeError struct{} func (e *OidcInvalidAuthorizationCodeError) Error() string { return "invalid authorization code" } -func (e *OidcInvalidAuthorizationCodeError) HttpStatusCode() int { return 400 } +func (e *OidcInvalidAuthorizationCodeError) HttpStatusCode() int { return http.StatusBadRequest } + +type OidcClientNotFoundError struct{} + +func (e *OidcClientNotFoundError) Error() string { return "client not found" } +func (e *OidcClientNotFoundError) HttpStatusCode() int { return http.StatusNotFound } type OidcMissingCallbackURLError struct{} func (e *OidcMissingCallbackURLError) Error() string { return "unable to detect callback url, it might be necessary for an admin to fix this" } -func (e *OidcMissingCallbackURLError) HttpStatusCode() int { return 400 } +func (e *OidcMissingCallbackURLError) HttpStatusCode() int { return http.StatusBadRequest } type OidcInvalidCallbackURLError struct{} func (e *OidcInvalidCallbackURLError) Error() string { return "invalid callback URL, it might be necessary for an admin to fix this" } -func (e *OidcInvalidCallbackURLError) HttpStatusCode() int { return 400 } +func (e *OidcInvalidCallbackURLError) HttpStatusCode() int { return http.StatusBadRequest } type FileTypeNotSupportedError struct{} func (e *FileTypeNotSupportedError) Error() string { return "file type not supported" } -func (e *FileTypeNotSupportedError) HttpStatusCode() int { return 400 } +func (e *FileTypeNotSupportedError) HttpStatusCode() int { return http.StatusBadRequest } type FileTooLargeError struct { MaxSize string diff --git a/backend/internal/controller/oidc_controller.go b/backend/internal/controller/oidc_controller.go index 5dd9404f..193a6723 100644 --- a/backend/internal/controller/oidc_controller.go +++ b/backend/internal/controller/oidc_controller.go @@ -335,11 +335,13 @@ func (oc *OidcController) introspectTokenHandler(c *gin.Context) { ) creds.ClientID, creds.ClientSecret, ok = utils.OAuthClientBasicAuth(c.Request) if !ok { - // If there's no basic auth, check if we have a bearer token + // If there's no basic auth, check if we have a bearer token (used as client assertion) bearer, ok := utils.BearerAuth(c.Request) if ok { creds.ClientAssertionType = service.ClientAssertionTypeJWTBearer creds.ClientAssertion = bearer + // When using client assertions, client_id can be passed as a form field + creds.ClientID = input.ClientID } } @@ -662,8 +664,13 @@ func (oc *OidcController) updateAllowedUserGroupsHandler(c *gin.Context) { } func (oc *OidcController) deviceAuthorizationHandler(c *gin.Context) { + // Per RFC 8628 (OAuth 2.0 Device Authorization Grant), parameters for the device authorization request MUST be sent in the body of the POST request + // Gin's "ShouldBind" by default reads from the query string too, so we need to reset all query string args before invoking ShouldBind + c.Request.URL.RawQuery = "" + var input dto.OidcDeviceAuthorizationRequestDto - if err := c.ShouldBind(&input); err != nil { + err := c.ShouldBind(&input) + if err != nil { _ = c.Error(err) return } diff --git a/backend/internal/dto/oidc_dto.go b/backend/internal/dto/oidc_dto.go index 08e271bb..e6a186a6 100644 --- a/backend/internal/dto/oidc_dto.go +++ b/backend/internal/dto/oidc_dto.go @@ -98,7 +98,8 @@ type OidcCreateTokensDto struct { } type OidcIntrospectDto struct { - Token string `form:"token" binding:"required"` + Token string `form:"token" binding:"required"` + ClientID string `form:"client_id"` } type OidcUpdateAllowedUserGroupsDto struct { diff --git a/backend/internal/service/oidc_service.go b/backend/internal/service/oidc_service.go index 828e0710..1d04c8d1 100644 --- a/backend/internal/service/oidc_service.go +++ b/backend/internal/service/oidc_service.go @@ -1644,34 +1644,19 @@ func clientAuthCredentialsFromCreateTokensDto(d *dto.OidcCreateTokensDto) Client } func (s *OidcService) verifyClientCredentialsInternal(ctx context.Context, tx *gorm.DB, input ClientAuthCredentials, allowPublicClientsWithoutAuth bool) (client *model.OidcClient, err error) { - isClientAssertion := input.ClientAssertionType == ClientAssertionTypeJWTBearer && input.ClientAssertion != "" - - // Determine the client ID based on the authentication method - var clientID string - switch { - case isClientAssertion: - // Extract client ID from the JWT assertion's 'sub' claim - clientID, err = s.extractClientIDFromAssertion(input.ClientAssertion) - if err != nil { - slog.Error("Failed to extract client ID from assertion", "error", err) - return nil, &common.OidcClientAssertionInvalidError{} - } - case input.ClientID != "": - // Use the provided client ID for other authentication methods - clientID = input.ClientID - default: + if input.ClientID == "" { return nil, &common.OidcMissingClientCredentialsError{} } // Load the OIDC client's configuration err = tx. WithContext(ctx). - First(&client, "id = ?", clientID). + First(&client, "id = ?", input.ClientID). Error - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) && isClientAssertion { - return nil, &common.OidcClientAssertionInvalidError{} - } + if errors.Is(err, gorm.ErrRecordNotFound) { + slog.WarnContext(ctx, "Client not found", slog.String("client", input.ClientID)) + return nil, &common.OidcClientNotFoundError{} + } else if err != nil { return nil, err } @@ -1686,7 +1671,7 @@ func (s *OidcService) verifyClientCredentialsInternal(ctx context.Context, tx *g return client, nil // Next, check if we want to use client assertions from federated identities - case isClientAssertion: + case input.ClientAssertionType == ClientAssertionTypeJWTBearer && input.ClientAssertion != "": err = s.verifyClientAssertionFromFederatedIdentities(ctx, client, input) if err != nil { slog.WarnContext(ctx, "Invalid assertion for client", slog.String("client", client.ID), slog.Any("error", err)) @@ -1783,36 +1768,20 @@ func (s *OidcService) verifyClientAssertionFromFederatedIdentities(ctx context.C // (Note: we don't use jwt.WithIssuer() because that would be redundant) _, err = jwt.Parse(assertion, jwt.WithValidate(true), + jwt.WithAcceptableSkew(clockSkew), jwt.WithKeySet(jwks, jws.WithInferAlgorithmFromKey(true), jws.WithUseDefault(true)), jwt.WithAudience(audience), jwt.WithSubject(subject), ) if err != nil { - return fmt.Errorf("client assertion is not valid: %w", err) + return fmt.Errorf("client assertion could not be verified: %w", err) } // If we're here, the assertion is valid return nil } -// extractClientIDFromAssertion extracts the client_id from the JWT assertion's 'sub' claim -func (s *OidcService) extractClientIDFromAssertion(assertion string) (string, error) { - // Parse the JWT without verification first to get the claims - insecureToken, err := jwt.ParseInsecure([]byte(assertion)) - if err != nil { - return "", fmt.Errorf("failed to parse JWT assertion: %w", err) - } - - // Extract the subject claim which must be the client_id according to RFC 7523 - sub, ok := insecureToken.Subject() - if !ok || sub == "" { - return "", fmt.Errorf("missing or invalid 'sub' claim in JWT assertion") - } - - return sub, nil -} - func (s *OidcService) GetClientPreview(ctx context.Context, clientID string, userID string, scopes []string) (*dto.OidcClientPreviewDto, error) { tx := s.db.Begin() defer func() { diff --git a/backend/internal/service/oidc_service_test.go b/backend/internal/service/oidc_service_test.go index 4dbab3f6..4374a430 100644 --- a/backend/internal/service/oidc_service_test.go +++ b/backend/internal/service/oidc_service_test.go @@ -229,6 +229,12 @@ func TestOidcService_verifyClientCredentialsInternal(t *testing.T) { Subject: federatedClient.ID, JWKS: federatedClientIssuer + "/jwks.json", }, + { + Issuer: "federated-issuer-2", + Audience: federatedClientAudience, + Subject: "my-federated-client", + JWKS: federatedClientIssuer + "/jwks.json", + }, {Issuer: federatedClientIssuerDefaults}, }, }, @@ -461,6 +467,43 @@ func TestOidcService_verifyClientCredentialsInternal(t *testing.T) { // Generate a token input := dto.OidcCreateTokensDto{ + ClientID: federatedClient.ID, + ClientAssertion: string(signedToken), + ClientAssertionType: ClientAssertionTypeJWTBearer, + } + createdToken, err := s.createTokenFromClientCredentials(t.Context(), input) + require.NoError(t, err) + require.NotNil(t, token) + + // Verify the token + claims, err := s.jwtService.VerifyOAuthAccessToken(createdToken.AccessToken) + require.NoError(t, err, "Failed to verify generated token") + + // Check the claims + subject, ok := claims.Subject() + _ = assert.True(t, ok, "User ID not found in token") && + assert.Equal(t, "client-"+federatedClient.ID, subject, "Token subject should match federated client ID with prefix") + audience, ok := claims.Audience() + _ = assert.True(t, ok, "Audience not found in token") && + assert.Equal(t, []string{federatedClient.ID}, audience, "Audience should contain the federated client ID") + }) + + t.Run("Succeeds with valid assertion and custom subject", func(t *testing.T) { + // Create JWT for federated identity + token, err := jwt.NewBuilder(). + Issuer("federated-issuer-2"). + Audience([]string{federatedClientAudience}). + Subject("my-federated-client"). + IssuedAt(time.Now()). + Expiration(time.Now().Add(10 * time.Minute)). + Build() + require.NoError(t, err) + signedToken, err := jwt.Sign(token, jwt.WithKey(jwa.ES256(), privateJWK)) + require.NoError(t, err) + + // Generate a token + input := dto.OidcCreateTokensDto{ + ClientID: federatedClient.ID, ClientAssertion: string(signedToken), ClientAssertionType: ClientAssertionTypeJWTBearer, } @@ -483,6 +526,7 @@ func TestOidcService_verifyClientCredentialsInternal(t *testing.T) { t.Run("Fails with invalid assertion", func(t *testing.T) { input := dto.OidcCreateTokensDto{ + ClientID: confidentialClient.ID, ClientAssertion: "invalid.jwt.token", ClientAssertionType: ClientAssertionTypeJWTBearer, } diff --git a/frontend/messages/cs.json b/frontend/messages/cs.json index 0368fb55..8bf78ad7 100644 --- a/frontend/messages/cs.json +++ b/frontend/messages/cs.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Zadejte kód, který byl zobrazen v předchozím kroku.", "authorize": "Autorizovat", "federated_client_credentials": "Údaje o klientovi ve federaci", - "federated_client_credentials_description": "Pomocí federovaných přihlašovacích údajů klienta můžete ověřit klienty OIDC pomocí JWT tokenů vydaných třetí stranou.", "add_federated_client_credential": "Přidat údaje federovaného klienta", "add_another_federated_client_credential": "Přidat dalšího federovaného klienta", "oidc_allowed_group_count": "Počet povolených skupin", diff --git a/frontend/messages/da.json b/frontend/messages/da.json index 9afdca79..5d2b3336 100644 --- a/frontend/messages/da.json +++ b/frontend/messages/da.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Indtast koden, der blev vist i det forrige trin.", "authorize": "Godkend", "federated_client_credentials": "Federated klientlegitimationsoplysninger", - "federated_client_credentials_description": "Ved hjælp af federated klientlegitimationsoplysninger kan du godkende OIDC-klienter med JWT-tokens udstedt af tredjepartsudbydere.", "add_federated_client_credential": "Tilføj federated klientlegitimation", "add_another_federated_client_credential": "Tilføj endnu en federated klientlegitimation", "oidc_allowed_group_count": "Tilladt antal grupper", diff --git a/frontend/messages/de.json b/frontend/messages/de.json index bffc38c6..79885fcc 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Gib den Code ein, der im vorherigen Schritt angezeigt wurde.", "authorize": "Autorisieren", "federated_client_credentials": "Federated Client Credentials", - "federated_client_credentials_description": "Mit Hilfe von Verbund-Client-Anmeldeinformationen kannst du OIDC-Clients mit JWT-Tokens authentifizieren, die von Drittanbietern ausgestellt wurden.", "add_federated_client_credential": "Föderierte Client-Anmeldeinfos hinzufügen", "add_another_federated_client_credential": "Weitere Anmeldeinformationen für einen Verbundclient hinzufügen", "oidc_allowed_group_count": "Erlaubte Gruppenanzahl", diff --git a/frontend/messages/en.json b/frontend/messages/en.json index e328330e..e89938b8 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -365,7 +365,7 @@ "enter_code_displayed_in_previous_step": "Enter the code that was displayed in the previous step.", "authorize": "Authorize", "federated_client_credentials": "Federated Client Credentials", - "federated_client_credentials_description": "Using federated client credentials, you can authenticate OIDC clients using JWT tokens issued by third-party authorities.", + "federated_client_credentials_description": "Federated client credentials allow authenticating OIDC clients without managing long-lived secrets. They leverage JWT tokens issued by third-party authorities for client assertions, e.g. workload identity tokens.", "add_federated_client_credential": "Add Federated Client Credential", "add_another_federated_client_credential": "Add another federated client credential", "oidc_allowed_group_count": "Allowed Group Count", diff --git a/frontend/messages/es.json b/frontend/messages/es.json index 9c1cce93..ac6fdd78 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Introduce el código que se mostró en el paso anterior.", "authorize": "Autorizar", "federated_client_credentials": "Credenciales de cliente federadas", - "federated_client_credentials_description": "Mediante credenciales de cliente federadas, puedes autenticar clientes OIDC utilizando tokens JWT emitidos por autoridades de terceros.", "add_federated_client_credential": "Añadir credenciales de cliente federado", "add_another_federated_client_credential": "Añadir otra credencial de cliente federado", "oidc_allowed_group_count": "Recuento de grupos permitidos", diff --git a/frontend/messages/et.json b/frontend/messages/et.json index e328330e..d3aedc7c 100644 --- a/frontend/messages/et.json +++ b/frontend/messages/et.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Enter the code that was displayed in the previous step.", "authorize": "Authorize", "federated_client_credentials": "Federated Client Credentials", - "federated_client_credentials_description": "Using federated client credentials, you can authenticate OIDC clients using JWT tokens issued by third-party authorities.", "add_federated_client_credential": "Add Federated Client Credential", "add_another_federated_client_credential": "Add another federated client credential", "oidc_allowed_group_count": "Allowed Group Count", diff --git a/frontend/messages/fi.json b/frontend/messages/fi.json index b8ba5899..59d1f041 100644 --- a/frontend/messages/fi.json +++ b/frontend/messages/fi.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Syötä edellisessä vaiheessa näkynyt koodi.", "authorize": "Salli", "federated_client_credentials": "Federoidut asiakastunnukset", - "federated_client_credentials_description": "Yhdistettyjen asiakastunnistetietojen avulla voit todentaa OIDC-asiakkaat kolmannen osapuolen myöntämillä JWT-tunnuksilla.", "add_federated_client_credential": "Lisää federoitu asiakastunnus", "add_another_federated_client_credential": "Lisää toinen federoitu asiakastunnus", "oidc_allowed_group_count": "Sallittujen ryhmien määrä", diff --git a/frontend/messages/fr.json b/frontend/messages/fr.json index aa9f71b2..9ff785d4 100644 --- a/frontend/messages/fr.json +++ b/frontend/messages/fr.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Entrez le code affiché à l'étape précédente.", "authorize": "Autoriser", "federated_client_credentials": "Identifiants client fédérés", - "federated_client_credentials_description": "Avec des identifiants clients fédérés, vous pouvez authentifier des clients OIDC avec des tokens JWT émis par des autorités tierces.", "add_federated_client_credential": "Ajouter un identifiant client fédéré", "add_another_federated_client_credential": "Ajouter un autre identifiant client fédéré", "oidc_allowed_group_count": "Nombre de groupes autorisés", diff --git a/frontend/messages/it.json b/frontend/messages/it.json index 890ad08c..e4337b43 100644 --- a/frontend/messages/it.json +++ b/frontend/messages/it.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Inserisci il codice visualizzato nel passaggio precedente.", "authorize": "Autorizza", "federated_client_credentials": "Identità Federate", - "federated_client_credentials_description": "Utilizzando identità federate, è possibile autenticare i client OIDC utilizzando i token JWT emessi da autorità di terze parti.", "add_federated_client_credential": "Aggiungi Identità Federata", "add_another_federated_client_credential": "Aggiungi un'altra identità federata", "oidc_allowed_group_count": "Numero Gruppi Consentiti", diff --git a/frontend/messages/ja.json b/frontend/messages/ja.json index fa8c024c..2f480471 100644 --- a/frontend/messages/ja.json +++ b/frontend/messages/ja.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "前のステップで表示されたコードを入力してください。", "authorize": "Authorize", "federated_client_credentials": "連携クライアントの資格情報", - "federated_client_credentials_description": "Using federated client credentials, you can authenticate OIDC clients using JWT tokens issued by third-party authorities.", "add_federated_client_credential": "Add Federated Client Credential", "add_another_federated_client_credential": "Add another federated client credential", "oidc_allowed_group_count": "許可されたグループ数", diff --git a/frontend/messages/ko.json b/frontend/messages/ko.json index 0de41375..6ec07029 100644 --- a/frontend/messages/ko.json +++ b/frontend/messages/ko.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "이전 단계에 표시된 코드를 입력하세요.", "authorize": "승인", "federated_client_credentials": "연동 클라이언트 자격 증명", - "federated_client_credentials_description": "연동 클라이언트 자격 증명을 이용하여, OIDC 클라이언트를 제3자 인증 기관에서 발급한 JWT 토큰을 이용해 인증할 수 있습니다.", "add_federated_client_credential": "연동 클라이언트 자격 증명 추가", "add_another_federated_client_credential": "다른 연동 클라이언트 자격 증명 추가", "oidc_allowed_group_count": "허용된 그룹 수", diff --git a/frontend/messages/nl.json b/frontend/messages/nl.json index 19cb99d3..958da44c 100644 --- a/frontend/messages/nl.json +++ b/frontend/messages/nl.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Voer de code in die in de vorige stap werd getoond.", "authorize": "Autoriseren", "federated_client_credentials": "Federatieve clientreferenties", - "federated_client_credentials_description": "Met federatieve clientreferenties kun je OIDC-clients verifiëren met JWT-tokens die zijn uitgegeven door andere instanties.", "add_federated_client_credential": "Federatieve clientreferenties toevoegen", "add_another_federated_client_credential": "Voeg nog een federatieve clientreferentie toe", "oidc_allowed_group_count": "Aantal groepen met toegang", diff --git a/frontend/messages/no.json b/frontend/messages/no.json index 1c131f38..320291e5 100644 --- a/frontend/messages/no.json +++ b/frontend/messages/no.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Enter the code that was displayed in the previous step.", "authorize": "Authorize", "federated_client_credentials": "Federated Client Credentials", - "federated_client_credentials_description": "Using federated client credentials, you can authenticate OIDC clients using JWT tokens issued by third-party authorities.", "add_federated_client_credential": "Add Federated Client Credential", "add_another_federated_client_credential": "Add another federated client credential", "oidc_allowed_group_count": "Allowed Group Count", diff --git a/frontend/messages/pl.json b/frontend/messages/pl.json index ee6a0148..be63509c 100644 --- a/frontend/messages/pl.json +++ b/frontend/messages/pl.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Wprowadź kod wyświetlony w poprzednim kroku.", "authorize": "Autoryzuj", "federated_client_credentials": "Połączone poświadczenia klienta", - "federated_client_credentials_description": "Korzystając z połączonych poświadczeń klienta, możecie uwierzytelnić klientów OIDC za pomocą tokenów JWT wydanych przez zewnętrzne organy.", "add_federated_client_credential": "Dodaj poświadczenia klienta federacyjnego", "add_another_federated_client_credential": "Dodaj kolejne poświadczenia klienta federacyjnego", "oidc_allowed_group_count": "Dopuszczalna liczba grup", diff --git a/frontend/messages/pt-BR.json b/frontend/messages/pt-BR.json index 1b94665b..3d28baec 100644 --- a/frontend/messages/pt-BR.json +++ b/frontend/messages/pt-BR.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Digite o código que apareceu na etapa anterior.", "authorize": "Autorizar", "federated_client_credentials": "Credenciais de Cliente Federadas", - "federated_client_credentials_description": "Ao utilizar credenciais de cliente federadas, é possível autenticar clientes OIDC usando tokens JWT emitidos por autoridades de terceiros.", "add_federated_client_credential": "Adicionar credencial de cliente federado", "add_another_federated_client_credential": "Adicionar outra credencial de cliente federado", "oidc_allowed_group_count": "Total de grupos permitidos", diff --git a/frontend/messages/ru.json b/frontend/messages/ru.json index 3f2239fa..7774a94e 100644 --- a/frontend/messages/ru.json +++ b/frontend/messages/ru.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Введите код, который был отображен на предыдущем шаге.", "authorize": "Авторизовать", "federated_client_credentials": "Федеративные учетные данные клиента", - "federated_client_credentials_description": "Используя федеративные учетные данные клиента, вы можете аутентифицировать клиентов OIDC с помощью токенов JWT, выпущенных сторонними поставщиками удостоверений.", "add_federated_client_credential": "Добавить федеративные учетные данные клиента", "add_another_federated_client_credential": "Добавить другие федеративные учетные данные клиента", "oidc_allowed_group_count": "Число разрешенных групп", diff --git a/frontend/messages/sv.json b/frontend/messages/sv.json index c45fdf43..2a46b86f 100644 --- a/frontend/messages/sv.json +++ b/frontend/messages/sv.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Ange koden som visades i föregående steg.", "authorize": "Godkänn", "federated_client_credentials": "Federerade klientuppgifter", - "federated_client_credentials_description": "Med hjälp av federerade klientuppgifter kan du autentisera OIDC-klienter med JWT-tokens som utfärdats av externa auktoriteter.", "add_federated_client_credential": "Lägg till federerad klientuppgift", "add_another_federated_client_credential": "Lägg till ytterligare en federerad klientuppgift", "oidc_allowed_group_count": "Tillåtet antal grupper", diff --git a/frontend/messages/tr.json b/frontend/messages/tr.json index dc7b5985..608939db 100644 --- a/frontend/messages/tr.json +++ b/frontend/messages/tr.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Önceki adımda görüntülenen kodu girin.", "authorize": "Yetkilendir", "federated_client_credentials": "Birleştirilmiş İstemci Kimlik Bilgileri", - "federated_client_credentials_description": "Birleşik istemci kimlik bilgilerini kullanarak, üçüncü taraf otoriteleri tarafından verilen JWT token'ları kullanarak OIDC istemcilerinin kimliklerini doğrulayabilirsiniz.", "add_federated_client_credential": "Birleştirilmiş İstemci Kimlik Bilgisi Ekle", "add_another_federated_client_credential": "Başka bir birleştirilmiş istemci kimlik bilgisi ekle", "oidc_allowed_group_count": "İzin Verilen Grup Sayısı", diff --git a/frontend/messages/uk.json b/frontend/messages/uk.json index c4e7955c..06b548fe 100644 --- a/frontend/messages/uk.json +++ b/frontend/messages/uk.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Введіть код, який було показано на попередньому кроці.", "authorize": "Авторизувати", "federated_client_credentials": "Федеративні облікові дані клієнта", - "federated_client_credentials_description": "За допомогою федеративних облікових даних клієнта ви можете автентифікувати клієнтів OIDC за допомогою токенів JWT, виданих третіми сторонами.", "add_federated_client_credential": "Додати федеративний обліковий запис клієнта", "add_another_federated_client_credential": "Додати ще один федеративний обліковий запис клієнта", "oidc_allowed_group_count": "Кількість дозволених груп", diff --git a/frontend/messages/vi.json b/frontend/messages/vi.json index 611a887c..8d861e5a 100644 --- a/frontend/messages/vi.json +++ b/frontend/messages/vi.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "Nhập mã đã hiển thị ở bước trước.", "authorize": "Cho phép", "federated_client_credentials": "Thông Tin Xác Thực Của Federated Clients", - "federated_client_credentials_description": "Sử dụng thông tin xác thực của federated client, bạn có thể xác thực các client OIDC bằng cách sử dụng token JWT được cấp bởi các bên thứ ba.", "add_federated_client_credential": "Thêm thông tin xác thực cho federated clients", "add_another_federated_client_credential": "Thêm một thông tin xác thực cho federated clients khác", "oidc_allowed_group_count": "Số lượng nhóm được phép", diff --git a/frontend/messages/zh-CN.json b/frontend/messages/zh-CN.json index 5b051731..99b9a987 100644 --- a/frontend/messages/zh-CN.json +++ b/frontend/messages/zh-CN.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "输入在上一步中显示的代码", "authorize": "授权", "federated_client_credentials": "联合身份", - "federated_client_credentials_description": "您可以使用联合身份,通过第三方授权机构签发的 JWT 令牌,对 OIDC 客户端进行认证。", "add_federated_client_credential": "添加联合身份", "add_another_federated_client_credential": "再添加一个联合身份", "oidc_allowed_group_count": "允许的群组数量", diff --git a/frontend/messages/zh-TW.json b/frontend/messages/zh-TW.json index 58c57e5a..eac55e18 100644 --- a/frontend/messages/zh-TW.json +++ b/frontend/messages/zh-TW.json @@ -365,7 +365,6 @@ "enter_code_displayed_in_previous_step": "請輸入上一步顯示的代碼。", "authorize": "授權", "federated_client_credentials": "聯邦身分", - "federated_client_credentials_description": "使用聯邦身分,您可以透過由第三方授權機構簽發的 JWT 令牌來驗證 OIDC 客戶端。", "add_federated_client_credential": "增加聯邦身分", "add_another_federated_client_credential": "新增另一組聯邦身分", "oidc_allowed_group_count": "允許的群組數量", diff --git a/tests/specs/oidc.spec.ts b/tests/specs/oidc.spec.ts index 9f16d8fc..2b96709f 100644 --- a/tests/specs/oidc.spec.ts +++ b/tests/specs/oidc.spec.ts @@ -332,6 +332,7 @@ test.describe('Introspection endpoint', () => { Authorization: 'Bearer ' + clientAssertion }, form: { + client_id: oidcClients.federated.id, token: validAccessToken } }); @@ -374,6 +375,7 @@ test.describe('Introspection endpoint', () => { Authorization: 'Bearer ' + clientAssertion }, form: { + client_id: oidcClients.federated.id, token: validAccessToken } }); From 6159e0bf96623a160043a5b907aa0fcc4eb8466e Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Sun, 1 Mar 2026 13:04:38 -0800 Subject: [PATCH 09/15] perf: frontend performance optimizations (#1344) --- backend/frontend/frontend_included.go | 147 +++++++++++++++--- frontend/.prettierignore | 3 +- frontend/package.json | 123 +++++++-------- frontend/vite.config.ts | 59 +++++--- pnpm-lock.yaml | 210 +++++++++++++++----------- pnpm-workspace.yaml | 18 ++- 6 files changed, 370 insertions(+), 190 deletions(-) diff --git a/backend/frontend/frontend_included.go b/backend/frontend/frontend_included.go index ac1f6b2e..6486671b 100644 --- a/backend/frontend/frontend_included.go +++ b/backend/frontend/frontend_included.go @@ -8,8 +8,10 @@ import ( "fmt" "io" "io/fs" + "mime" "net/http" "os" + "path" "strings" "time" @@ -58,9 +60,16 @@ func RegisterFrontend(router *gin.Engine, rateLimitMiddleware gin.HandlerFunc) e return fmt.Errorf("failed to create sub FS: %w", err) } - cacheMaxAge := time.Hour * 24 - fileServer := NewFileServerWithCaching(http.FS(distFS), int(cacheMaxAge.Seconds())) + // Load a map of all files to see which ones are available pre-compressed + preCompressed, err := listPreCompressedAssets(distFS) + if err != nil { + return fmt.Errorf("failed to index pre-compressed frontend assets: %w", err) + } + // Init the file server + fileServer := NewFileServerWithCaching(http.FS(distFS), preCompressed) + + // Handler for Gin handler := func(c *gin.Context) { path := strings.TrimPrefix(c.Request.URL.Path, "/") @@ -108,34 +117,138 @@ func RegisterFrontend(router *gin.Engine, rateLimitMiddleware gin.HandlerFunc) e type FileServerWithCaching struct { root http.FileSystem lastModified time.Time - cacheMaxAge int lastModifiedHeaderValue string - cacheControlHeaderValue string + preCompressed preCompressedMap } -func NewFileServerWithCaching(root http.FileSystem, maxAge int) *FileServerWithCaching { +func NewFileServerWithCaching(root http.FileSystem, preCompressed preCompressedMap) *FileServerWithCaching { return &FileServerWithCaching{ root: root, lastModified: time.Now(), - cacheMaxAge: maxAge, lastModifiedHeaderValue: time.Now().UTC().Format(http.TimeFormat), - cacheControlHeaderValue: fmt.Sprintf("public, max-age=%d", maxAge), + preCompressed: preCompressed, } } func (f *FileServerWithCaching) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // Check if the client has a cached version - if ifModifiedSince := r.Header.Get("If-Modified-Since"); ifModifiedSince != "" { - ifModifiedSinceTime, err := time.Parse(http.TimeFormat, ifModifiedSince) - if err == nil && f.lastModified.Before(ifModifiedSinceTime.Add(1*time.Second)) { - // Client's cached version is up to date - w.WriteHeader(http.StatusNotModified) - return + // First, set cache headers + // Check if the request is for an immutable asset + if isImmutableAsset(r) { + // Set the cache control header as immutable with a long expiration + w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") + } else { + // Check if the client has a cached version + ifModifiedSince := r.Header.Get("If-Modified-Since") + if ifModifiedSince != "" { + ifModifiedSinceTime, err := time.Parse(http.TimeFormat, ifModifiedSince) + if err == nil && f.lastModified.Before(ifModifiedSinceTime.Add(1*time.Second)) { + // Client's cached version is up to date + w.WriteHeader(http.StatusNotModified) + return + } + } + + // Cache other assets for up to 24 hours, but set Last-Modified too + w.Header().Set("Last-Modified", f.lastModifiedHeaderValue) + w.Header().Set("Cache-Control", "public, max-age=86400") + } + + // Check if the asset is available pre-compressed + _, ok := f.preCompressed[r.URL.Path] + if ok { + // Add a "Vary" with "Accept-Encoding" so CDNs are aware that content is pre-compressed + w.Header().Add("Vary", "Accept-Encoding") + + // Select the encoding if any + ext, ce := f.selectEncoding(r) + if ext != "" { + // Set the content type explicitly before changing the path + ct := mime.TypeByExtension(path.Ext(r.URL.Path)) + if ct != "" { + w.Header().Set("Content-Type", ct) + } + + // Make the serve return the encoded content + w.Header().Set("Content-Encoding", ce) + r.URL.Path += "." + ext } } - w.Header().Set("Last-Modified", f.lastModifiedHeaderValue) - w.Header().Set("Cache-Control", f.cacheControlHeaderValue) - http.FileServer(f.root).ServeHTTP(w, r) } + +func (f *FileServerWithCaching) selectEncoding(r *http.Request) (ext string, contentEnc string) { + available, ok := f.preCompressed[r.URL.Path] + if !ok { + return "", "" + } + + // Check if the client accepts compressed files + acceptEncoding := strings.TrimSpace(strings.ToLower(r.Header.Get("Accept-Encoding"))) + if acceptEncoding == "" { + return "", "" + } + + // Prefer brotli over gzip when both are accepted. + if available.br && (acceptEncoding == "*" || acceptEncoding == "br" || strings.Contains(acceptEncoding, "br")) { + return "br", "br" + } + if available.gz && (acceptEncoding == "gzip" || strings.Contains(acceptEncoding, "gzip")) { + return "gz", "gzip" + } + + return "", "" +} + +func isImmutableAsset(r *http.Request) bool { + switch { + // Fonts + case strings.HasPrefix(r.URL.Path, "/fonts/"): + return true + + // Compiled SvelteKit assets + case strings.HasPrefix(r.URL.Path, "/_app/immutable/"): + return true + + default: + return false + } +} + +type preCompressedMap map[string]struct { + br bool + gz bool +} + +func listPreCompressedAssets(distFS fs.FS) (preCompressedMap, error) { + preCompressed := make(preCompressedMap, 0) + err := fs.WalkDir(distFS, ".", func(path string, d fs.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + + if d.IsDir() { + return nil + } + + switch { + case strings.HasSuffix(path, ".br"): + originalPath := "/" + strings.TrimSuffix(path, ".br") + entry := preCompressed[originalPath] + entry.br = true + preCompressed[originalPath] = entry + case strings.HasSuffix(path, ".gz"): + originalPath := "/" + strings.TrimSuffix(path, ".gz") + entry := preCompressed[originalPath] + entry.gz = true + preCompressed[originalPath] = entry + } + + return nil + }) + if err != nil { + return nil, err + } + + return preCompressed, nil +} diff --git a/frontend/.prettierignore b/frontend/.prettierignore index 9ad56c28..e8d6b617 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -5,4 +5,5 @@ yarn.lock # Compiled files .svelte-kit/ -build/ \ No newline at end of file +build/ +src/lib/paraglide/messages \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 383c0e54..4699106c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,63 +1,64 @@ { - "name": "pocket-id-frontend", - "version": "2.3.0", - "private": true, - "type": "module", - "scripts": { - "preinstall": "npx only-allow pnpm", - "dev": "vite dev --port 3000", - "build": "vite build", - "preview": "vite preview --port 3000", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "prettier --check . && eslint .", - "format": "prettier --write ." - }, - "dependencies": { - "@simplewebauthn/browser": "^13.2.2", - "@tailwindcss/vite": "^4.2.0", - "axios": "^1.13.5", - "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "jose": "^6.1.3", - "qrcode": "^1.5.4", - "runed": "^0.37.1", - "sveltekit-superforms": "^2.30.0", - "tailwind-merge": "^3.5.0", - "zod": "^4.3.6" - }, - "devDependencies": { - "@inlang/paraglide-js": "^2.12.0", - "@inlang/plugin-m-function-matcher": "^2.2.1", - "@inlang/plugin-message-format": "^4.3.0", - "@internationalized/date": "^3.11.0", - "@lucide/svelte": "^0.559.0", - "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.53.3", - "@sveltejs/vite-plugin-svelte": "^6.2.4", - "@types/eslint": "^9.6.1", - "@types/node": "^24.10.13", - "@types/qrcode": "^1.5.6", - "bits-ui": "^2.16.2", - "eslint": "^9.39.3", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-svelte": "^3.15.0", - "formsnap": "^2.0.1", - "globals": "^16.5.0", - "mode-watcher": "^1.1.0", - "prettier": "^3.8.1", - "prettier-plugin-svelte": "^3.5.0", - "prettier-plugin-tailwindcss": "^0.7.2", - "rollup": "^4.59.0", - "svelte": "^5.53.5", - "svelte-check": "^4.4.3", - "svelte-sonner": "^1.0.7", - "tailwind-variants": "^3.2.2", - "tailwindcss": "^4.2.0", - "tslib": "^2.8.1", - "tw-animate-css": "^1.4.0", - "typescript": "^5.9.3", - "typescript-eslint": "^8.56.0", - "vite": "^7.3.1" - } + "name": "pocket-id-frontend", + "version": "2.3.0", + "private": true, + "type": "module", + "scripts": { + "preinstall": "npx only-allow pnpm", + "dev": "vite dev --port 3000", + "build": "vite build", + "preview": "vite preview --port 3000", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --check . && eslint .", + "format": "prettier --write ." + }, + "dependencies": { + "@simplewebauthn/browser": "^13.2.2", + "@tailwindcss/vite": "^4.2.0", + "axios": "^1.13.5", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "jose": "^6.1.3", + "qrcode": "^1.5.4", + "runed": "^0.37.1", + "sveltekit-superforms": "^2.30.0", + "tailwind-merge": "^3.5.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "@inlang/paraglide-js": "^2.12.0", + "@inlang/plugin-m-function-matcher": "^2.2.1", + "@inlang/plugin-message-format": "^4.3.0", + "@internationalized/date": "^3.11.0", + "@lucide/svelte": "^0.559.0", + "@sveltejs/adapter-static": "^3.0.10", + "@sveltejs/kit": "^2.53.4", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@types/eslint": "^9.6.1", + "@types/node": "^24.10.13", + "@types/qrcode": "^1.5.6", + "bits-ui": "^2.16.2", + "eslint": "^9.39.3", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.15.0", + "formsnap": "^2.0.1", + "globals": "^16.5.0", + "mode-watcher": "^1.1.0", + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.5.0", + "prettier-plugin-tailwindcss": "^0.7.2", + "rollup": "^4.59.0", + "svelte": "^5.53.6", + "svelte-check": "^4.4.3", + "svelte-sonner": "^1.0.7", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.2.0", + "tslib": "^2.8.1", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.56.0", + "vite": "^7.3.1", + "vite-plugin-compression": "^0.5.1" + } } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 38e2b69b..7c0f0dee 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -2,28 +2,47 @@ import { paraglideVitePlugin } from '@inlang/paraglide-js'; import { sveltekit } from '@sveltejs/kit/vite'; import tailwindcss from '@tailwindcss/vite'; import { defineConfig } from 'vite'; +import viteCompression from 'vite-plugin-compression'; -export default defineConfig({ - plugins: [ - sveltekit(), - tailwindcss(), - paraglideVitePlugin({ - project: './project.inlang', - outdir: './src/lib/paraglide', - cookieName: 'locale', - strategy: ['cookie', 'preferredLanguage', 'baseLocale'] - }) - ], +export default defineConfig((mode) => { + return { + plugins: [ + sveltekit(), + tailwindcss(), + paraglideVitePlugin({ + project: './project.inlang', + outdir: './src/lib/paraglide', + cookieName: 'locale', + strategy: ['cookie', 'preferredLanguage', 'baseLocale'] + }), - server: { - host: process.env.HOST, - proxy: { - '/api': { - target: process.env.DEVELOPMENT_BACKEND_URL || 'http://localhost:1411' - }, - '/.well-known': { - target: process.env.DEVELOPMENT_BACKEND_URL || 'http://localhost:1411' + // Create gzip-compressed files + viteCompression({ + disable: mode.isPreview, + algorithm: 'gzip', + ext: '.gz', + filter: /\.(js|mjs|json|css)$/i + }), + + // Create brotli-compressed files + viteCompression({ + disable: mode.isPreview, + algorithm: 'brotliCompress', + ext: '.br', + filter: /\.(js|mjs|json|css)$/i + }) + ], + + server: { + host: process.env.HOST, + proxy: { + '/api': { + target: process.env.DEVELOPMENT_BACKEND_URL || 'http://localhost:1411' + }, + '/.well-known': { + target: process.env.DEVELOPMENT_BACKEND_URL || 'http://localhost:1411' + } } } - } + }; }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6e490f0..9d04a705 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,10 +77,10 @@ importers: version: 1.5.4 runed: specifier: ^0.37.1 - version: 0.37.1(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(zod@4.3.6) + version: 0.37.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(zod@4.3.6) sveltekit-superforms: specifier: ^2.30.0 - version: 2.30.0(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3) + version: 2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3) tailwind-merge: specifier: ^3.5.0 version: 3.5.0 @@ -102,16 +102,16 @@ importers: version: 3.11.0 '@lucide/svelte': specifier: ^0.559.0 - version: 0.559.0(svelte@5.53.5) + version: 0.559.0(svelte@5.53.6) '@sveltejs/adapter-static': specifier: ^3.0.10 - version: 3.0.10(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))) + version: 3.0.10(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))) '@sveltejs/kit': - specifier: ^2.53.3 - version: 2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + specifier: ^2.53.4 + version: 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.4 - version: 6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + version: 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@types/eslint': specifier: ^9.6.1 version: 9.6.1 @@ -123,7 +123,7 @@ importers: version: 1.5.6 bits-ui: specifier: ^2.16.2 - version: 2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) + version: 2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6) eslint: specifier: ^9.39.3 version: 9.39.3(jiti@2.6.1) @@ -132,37 +132,37 @@ importers: version: 10.1.8(eslint@9.39.3(jiti@2.6.1)) eslint-plugin-svelte: specifier: ^3.15.0 - version: 3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.5) + version: 3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.6) formsnap: specifier: ^2.0.1 - version: 2.0.1(svelte@5.53.5)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3)) + version: 2.0.1(svelte@5.53.6)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3)) globals: specifier: ^16.5.0 version: 16.5.0 mode-watcher: specifier: ^1.1.0 - version: 1.1.0(svelte@5.53.5) + version: 1.1.0(svelte@5.53.6) prettier: specifier: ^3.8.1 version: 3.8.1 prettier-plugin-svelte: specifier: ^3.5.0 - version: 3.5.0(prettier@3.8.1)(svelte@5.53.5) + version: 3.5.0(prettier@3.8.1)(svelte@5.53.6) prettier-plugin-tailwindcss: specifier: ^0.7.2 - version: 0.7.2(prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.5))(prettier@3.8.1) + version: 0.7.2(prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.6))(prettier@3.8.1) rollup: specifier: ^4.59.0 version: 4.59.0 svelte: - specifier: ^5.53.5 - version: 5.53.5 + specifier: ^5.53.6 + version: 5.53.6 svelte-check: specifier: ^4.4.3 - version: 4.4.3(picomatch@4.0.3)(svelte@5.53.5)(typescript@5.9.3) + version: 4.4.3(picomatch@4.0.3)(svelte@5.53.6)(typescript@5.9.3) svelte-sonner: specifier: ^1.0.7 - version: 1.0.7(svelte@5.53.5) + version: 1.0.7(svelte@5.53.6) tailwind-variants: specifier: ^3.2.2 version: 3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.0) @@ -184,6 +184,9 @@ importers: vite: specifier: ^7.3.1 version: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + vite-plugin-compression: + specifier: ^0.5.1 + version: 0.5.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) tests: dependencies: @@ -1408,8 +1411,8 @@ packages: peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/kit@2.53.3': - resolution: {integrity: sha512-tshOeBUid2v5LAblUpatIdFm5Cyykbw2EiKWOunAAX0A/oJaR7DOdC9wLR5Qqh9zUf3QUISA2m9A3suBdQSYQg==} + '@sveltejs/kit@2.53.4': + resolution: {integrity: sha512-iAIPEahFgDJJyvz8g0jP08KvqnM6JvdW8YfsygZ+pMeMvyM2zssWMltcsotETvjSZ82G3VlitgDtBIvpQSZrTA==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -2199,6 +2202,10 @@ packages: svelte: ^5.0.0 sveltekit-superforms: ^2.19.0 + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2383,6 +2390,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -3109,8 +3119,8 @@ packages: peerDependencies: svelte: ^5.0.0 - svelte@5.53.5: - resolution: {integrity: sha512-YkqERnF05g8KLdDZwZrF8/i1eSbj6Eoat8Jjr2IfruZz9StLuBqo8sfCSzjosNKd+ZrQ8DkKZDjpO5y3ht1Pow==} + svelte@5.53.6: + resolution: {integrity: sha512-lP5DGF3oDDI9fhHcSpaBiJEkFLuS16h92DhM1L5K1lFm0WjOmUh1i2sNkBBk8rkxJRpob0dBE75jRfUzGZUOGA==} engines: {node: '>=18'} sveltekit-superforms@2.30.0: @@ -3236,6 +3246,10 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} @@ -3273,6 +3287,11 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite-plugin-compression@0.5.1: + resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==} + peerDependencies: + vite: '>=2.0.0' + vite@7.3.1: resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3951,9 +3970,9 @@ snapshots: '@lix-js/server-protocol-schema@0.1.1': {} - '@lucide/svelte@0.559.0(svelte@5.53.5)': + '@lucide/svelte@0.559.0(svelte@5.53.6)': dependencies: - svelte: 5.53.5 + svelte: 5.53.6 '@next/env@16.1.6': {} @@ -4232,15 +4251,15 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))': dependencies: - '@sveltejs/kit': 2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) - '@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': + '@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 1.1.1 @@ -4251,25 +4270,25 @@ snapshots: mrmime: 2.0.1 set-cookie-parser: 3.0.1 sirv: 3.0.2 - svelte: 5.53.5 + svelte: 5.53.6 vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) optionalDependencies: typescript: 5.9.3 - '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) obug: 2.1.1 - svelte: 5.53.5 + svelte: 5.53.6 vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) - '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 - svelte: 5.53.5 + svelte: 5.53.6 vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) vitefu: 1.1.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) @@ -4604,15 +4623,15 @@ snapshots: baseline-browser-mapping@2.9.19: {} - bits-ui@2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5): + bits-ui@2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6): dependencies: '@floating-ui/core': 1.7.4 '@floating-ui/dom': 1.7.5 '@internationalized/date': 3.11.0 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) - svelte: 5.53.5 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) + runed: 0.35.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6) + svelte: 5.53.6 + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6) tabbable: 6.4.0 transitivePeerDependencies: - '@sveltejs/kit' @@ -4956,7 +4975,7 @@ snapshots: dependencies: eslint: 9.39.3(jiti@2.6.1) - eslint-plugin-svelte@3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.5): + eslint-plugin-svelte@3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.6): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) '@jridgewell/sourcemap-codec': 1.5.5 @@ -4968,9 +4987,9 @@ snapshots: postcss-load-config: 3.1.4(postcss@8.5.6) postcss-safe-parser: 7.0.1(postcss@8.5.6) semver: 7.7.4 - svelte-eslint-parser: 1.4.1(svelte@5.53.5) + svelte-eslint-parser: 1.4.1(svelte@5.53.6) optionalDependencies: - svelte: 5.53.5 + svelte: 5.53.6 transitivePeerDependencies: - ts-node @@ -5104,11 +5123,17 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 - formsnap@2.0.1(svelte@5.53.5)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3)): + formsnap@2.0.1(svelte@5.53.6)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3)): dependencies: - svelte: 5.53.5 - svelte-toolbelt: 0.5.0(svelte@5.53.5) - sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3) + svelte: 5.53.6 + svelte-toolbelt: 0.5.0(svelte@5.53.6) + sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3) + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 fsevents@2.3.2: optional: true @@ -5267,6 +5292,12 @@ snapshots: json5@2.2.3: {} + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -5406,11 +5437,11 @@ snapshots: minipass@7.1.2: {} - mode-watcher@1.1.0(svelte@5.53.5): + mode-watcher@1.1.0(svelte@5.53.6): dependencies: - runed: 0.25.0(svelte@5.53.5) - svelte: 5.53.5 - svelte-toolbelt: 0.7.1(svelte@5.53.5) + runed: 0.25.0(svelte@5.53.6) + svelte: 5.53.6 + svelte-toolbelt: 0.7.1(svelte@5.53.6) mri@1.2.0: {} @@ -5585,16 +5616,16 @@ snapshots: prelude-ls@1.2.1: {} - prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.5): + prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.6): dependencies: prettier: 3.8.1 - svelte: 5.53.5 + svelte: 5.53.6 - prettier-plugin-tailwindcss@0.7.2(prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.5))(prettier@3.8.1): + prettier-plugin-tailwindcss@0.7.2(prettier-plugin-svelte@3.5.0(prettier@3.8.1)(svelte@5.53.6))(prettier@3.8.1): dependencies: prettier: 3.8.1 optionalDependencies: - prettier-plugin-svelte: 3.5.0(prettier@3.8.1)(svelte@5.53.5) + prettier-plugin-svelte: 3.5.0(prettier@3.8.1)(svelte@5.53.6) prettier@3.7.4: {} @@ -5702,38 +5733,38 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 - runed@0.23.4(svelte@5.53.5): + runed@0.23.4(svelte@5.53.6): dependencies: esm-env: 1.2.2 - svelte: 5.53.5 + svelte: 5.53.6 - runed@0.25.0(svelte@5.53.5): + runed@0.25.0(svelte@5.53.6): dependencies: esm-env: 1.2.2 - svelte: 5.53.5 + svelte: 5.53.6 - runed@0.28.0(svelte@5.53.5): + runed@0.28.0(svelte@5.53.6): dependencies: esm-env: 1.2.2 - svelte: 5.53.5 + svelte: 5.53.6 - runed@0.35.1(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5): + runed@0.35.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 - svelte: 5.53.5 + svelte: 5.53.6 optionalDependencies: - '@sveltejs/kit': 2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) - runed@0.37.1(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(zod@4.3.6): + runed@0.37.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(zod@4.3.6): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 - svelte: 5.53.5 + svelte: 5.53.6 optionalDependencies: - '@sveltejs/kit': 2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) zod: 4.3.6 sade@1.8.1: @@ -5894,19 +5925,19 @@ snapshots: dependencies: has-flag: 4.0.0 - svelte-check@4.4.3(picomatch@4.0.3)(svelte@5.53.5)(typescript@5.9.3): + svelte-check@4.4.3(picomatch@4.0.3)(svelte@5.53.6)(typescript@5.9.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.3) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.53.5 + svelte: 5.53.6 typescript: 5.9.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.4.1(svelte@5.53.5): + svelte-eslint-parser@1.4.1(svelte@5.53.6): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -5915,36 +5946,36 @@ snapshots: postcss-scss: 4.0.9(postcss@8.5.6) postcss-selector-parser: 7.1.1 optionalDependencies: - svelte: 5.53.5 + svelte: 5.53.6 - svelte-sonner@1.0.7(svelte@5.53.5): + svelte-sonner@1.0.7(svelte@5.53.6): dependencies: - runed: 0.28.0(svelte@5.53.5) - svelte: 5.53.5 + runed: 0.28.0(svelte@5.53.6) + svelte: 5.53.6 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5) + runed: 0.35.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6) style-to-object: 1.0.14 - svelte: 5.53.5 + svelte: 5.53.6 transitivePeerDependencies: - '@sveltejs/kit' - svelte-toolbelt@0.5.0(svelte@5.53.5): + svelte-toolbelt@0.5.0(svelte@5.53.6): dependencies: clsx: 2.1.1 style-to-object: 1.0.14 - svelte: 5.53.5 + svelte: 5.53.6 - svelte-toolbelt@0.7.1(svelte@5.53.5): + svelte-toolbelt@0.7.1(svelte@5.53.6): dependencies: clsx: 2.1.1 - runed: 0.23.4(svelte@5.53.5) + runed: 0.23.4(svelte@5.53.6) style-to-object: 1.0.14 - svelte: 5.53.5 + svelte: 5.53.6 - svelte@5.53.5: + svelte@5.53.6: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 @@ -5963,12 +5994,12 @@ snapshots: magic-string: 0.30.21 zimmerframe: 1.1.4 - sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.5)(typescript@5.9.3): + sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3): dependencies: - '@sveltejs/kit': 2.53.3(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.5)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) + '@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) devalue: 5.6.3 memoize-weak: 1.0.2 - svelte: 5.53.5 + svelte: 5.53.6 ts-deepmerge: 7.0.3 optionalDependencies: '@exodus/schemasafe': 1.3.0 @@ -6091,6 +6122,8 @@ snapshots: undici-types@7.16.0: {} + universalify@2.0.1: {} + unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 @@ -6120,6 +6153,15 @@ snapshots: vary@1.1.2: {} + vite-plugin-compression@0.5.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)): + dependencies: + chalk: 4.1.2 + debug: 4.4.3 + fs-extra: 10.1.0 + vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: esbuild: 0.27.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9eb81930..d0c6d6c8 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,12 +3,16 @@ packages: - tests - email-templates +onlyBuiltDependencies: + - esbuild + - sharp + overrides: - cookie@<0.7.0: ">=0.7.0" + '@isaacs/brace-expansion': '>=5.0.1' + cookie@<0.7.0: '>=0.7.0' devalue: ^5.6.2 - glob@>=11.0.0 <11.1.0: ">=11.1.0" - js-yaml@>=4.0.0 <4.1.1: ">=4.1.1" - valibot@>=0.31.0 <1.2.0: ">=1.2.0" - validator@<13.15.20: ">=13.15.20" - "@isaacs/brace-expansion": ">=5.0.1" - next: ">=16.1.5" + glob@>=11.0.0 <11.1.0: '>=11.1.0' + js-yaml@>=4.0.0 <4.1.1: '>=4.1.1' + next: '>=16.1.5' + valibot@>=0.31.0 <1.2.0: '>=1.2.0' + validator@<13.15.20: '>=13.15.20' From 89349dc1adc2a6defd2bc05ff96c1e8c6c2ae721 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Tue, 3 Mar 2026 01:52:42 -0800 Subject: [PATCH 10/15] fix: handle IPv6 addresses in callback URLs (#1355) --- backend/internal/utils/callback_url_util.go | 94 ++++++-- .../internal/utils/callback_url_util_test.go | 227 +++++++++++++++++- 2 files changed, 290 insertions(+), 31 deletions(-) diff --git a/backend/internal/utils/callback_url_util.go b/backend/internal/utils/callback_url_util.go index f4c3306b..7fa44a97 100644 --- a/backend/internal/utils/callback_url_util.go +++ b/backend/internal/utils/callback_url_util.go @@ -34,22 +34,7 @@ func GetCallbackURLFromList(urls []string, inputCallbackURL string) (callbackURL // time of the request for loopback IP redirect URIs, to accommodate // clients that obtain an available ephemeral port from the operating // system at the time of the request. - loopbackCallbackURLWithoutPort := "" - u, _ := url.Parse(inputCallbackURL) - - if u != nil && u.Scheme == "http" { - host := u.Hostname() - ip := net.ParseIP(host) - if host == "localhost" || (ip != nil && ip.IsLoopback()) { - // For IPv6 loopback hosts, brackets are required when serializing without a port. - if strings.Contains(host, ":") { - u.Host = "[" + host + "]" - } else { - u.Host = host - } - loopbackCallbackURLWithoutPort = u.String() - } - } + loopbackCallbackURLWithoutPort := loopbackURLWithWildcardPort(inputCallbackURL) for _, pattern := range urls { // Try the original callback first @@ -76,6 +61,28 @@ func GetCallbackURLFromList(urls []string, inputCallbackURL string) (callbackURL return "", nil } +func loopbackURLWithWildcardPort(input string) string { + u, _ := url.Parse(input) + + if u == nil || u.Scheme != "http" { + return "" + } + + host := u.Hostname() + ip := net.ParseIP(host) + if host != "localhost" && (ip == nil || !ip.IsLoopback()) { + return "" + } + + // For IPv6 loopback hosts, brackets are required when serializing without a port. + if strings.Contains(host, ":") { + u.Host = "[" + host + "]" + } else { + u.Host = host + } + return u.String() +} + // matchCallbackURL checks if the input callback URL matches the given pattern. // It supports wildcard matching for paths and query parameters. // @@ -125,10 +132,57 @@ func matchCallbackURL(pattern string, inputCallbackURL string) (matches bool, er // normalizeToURLPatternStandard converts patterns with single asterisk wildcards and globstar wildcards // into a format that can be parsed by the urlpattern package, which uses :param for single segment wildcards // and ** for multi-segment wildcards. +// Additionally, it escapes ":" with a backslash inside IPv6 addresses func normalizeToURLPatternStandard(pattern string) string { patternBase, patternPath := extractPath(pattern) var result strings.Builder + result.Grow(len(pattern) + 5) // Add 5 for some extra capacity, hoping to avoid many re-allocations + + // First, process the base + + // 0 = scheme + // 1 = hostname (optionally with username/password) - before IPv6 start (no `[` found) + // 2 = is matching IPv6 (until `]`) + // 3 = after hostname + var step int + for i := 0; i < len(patternBase); i++ { + switch step { + case 0: + if i > 3 && patternBase[i] == '/' && patternBase[i-1] == '/' && patternBase[i-2] == ':' { + // We just passed the scheme + step = 1 + } + case 1: + switch patternBase[i] { + case '/', ']': + // No IPv6, skip to end of this logic + step = 3 + case '[': + // Start of IPv6 match + step = 2 + } + case 2: + if patternBase[i] == '/' || patternBase[i] == ']' || patternBase[i] == '[' { + // End of IPv6 match + step = 3 + } + + switch patternBase[i] { + case ':': + // We are matching an IPv6 block and there's a colon, so escape that + result.WriteByte('\\') + case '/', ']', '[': + // End of IPv6 match + step = 3 + } + } + + // Write the byte + result.WriteByte(patternBase[i]) + } + + // Next, process the path for i := 0; i < len(patternPath); i++ { if patternPath[i] == '*' { // Replace globstar with a single asterisk @@ -141,19 +195,19 @@ func normalizeToURLPatternStandard(pattern string) string { result.WriteString(strconv.Itoa(i)) } } else { + // Add the byte result.WriteByte(patternPath[i]) } } - patternPath = result.String() - - return patternBase + patternPath + return result.String() } func extractPath(url string) (base string, path string) { pathStart := -1 // Look for scheme:// first - if i := strings.Index(url, "://"); i >= 0 { + i := strings.Index(url, "://") + if i >= 0 { // Look for the next slash after scheme:// rest := url[i+3:] if j := strings.IndexByte(rest, '/'); j >= 0 { diff --git a/backend/internal/utils/callback_url_util_test.go b/backend/internal/utils/callback_url_util_test.go index c3423dde..9e109620 100644 --- a/backend/internal/utils/callback_url_util_test.go +++ b/backend/internal/utils/callback_url_util_test.go @@ -58,11 +58,6 @@ func TestValidateCallbackURLPattern(t *testing.T) { pattern: "https://exa[mple.com/callback", shouldError: true, }, - { - name: "malformed authority", - pattern: "https://[::1/callback", - shouldError: true, - }, } for _, tt := range tests { @@ -78,6 +73,76 @@ func TestValidateCallbackURLPattern(t *testing.T) { } } +func TestNormalizeToURLPatternStandard(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "exact URL unchanged", + input: "https://example.com/callback", + expected: "https://example.com/callback", + }, + { + name: "single wildcard path segment converted to named parameter", + input: "https://example.com/api/*/callback", + expected: "https://example.com/api/:p5/callback", + }, + { + name: "single wildcard in path suffix converted to named parameter", + input: "https://example.com/test*", + expected: "https://example.com/test:p5", + }, + { + name: "globstar converted to single asterisk", + input: "https://example.com/**/callback", + expected: "https://example.com/*/callback", + }, + { + name: "mixed globstar and single wildcard conversion", + input: "https://example.com/**/v1/**/callback/*", + expected: "https://example.com/*/v1/*/callback/:p19", + }, + { + name: "URL without path unchanged", + input: "https://example.com", + expected: "https://example.com", + }, + { + name: "relative path conversion", + input: "/foo/*/bar", + expected: "/foo/:p5/bar", + }, + { + name: "wildcard in hostname is not normalized by this function", + input: "https://*.example.com/callback", + expected: "https://*.example.com/callback", + }, + { + name: "IPv6 hostname escapes all colons inside address", + input: "https://[2001:db8:1:1::a:1]/callback", + expected: "https://[2001\\:db8\\:1\\:1\\:\\:a\\:1]/callback", + }, + { + name: "IPv6 hostname with port escapes only address colons", + input: "https://[::1]:8080/callback", + expected: "https://[\\:\\:1]:8080/callback", + }, + { + name: "wildcard in query is converted when query is part of input", + input: "https://example.com/callback?code=*", + expected: "https://example.com/callback?code=:p15", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, normalizeToURLPatternStandard(tt.input)) + }) + } +} + func TestMatchCallbackURL(t *testing.T) { tests := []struct { name string @@ -98,6 +163,18 @@ func TestMatchCallbackURL(t *testing.T) { "https://example.com/callback", false, }, + { + "exact match - IPv4", + "https://10.1.0.1/callback", + "https://10.1.0.1/callback", + true, + }, + { + "exact match - IPv6", + "https://[2001:db8:1:1::a:1]/callback", + "https://[2001:db8:1:1::a:1]/callback", + true, + }, // Scheme { @@ -182,6 +259,30 @@ func TestMatchCallbackURL(t *testing.T) { "https://example.com:8080/callback", true, }, + { + "wildcard port - IPv4", + "https://10.1.0.1:*/callback", + "https://10.1.0.1:8080/callback", + true, + }, + { + "partial wildcard in port prefix - IPv4", + "https://10.1.0.1:80*/callback", + "https://10.1.0.1:8080/callback", + true, + }, + { + "wildcard port - IPv6", + "https://[2001:db8:1:1::a:1]:*/callback", + "https://[2001:db8:1:1::a:1]:8080/callback", + true, + }, + { + "partial wildcard in port prefix - IPv6", + "https://[2001:db8:1:1::a:1]:80*/callback", + "https://[2001:db8:1:1::a:1]:8080/callback", + true, + }, // Path { @@ -202,6 +303,18 @@ func TestMatchCallbackURL(t *testing.T) { "https://example.com/callback", true, }, + { + "wildcard entire path - IPv4", + "https://10.1.0.1/*", + "https://10.1.0.1/callback", + true, + }, + { + "wildcard entire path - IPv6", + "https://[2001:db8:1:1::a:1]/*", + "https://[2001:db8:1:1::a:1]/callback", + true, + }, { "partial wildcard in path prefix", "https://example.com/test*", @@ -435,10 +548,11 @@ func TestMatchCallbackURL(t *testing.T) { } for _, tt := range tests { - matches, err := matchCallbackURL(tt.pattern, tt.input) - require.NoError(t, err, tt.name) - assert.Equal(t, tt.shouldMatch, matches, tt.name) - + t.Run(tt.name, func(t *testing.T) { + matches, err := matchCallbackURL(tt.pattern, tt.input) + require.NoError(t, err) + assert.Equal(t, tt.shouldMatch, matches) + }) } } @@ -472,14 +586,21 @@ func TestGetCallbackURLFromList_LoopbackSpecialHandling(t *testing.T) { expectMatch: true, }, { - name: "IPv6 loopback with dynamic port", + name: "IPv6 loopback with dynamic port - exact match", urls: []string{"http://[::1]/callback"}, inputCallbackURL: "http://[::1]:8080/callback", expectedURL: "http://[::1]:8080/callback", expectMatch: true, }, { - name: "IPv6 loopback with wildcard path", + name: "IPv6 loopback with same port - exact match", + urls: []string{"http://[::1]:8080/callback"}, + inputCallbackURL: "http://[::1]:8080/callback", + expectedURL: "http://[::1]:8080/callback", + expectMatch: true, + }, + { + name: "IPv6 loopback with path match", urls: []string{"http://[::1]/auth/*"}, inputCallbackURL: "http://[::1]:8080/auth/callback", expectedURL: "http://[::1]:8080/auth/callback", @@ -506,6 +627,20 @@ func TestGetCallbackURLFromList_LoopbackSpecialHandling(t *testing.T) { expectedURL: "http://127.0.0.1:3000/auth/callback", expectMatch: true, }, + { + name: "loopback with path port", + urls: []string{"http://127.0.0.1:*/auth/callback"}, + inputCallbackURL: "http://127.0.0.1:3000/auth/callback", + expectedURL: "http://127.0.0.1:3000/auth/callback", + expectMatch: true, + }, + { + name: "IPv6 loopback with path port", + urls: []string{"http://[::1]:*/auth/callback"}, + inputCallbackURL: "http://[::1]:3000/auth/callback", + expectedURL: "http://[::1]:3000/auth/callback", + expectMatch: true, + }, { name: "loopback with path mismatch", urls: []string{"http://127.0.0.1/callback"}, @@ -549,6 +684,76 @@ func TestGetCallbackURLFromList_LoopbackSpecialHandling(t *testing.T) { } } +func TestLoopbackURLWithWildcardPort(t *testing.T) { + tests := []struct { + name string + input string + output string + }{ + { + name: "localhost http with port strips port", + input: "http://localhost:3000/callback", + output: "http://localhost/callback", + }, + { + name: "localhost http without port stays same", + input: "http://localhost/callback", + output: "http://localhost/callback", + }, + { + name: "IPv4 loopback with port strips port", + input: "http://127.0.0.1:8080/callback", + output: "http://127.0.0.1/callback", + }, + { + name: "IPv4 loopback without port stays same", + input: "http://127.0.0.1/callback", + output: "http://127.0.0.1/callback", + }, + { + name: "IPv6 loopback with port strips port and keeps brackets", + input: "http://[::1]:8080/callback", + output: "http://[::1]/callback", + }, + { + name: "IPv6 loopback preserves path query and fragment", + input: "http://[::1]:8080/auth/callback?code=123#state", + output: "http://[::1]/auth/callback?code=123#state", + }, + { + name: "https loopback returns empty", + input: "https://127.0.0.1:8080/callback", + output: "", + }, + { + name: "non loopback host returns empty", + input: "http://example.com:8080/callback", + output: "", + }, + { + name: "non loopback IP returns empty", + input: "http://192.168.1.10:8080/callback", + output: "", + }, + { + name: "malformed URL returns empty", + input: "http://[::1:8080/callback", + output: "", + }, + { + name: "relative URL returns empty", + input: "/callback", + output: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.output, loopbackURLWithWildcardPort(tt.input)) + }) + } +} + func TestGetCallbackURLFromList_MultiplePatterns(t *testing.T) { tests := []struct { name string From 45bcdb4b1d15e097c317cde7320ff9381d2eb50d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:22:26 -0600 Subject: [PATCH 11/15] chore: update AAGUIDs (#1354) Co-authored-by: stonith404 <58886915+stonith404@users.noreply.github.com> --- backend/resources/aaguids.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/resources/aaguids.json b/backend/resources/aaguids.json index 2bead1a3..f6ead537 100644 --- a/backend/resources/aaguids.json +++ b/backend/resources/aaguids.json @@ -1 +1 @@ -{"fcb1bcb4-f370-078c-6993-bc24d0ae3fbe":"Ledger Nano X FIDO2 Authenticator","6e8d1eae-8d40-4c25-bcf8-4633959afc71":"Veridium iOS SDK","9eb7eabc-9db5-49a1-b6c3-555a802093f4":"YubiKey 5 Series with NFC KVZR57","4d41190c-7beb-4a84-8018-adf265a6352d":"Thales IDPrime FIDO Bio","2772ce93-eb4b-4090-8b73-330f48477d73":"Security Key NFC by Yubico - Enterprise Edition Preview","6dae43be-af9c-417b-8b9f-1b611168ec60":"Dapple Authenticator from Dapple Security Inc.","5626bed4-e756-430b-a7ff-ca78c8b12738":"VALMIDO PRO FIDO","260e3021-482d-442d-838c-7edfbe153b7e":"Feitian ePass FIDO2-NFC Plus Authenticator","95e4d58c-056e-4a65-866d-f5a69659e880":"TruU Windows Authenticator","90636e1f-ef82-43bf-bdcf-5255f139d12f":"YubiKey Bio Series - Multi-protocol Edition","9c835346-796b-4c27-8898-d6032f515cc5":"Cryptnox FIDO2","c3f47802-de73-4dfc-ba22-671fe3304f90":"eToken Fusion NFC PIV Enterprise","0d9b2e56-566b-c393-2940-f821b7f15d6d":"Excelsecu eSecu FIDO2 Pro Security Key","2bff89f2-323a-48fc-b7c8-9ff7fe87c07e":"Feitian BioPass FIDO2 Pro (Enterprise Profile)","c5ef55ff-ad9a-4b9f-b580-adebafe026d0":"YubiKey 5 Series with Lightning","2194b428-9397-4046-8f39-007a1605a482":"IDPrime 931 Fido","39a5647e-1853-446c-a1f6-a79bae9f5bc7":"IDmelon","664d9f67-84a2-412a-9ff7-b4f7d8ee6d05":"OpenSK authenticator","3789da91-f943-46bc-95c3-50ea2012f03a":"NEOWAVE Winkeo FIDO2","fa2b99dc-9e39-4257-8f92-4a30d23c4118":"YubiKey 5 Series with NFC","341e4da9-3c2e-8103-5a9f-aad887135200":"Ledger Nano S FIDO2 Authenticator","69700f79-d1fb-472e-bd9b-a3a3b9a9eda0":"Pone Biometrics OFFPAD Authenticator","8da0e4dc-164b-454e-972e-88f362b23d59":"CardOS FIDO2 Token","89b19028-256b-4025-8872-255358d950e4":"Sentry Enterprises CTAP2 Authenticator","4e768f2c-5fab-48b3-b300-220eb487752b":"Hideez Key 4 FIDO2 SDK","47ab2fb4-66ac-4184-9ae1-86be814012d5":"Security Key NFC by Yubico - Enterprise Edition","931327dd-c89b-406c-a81e-ed7058ef36c6":"Swissbit iShield Key FIDO2","f8d5c4e9-e539-4c06-8662-ec2a4155a555":"StarSign Key Fob","b7d3f68e-88a6-471e-9ecf-2df26d041ede":"Security Key NFC by Yubico","8d1b1fcb-3c76-49a9-9129-5515b346aa02":"IDEMIA ID-ONE Card","30b5035e-d297-4ff7-020b-addc96ba6a98":"OneSpan DIGIPASS FX7","454e5346-4944-4ffd-6c93-8e9267193e9a":"Ensurity ThinC","e1a96183-5016-4f24-b55b-e3ae23614cc6":"ATKey.Pro CTAP2.0","9ff4cc65-6154-4fff-ba09-9e2af7882ad2":"Security Key NFC by Yubico - Enterprise Edition (Enterprise Profile)","4599062e-6926-4fe7-9566-9e8fb1aedaa0":"YubiKey 5 Series (Enterprise Profile)","9d3df6ba-282f-11ed-a261-0242ac120002":"Arculus FIDO2/U2F Key Card","fbefdf68-fe86-0106-213e-4d5fa24cbe2e":"Excelsecu eSecu FIDO2 NFC Security Key","62e54e98-c209-4df3-b692-de71bb6a8528":"YubiKey 5 FIPS Series with NFC Preview","ab32f0c6-2239-afbb-c470-d2ef4e254db7":"TOKEN2 FIDO2 Security Key","ce6bf97f-9f69-4ba7-9032-97adc6ca5cf1":"YubiKey 5 FIPS Series with NFC (RC Preview)","ad08c78a-4e41-49b9-86a2-ac15b06899e2":"YubiKey Bio Series - FIDO Edition (Enterprise Profile)","930b0c03-ef46-4ac4-935c-538dccd1fcdb":"Chipwon Clife Key","7787a482-13e8-4784-8a06-c7ed49a7aaf4":"Swissbit iShield Key 2","72c6b72d-8512-4c66-8359-9d3d10d9222f":"Security Key NFC by Yubico - Enterprise Edition (Enterprise Profile)","99ed6c29-4573-4847-816d-78ad8f1c75ef":"VeroCard FIDO2 Authenticator","973446ca-e21c-9a9b-99f5-9b985a67af0f":"ACS FIDO Authenticator Card","74820b05-a6c9-40f9-8fb0-9f86aca93998":"SafeNet eToken Fusion","1105e4ed-af1d-02ff-ffff-ffffffffffff":"Egomet FIDO2 Authenticator for Android","08987058-cadc-4b81-b6e1-30de50dcbe96":"Windows Hello","a4e9fc6d-4cbe-4758-b8ba-37598bb5bbaa":"Security Key NFC by Yubico","0acf3011-bc60-f375-fb53-6f05f43154e0":"Nymi FIDO2 Authenticator","d91c5288-0ef0-49b7-b8ae-21ca0aa6b3f3":"KEY-ID FIDO2 Authenticator","8eec9bf9-486c-46da-9a67-1fbb4f66b9ed":"HID Crescendo 4000 FIPS","4c50ff10-1057-4fc6-b8ed-43a529530c3c":"ImproveID Authenticator","c611b55c-77b2-4527-8082-590e931b2f08":"GoTrust Idem Key (Consumer profile)","ee041bce-25e5-4cdb-8f86-897fd6418464":"Feitian ePass FIDO2-NFC Authenticator","4b89f401-464e-4745-a520-486ddfc5d80e":"IIST FIDO2 Authenticator","2cd2f727-f6ca-44da-8f48-5c2e5da000a2":"Nitrokey 3 AM","10c70715-2a9a-4de1-b0aa-3cff6d496d39":"eToken Fusion NFC FIPS","efb96b10-a9ee-4b6c-a4a9-d32125ccd4a4":"Safenet eToken FIDO","4b3f8944-d4f2-4d21-bb19-764a986ec160":"KeyXentic FIDO2 Secp256R1 FIDO2 CTAP2 Authenticator","4c0cf95d-2f40-43b5-ba42-4c83a11c04ba":"Feitian BioPass FIDO2 Pro Authenticator","5343502d-5343-5343-6172-644649444f32":"ESS Smart Card Inc. Authenticator","69e7c36f-f2f6-9e0d-07a6-bcc243262e6b":"OneKey FIDO2 Authenticator","09591fc6-9811-48f7-8f57-b9f23df6413f":"Pone Biometrics OFFPAD Authenticator","912435d9-4a88-42f3-972d-1244b0d51420":"SI0X FIDO CL WRIST v1.0","7e3f3d30-3557-4442-bdae-139312178b39":"RSA DS100","73bb0cd4-e502-49b8-9c6f-b59445bf720b":"YubiKey 5 FIPS Series","39589099-9a75-49fc-afaa-801ca211c62a":"Feitian ePass FIDO-NFC (Enterprise Profile) (CTAP2.1, CTAP2.0, U2F)","149a2021-8ef6-4133-96b8-81f8d5b7f1f5":"Security Key by Yubico with NFC","5df66f62-5b47-43d3-aa1d-a6e31c8dbeb5":"Securitag Assembly Group FIDO Authenticator NFC","09619fbf-d75e-4a62-be1d-fe4d240864ae":"VeriMark(TM) Guard 2.1 Fingerprint Security Key","50cbf15a-238c-4457-8f16-812c43bf3c49":"Ensurity AUTH TouchPro","ee7fa1e0-9539-432f-bd43-9c2fc6d4f311":"VeriMark NFC+ USB-C Security Key","b90e7dc1-316e-4fee-a25a-56a666a670fe":"YubiKey 5 Series with Lightning (Enterprise Profile)","175cd298-83d2-4a26-b637-313c07a6434e":"Chunghwa Telecom FIDO2 Smart Card Authenticator","34744913-4f57-4e6e-a527-e9ec3c4b94e6":"YubiKey Bio Series - Multi-protocol Edition","5ea308b2-7ac7-48b9-ac09-7e2da9015f8c":"Veridium Android SDK","3b1adb99-0dfe-46fd-90b8-7f7614a4de2a":"GoTrust Idem Key FIDO2 Authenticator","46544d5d-8f5d-4db4-89ac-ea8977073fff":"Foongtone FIDO Authenticator","998f358b-2dd2-4cbe-a43a-e8107438dfb3":"OnlyKey Secp256R1 FIDO2 CTAP2 Authenticator","30b5035e-d297-4ff2-010b-addc96ba6a98":"OneSpan DIGIPASS FX2-A","817cdab8-0d51-4de1-a821-e25b88519cf3":"Swissbit iShield Key 2 FIPS","61250591-b2bc-4456-b719-0b17be90bb30":"eWBM eFPA FIDO2 Authenticator","8c39ee86-7f9a-4a95-9ba3-f6b097e5c2ee":"YubiKey Bio Series - FIDO Edition (Enterprise Profile)","f8a011f3-8c0a-4d15-8006-17111f9edc7d":"Security Key by Yubico","8976631b-d4a0-427f-5773-0ec71c9e0279":"Solo Tap Secp256R1 FIDO2 CTAP2 Authenticator","516d3969-5a57-5651-5958-4e7a49434167":"SmartDisplayer BobeePass FIDO2 Authenticator","8681a073-5f50-4d52-bce4-e21658d207b3":"RSA Authenticator 4 for iOS","30b5035e-d297-4ff7-030b-addc96ba6a98":"OneSpan DIGIPASS FX7-C","e41b42a3-60ac-4afb-8757-a98f2d7f6c9f":"Deepnet SafeKey/Classic (FP)","c89e6a38-6c00-5426-5aa5-c9cbf48f0382":"ACS FIDO Authenticator NFC","a02167b9-ae71-4ac7-9a07-06432ebb6f1c":"YubiKey 5 Series with Lightning","82b0a720-127a-4788-b56d-d1d4b2d82eac":"ID-One Key","2c0df832-92de-4be1-8412-88a8f074df4a":"Feitian FIDO Smart Card","59f85fe7-faa5-4c92-9f52-697b9d4d5473":"RSA Authenticator 4 for Android","79f3c8ba-9e35-484b-8f47-53a5a0f5c630":"YubiKey 5 FIPS Series with NFC (Enterprise Profile)","def8ab1a-9f91-44f1-a103-088d8dc7d681":"IDEMIA SOLVO Fly 80 R3 FIDO Card e","970c8d9c-19d2-46af-aa32-3f448db49e35":"WinMagic FIDO Eazy - TPM","c5703116-972b-4851-a3e7-ae1259843399":"NEOWAVE Badgeo FIDO2","c80dbd9a-533f-4a17-b941-1a2f1c7cedff":"HID Crescendo C3000","5b0e46ba-db02-44ac-b979-ca9b84f5e335":"YubiKey 5 FIPS Series with Lightning Preview","12755c32-8ad1-46eb-881c-e0b38d848b09":"Feitian ePass FIDO Authenticator (CTAP2.1, CTAP2.0, U2F)","2a55aee6-27cb-42c0-bc6e-04efe999e88a":"HID Crescendo 4000","820d89ed-d65a-409e-85cb-f73f0578f82a":"IDmelon iOS Authenticator","019614a3-2703-7e35-a453-285fd06c5d24":"ATLKey Authenticator","3124e301-f14e-4e38-876d-fbeeb090e7bf":"YubiKey 5 Series with Lightning Preview","b6ede29c-3772-412c-8a78-539c1f4c62d2":"Feitian BioPass FIDO2 Plus Authenticator","ed042a3a-4b22-4455-bb69-a267b652ae7e":"Security Key NFC by Yubico - Enterprise Edition","b2c1a50b-dad8-4dc7-ba4d-0ce9597904bc":"YubiKey 5 Series with NFC - Enhanced PIN (Enterprise Profile)","85203421-48f9-4355-9bc8-8a53846e5083":"YubiKey 5 FIPS Series with Lightning","fcc0118f-cd45-435b-8da1-9782b2da0715":"YubiKey 5 FIPS Series with NFC","d821a7d4-e97c-4cb6-bd82-4237731fd4be":"Hyper FIDO Bio Security Key","9876631b-d4a0-427f-5773-0ec71c9e0279":"Somu Secp256R1 FIDO2 CTAP2 Authenticator","f56f58b3-d711-4afc-ba7d-6ac05f88cb19":"WinMagic FIDO Eazy - Phone","6ec5cff2-a0f9-4169-945b-f33b563f7b99":"YubiKey Bio Series - Multi-protocol Edition (Enterprise Profile)","882adaf5-3aa9-4708-8e7d-3957103775b4":"T-Shield TrustSec FIDO2 Bio and client PIN version","49a15c1c-3f63-3f51-23a7-b9e00096edd1":"IDEX CTAP2.1 Biometrics","f4c63eff-d26c-4248-801c-3736c7eaa93a":"FIDO KeyPass S3","d384db22-4d50-ebde-2eac-5765cf1e2a44":"Excelsecu eSecu FIDO2 Fingerprint Security Key","0db01cd6-5618-455b-bb46-1ec203d3213e":"GoldKey Security Token","b93fd961-f2e6-462f-b122-82002247de78":"Android Authenticator","aa79f476-ea00-417e-9628-1e8365123922":"HID Crescendo 4000 FIDO","1e906e14-77af-46bc-ae9f-fe6ef18257e4":"VeridiumID Passkey iOS SDK","2fc0579f-8113-47ea-b116-bb5a8db9202a":"YubiKey 5 Series with NFC","31c3f7ff-bf15-4327-83ec-9336abcbcd34":"WinMagic FIDO Eazy - Software","9ddd1817-af5a-4672-a2b9-3e3dd95000a9":"Windows Hello","d8522d9f-575b-4866-88a9-ba99fa02f35b":"YubiKey Bio Series - FIDO Edition","050dd0bc-ff20-4265-8d5d-305c4b215192":"eToken Fusion FIPS","50a45b0c-80e7-f944-bf29-f552bfa2e048":"ACS FIDO Authenticator","f7c558a0-f465-11e8-b568-0800200c9a66":"KONAI Secp256R1 FIDO2 Conformance Testing CTAP2 Authenticator","3f59672f-20aa-4afe-b6f4-7e5e916b6d98":"Arculus FIDO 2.1 Key Card [P71]","42b4fb4a-2866-43b2-9bf7-6c6669c2e5d3":"Google Titan Security Key v2","361a3082-0278-4583-a16f-72a527f973e4":"eWBM eFA500 FIDO2 Authenticator","2ffd6452-01da-471f-821b-ea4bf6c8676a":"IDPrime 941 Fido","30b5035e-d297-4ff7-b00b-addc96ba6a98":"OneSpan DIGIPASS FX7","5eaff75a-dd43-451f-af9f-87c9eeae293e":"Swissbit iShield Key 2 FIPS Enterprise","b415094c-49d3-4c8b-b3fe-7d0ad28a6bc4":"ZTPass SmartAuth","692db549-7ae5-44d5-a1e5-dd20a493b723":"HID Crescendo Key","23315ad0-6aca-4ba1-952e-f044f1e36976":"Clife Key 2 NFC","1d1b4e33-76a1-47fb-97a0-14b10d0933f1":"Cryptnox FIDO2.1","bbf4b6a7-679d-f6fc-c4f2-8ac0ddf9015a":"Excelsecu eSecu FIDO2 PRO Security Key","3e22415d-7fdf-4ea4-8a0c-dd60c4249b9d":"Feitian iePass FIDO Authenticator","23786452-f02d-4344-87ed-aaf703726881":"SafeNet eToken Fusion CC","5e264d9d-28ef-4d34-95b4-5941e7a4faa8":"Ideem ZSM FIDO2 Authenticator","d2fbd093-ee62-488d-9dad-1e36389f8826":"YubiKey 5 FIPS Series (RC Preview)","234cd403-35a2-4cc2-8015-77ea280c77f5":"Feitian ePass FIDO2-NFC Series (CTAP2.1, CTAP2.0, U2F)","6999180d-630c-442d-b8f7-424b90a43fae":"Hyper FIDO Pro (CTAP2.1, CTAP2.0, U2F)","662ef48a-95e2-4aaa-a6c1-5b9c40375824":"YubiKey 5 Series with NFC - Enhanced PIN","aeb6569c-f8fb-4950-ac60-24ca2bbe2e52":"HID Crescendo C2300","87dbc5a1-4c94-4dc8-8a47-97d800fd1f3c":"eWBM eFA320 FIDO2 Authenticator","58276709-bb4b-4bb3-baf1-60eea99282a7":"YubiKey Bio Series - Multi-protocol Edition 1VDJSN","7d2afadd-bf6b-44a2-a66b-e831fceb8eff":"Taglio CTAP2.1 EP","30b5035e-d297-4ff1-020b-addc96ba6a98":"OneSpan DIGIPASS FX1-C","20ac7a17-c814-4833-93fe-539f0d5e3389":"YubiKey 5 Series (Enterprise Profile)","9012593f-43e4-4461-a97a-d92777b55d74":"VinCSS FIDO2 Fingerprint","d7781e5d-e353-46aa-afe2-3ca49f13332a":"YubiKey 5 Series with NFC","9f0d8150-baa5-4c00-9299-ad62c8bb4e87":"GoTrust Idem Card FIDO2 Authenticator","12ded745-4bed-47d4-abaa-e713f51d6393":"Feitian AllinOne FIDO2 Authenticator","88bbd2f0-342a-42e7-9729-dd158be5407a":"Precision InnaIT Key FIDO 2 Level 2 certified","1d8cac46-47a1-3386-af50-e88ae46fe802":"Ledger Flex FIDO2 Authenticator","dd86a2da-86a0-4cbe-b462-4bd31f57bc6f":"YubiKey Bio Series - FIDO Edition","773c30d9-5919-4e96-a4f5-db65e95cf890":"GSTAG OAK FIDO2 Authenticator","34f5766d-1536-4a24-9033-0e294e510fb0":"YubiKey 5 Series with NFC Preview","83c47309-aabb-4108-8470-8be838b573cb":"YubiKey Bio Series - FIDO Edition (Enterprise Profile)","4e2ddbc2-2687-4709-8551-cb66c9776bfe":"SECORA ID V2 FIDO2.1 L1","be727034-574a-f799-5c76-0929e0430973":"Crayonic KeyVault K1 (USB-NFC-BLE FIDO2 Authenticator)","092277e5-8437-46b5-b911-ea64b294acb7":"Taglio CTAP2.1 CS","ca87cb70-4c1b-4579-a8e8-4efdd7c007e0":"FIDO Alliance TruU Sample FIDO2 Authenticator","23195a52-62d9-40fa-8ee5-23b173f4fb52":"Hyper FIDO Pro NFC","a7fc3f84-86a3-4da4-a3d7-eb6485a066d8":"NEOWAVE Badgeo FIDO2 (CTAP 2.1)","9e66c661-e428-452a-a8fb-51f7ed088acf":"YubiKey 5 FIPS Series with Lightning (RC Preview)","58b44d0b-0a7c-f33a-fd48-f7153c871352":"Ledger Nano S Plus FIDO2 Authenticator","454e5346-4944-4ffd-6c93-8e9267193e9b":"Ensurity AUTH BioPro","146e77ef-11eb-4423-b847-ce77864e9411":"eToken Fusion NFC PIV","13ac47cf-1d78-4fd5-9060-aedaabacf826":"HID Crescendo Key V3 - Enterprise Edition","e77e3c64-05e3-428b-8824-0cbeb04b829d":"Security Key NFC by Yubico","33d6d7d0-279f-4ef3-96b3-2d3282f4bde6":"Thales eToken Fusion BIO Enterprise","8d4378b0-725d-4432-b3c2-01fcdaf46286":"VeridiumID Passkey Android SDK","7409272d-1ff9-4e10-9fc9-ac0019c124fd":"YubiKey Bio Series - FIDO Edition","bb66c294-de08-47e4-b7aa-d12c2cd3fb20":"Mettlesemi Vishwaas Hawk Authenticator using FIDO2","c4ddaf11-3032-4e77-b3b9-3a340369b9ad":"HID Crescendo Fusion","7d1351a6-e097-4852-b8bf-c9ac5c9ce4a3":"YubiKey Bio Series - Multi-protocol Edition","07a9f89c-6407-4594-9d56-621d5f1e358b":"NXP Semiconductros FIDO2 Conformance Testing CTAP2 Authenticator","d61d3b87-3e7c-4aea-9c50-441c371903ad":"KeyVault Secp256R1 FIDO2 CTAP2 Authenticator","c62100de-759b-4bf8-b22b-63b3e3a80401":"Token Ring 3 FIDO2 Authenticator","5ca1ab1e-1337-fa57-f1d0-a117e71ca702":"Allthenticator iOS App: roaming BLE FIDO2 Allthenticator for Windows, Mac, Linux, and Allthenticate door readers","b92c3f9a-c014-4056-887f-140a2501163b":"Security Key by Yubico","54d9fee8-e621-4291-8b18-7157b99c5bec":"HID Crescendo Enabled","a25342c0-3cdc-4414-8e46-f4807fca511c":"YubiKey 5 Series with NFC","3a662962-c6d4-4023-bebb-98ae92e78e20":"YubiKey 5 FIPS Series with Lightning (Enterprise Profile)","20f0be98-9af9-986a-4b42-8eca4acb28e4":"Excelsecu eSecu FIDO2 Fingerprint Security Key","ca4cff1b-5a81-4404-8194-59aabcf1660b":"IDPrime 3930 FIDO","ab32f0c6-2239-afbb-c470-d2ef4e254db6":"TEST (DUMMY RECORD)","760eda36-00aa-4d29-855b-4012a182cdeb":"Security Key NFC by Yubico Preview","6028b017-b1d4-4c02-b4b3-afcdafc96bb2":"Windows Hello","b12eac35-586c-4809-a4b1-d81af6c305cf":"Deepnet SafeKey/Classic (NFC)","30b5035e-d297-4fc1-b00b-addc96ba6a97":"OneSpan FIDO Touch","560a780c-b6ae-4f03-b110-082f856425b4":"KQC QuKey Bio FIDO2 Authenticator","1ac71f64-468d-4fe0-bef1-0e5f2f551f18":"YubiKey 5 Series with NFC (Enterprise Profile)","6d44ba9b-f6ec-2e49-b930-0c8fe920cb73":"Security Key by Yubico with NFC","9eb85bb6-9625-4a72-815d-0487830ccab2":"Ensurity AUTH BioPro Desktop","30b5035e-d297-4ff7-010b-addc96ba6a98":"OneSpan DIGIPASS FX7-B","5ca1ab1e-fa57-1337-f1d0-a117371ca702":"Allthenticator Android App: roaming BLE FIDO2 Allthenticator for Windows, Mac, Linux, and Allthenticate door readers","eabb46cc-e241-80bf-ae9e-96fa6d2975cf":"TOKEN2 PIN Plus Security Key Series ","53414d53-554e-4700-0000-000000000000":"Samsung Pass","e416201b-afeb-41ca-a03d-2281c28322aa":"ATKey.Pro CTAP2.1","905b4cb4-ed6f-4da9-92fc-45e0d4e9b5c7":"YubiKey 5 FIPS Series (Enterprise Profile)","cfcb13a2-244f-4b36-9077-82b79d6a7de7":"USB/NFC Passcode Authenticator","76692dc1-c56a-48d9-8e7d-31b5ced430ac":"VeriMark NFC+ USB-A Security Key","91ad6b93-264b-4987-8737-3a690cad6917":"Token Ring FIDO2 Authenticator","a02140b7-0cbd-42e1-a9b5-a39da2545114":"Feitian BioPass FIDO2 Plus (Enterprise Profile)","5753362b-4e6b-6345-7b2f-255438404c75":"WiSECURE Blentity FIDO2 Authenticator","9f77e279-a6e2-4d58-b700-31e5943c6a98":"Hyper FIDO Pro","b9f6b7b6-f929-4189-bca9-dd951240c132":"Deepnet SafeKey/Classic (USB)","cc45f64e-52a2-451b-831a-4edd8022a202":"ToothPic Passkey Provider","0bb43545-fd2c-4185-87dd-feb0b2916ace":"Security Key NFC by Yubico - Enterprise Edition","73402251-f2a8-4f03-873e-3cb6db604b03":"uTrust FIDO2 Security Key","c1f9a0bc-1dd2-404a-b27f-8e29047a43fd":"YubiKey 5 FIPS Series with NFC","70e7c36f-f2f6-9e0d-07a6-bcc243262e6b":"OneKey FIDO2 Bluetooth Authenticator","4fc84f16-2545-4e53-b8fc-7bf4d7282a10":"YubiKey 5 CCN Series with NFC (Enterprise Profile)","6ab56fad-881f-4a43-acb2-0be065924522":"YubiKey 5 Series with NFC (Enterprise Profile)","504d7149-4e4c-3841-4555-55445a677357":"WiSECURE AuthTron USB FIDO2 Authenticator","2c2aeed8-8174-4159-814b-486e92a261d0":"NEOWAVE WINKEO V2.0","f2145e86-211e-4931-b874-e22bba7d01cc":"ID-One Key","a3975549-b191-fd67-b8fb-017e2917fdb3":"Excelsecu eSecu FIDO2 NFC Security Key","19083c3d-8383-4b18-bc03-8f1c9ab2fd1b":"YubiKey 5 Series","da1fa263-8b25-42b6-a820-c0036f21ba7f":"ATKey.Card NFC","6002f033-3c07-ce3e-d0f7-0ffe5ed42543":"Excelsecu eSecu FIDO2 Fingerprint Key","5fdb81b8-53f0-4967-a881-f5ec26fe4d18":"VinCSS FIDO2 Authenticator","78ba3993-d784-4f44-8d6e-cc0a8ad5230e":"Feitian ePass FIDO-NFC(CTAP2.1, CTAP2.0, U2F)","57f7de54-c807-4eab-b1c6-1c9be7984e92":"YubiKey 5 FIPS Series","bb405265-40cf-4115-93e5-a332c1968d8c":"ID-One Card","2d3bec26-15ee-4f5d-88b2-53622490270b":"HID Crescendo Key V2","489ff376-b48d-6640-bb69-782a860ca795":"Mettlesemi Vishwaas Eagle Authenticator using FIDO2","3b24bf49-1d45-4484-a917-13175df0867b":"YubiKey 5 Series with Lightning (Enterprise Profile)","30b5035e-d297-4ff1-010b-addc96ba6a98":"OneSpan DIGIPASS FX1a","cb69481e-8ff7-4039-93ec-0a2729a154a8":"YubiKey 5 Series","0076631b-d4a0-427f-5773-0ec71c9e0279":"HYPR FIDO2 Authenticator","d716019a-9f4e-4041-9750-17c78f8ae81a":"eToken Fusion BIO","57235694-51a5-4a4d-a81a-f42185df6502":"SHALO AUTH","24673149-6c86-42e7-98d9-433fb5b73296":"YubiKey 5 Series with Lightning","42df17de-06ba-4177-a2bb-6701be1380d6":"Feitian BioPass FIDO2 Plus Authenticator","d7a423ad-3e19-4492-9200-78137dccc136":"VivoKey Apex FIDO2","ba76a271-6eb6-4171-874d-b6428dbe3437":"ATKey.ProS","97e6a830-c952-4740-95fc-7c78dc97ce47":"YubiKey Bio Series - Multi-protocol Edition (Enterprise Profile)","f573f209-b7fb-b261-671a-d7cf624cc812":"Excelsecu eSecu FIDO2 PRO+ Security Key","6e24d385-004a-16a0-7bfe-efd963845b34":"Ledger Stax FIDO2 Authenticator","ee882879-721c-4913-9775-3dfcce97072a":"YubiKey 5 Series","8876631b-d4a0-427f-5773-0ec71c9e0279":"Solo Secp256R1 FIDO2 CTAP2 Authenticator","fec067a1-f1d0-4c5e-b4c0-cc3237475461":"KX701 SmartToken FIDO","30b5035e-d297-4ff1-b00b-addc96ba6a98":"OneSpan DIGIPASS FX1 BIO","b267239b-954f-4041-a01b-ee4f33c145b6":"authenton1 - CTAP2.1","b50d5e0a-7f81-4959-9b12-f45407407503":"IDPrime 3940 FIDO","8c97a730-3f7b-41a6-87d6-1e9b62bda6f0":"FT-JCOS FIDO Fingerprint Card","99bf4610-ec26-4252-b31f-7380ccd59db5":"ZTPass SmartAuth","a1f52be5-dfab-4364-b51c-2bd496b14a56":"OCTATCO EzFinger2 FIDO2 AUTHENTICATOR","0f00cc22-4640-41e7-9585-384ec73ffe9b":"Taglio CTAP2.1 BIO","ff4dac45-ede8-4ec2-aced-cf66103f4335":"YubiKey 5 Series","ba86dc56-635f-4141-aef6-00227b1b9af6":"TruU Windows Authenticator","3e078ffd-4c54-4586-8baa-a77da113aec5":"Hideez Key 3 FIDO2","fc5ca237-69a0-4f3c-afe4-1ebc66def6df":"Clife Key 2","ec31b4cc-2acc-4b8e-9c01-bade00ccbe26":"KeyXentic FIDO2 Secp256R1 FIDO2 CTAP2 Authenticator","5d629218-d3a5-11ed-afa1-0242ac120002":"Swissbit iShield Key Pro","bb878d7b-cf54-4784-b390-357030497043":"TruU FIDO2 Authenticator","d41f5a69-b817-4144-a13c-9ebd6d9254d6":"ATKey.Card CTAP2.0","e86addcd-7711-47e5-b42a-c18257b0bf61":"IDCore 3121 Fido","b113a455-cfb6-4c17-8cba-cd952feb7d48":"eToken FIDO NFC","95442b2e-f15e-4def-b270-efb106facb4e":"eWBM eFA310 FIDO2 Authenticator","dda9aa35-aaf1-4d3c-b6db-7902fd7dbbbf":"IDEMIA SOLVO Fly 80 R3 FIDO Card c","cdbdaea2-c415-5073-50f7-c04e968640b6":"Excelsecu eSecu FIDO2 Security Key","3aa78eb1-ddd8-46a8-a821-8f8ec57a7bd5":"YubiKey 5 CCN Series with NFC","bc2fe499-0d8e-4ffe-96f3-94a82840cf8c":"OCTATCO EzQuant FIDO2 AUTHENTICATOR","eb3b131e-59dc-536a-d176-cb7306da10f5":"ellipticSecure MIRkey USB Authenticator","3fd410dc-8ab7-4b86-a1cb-c7174620b2dc":"IDEMIA SOLVO Fly 80 R1 FIDO Card Draft","a6c5f5d8-2ad0-48b6-8257-e502c8970931":"eToken FIDO NFC Enterprise","e400ef8c-711d-4692-af46-7f2cf7da23ad":"Swissbit iShield Key 2 Enterprise","1c086528-58d5-f211-823c-356786e36140":"Atos CardOS FIDO2","77010bd7-212a-4fc9-b236-d2ca5e9d4084":"Feitian BioPass FIDO2 Authenticator","d94a29d9-52dd-4247-9c2d-8b818b610389":"VeriMark Guard Fingerprint Key","7b96457d-e3cd-432b-9ceb-c9fdd7ef7432":"YubiKey 5 FIPS Series with Lightning","7991798a-a7f3-487f-98c0-3faf7a458a04":"HID Crescendo Key V3","833b721a-ff5f-4d00-bb2e-bdda3ec01e29":"Feitian ePass FIDO2 Authenticator","c89674e3-a765-4b07-888a-7c086fbdf04b":"StarSign FIDO Card","a11a5faa-9f32-4b8c-8c5d-2f7d13e8c942":"AliasVault","ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4":"Google Password Manager","adce0002-35bc-c60a-648b-0b25f1f05503":"Chrome on Mac","dd4ec289-e01d-41c9-bb89-70fa845d4bf2":"iCloud Keychain (Managed)","531126d6-e717-415c-9320-3d9aa6981239":"Dashlane","bada5566-a7aa-401f-bd96-45619a55120d":"1Password","b84e4048-15dc-4dd0-8640-f4f60813c8af":"NordPass","0ea242b4-43c4-4a1b-8b17-dd6d0b6baec6":"Keeper","891494da-2c90-4d31-a9cd-4eab0aed1309":"Sésame","f3809540-7f14-49c1-a8b3-8f813b225541":"Enpass","b5397666-4885-aa6b-cebf-e52262a439a2":"Chromium Browser","771b48fd-d3d4-4f74-9232-fc157ab0507a":"Edge on Mac","d548826e-79b4-db40-a3d8-11116f7e8349":"Bitwarden","fbfc3007-154e-4ecc-8c0b-6e020557d7bd":"Apple Passwords","66a0ccb3-bd6a-191f-ee06-e375c50b9846":"Thales Bio iOS SDK","8836336a-f590-0921-301d-46427531eee6":"Thales Bio Android SDK","cd69adb5-3c7a-deb9-3177-6800ea6cb72a":"Thales PIN Android SDK","17290f1e-c212-34d0-1423-365d729f09d9":"Thales PIN iOS SDK","50726f74-6f6e-5061-7373-50726f746f6e":"Proton Pass","fdb141b2-5d84-443e-8a35-4698c205a502":"KeePassXC","eaecdef2-1c31-5634-8639-f1cbd9c00a08":"KeePassDX","bfc748bb-3429-4faa-b9f9-7cfa9f3b76d0":"iPasswords","b35a26b2-8f6e-4697-ab1d-d44db4da28c6":"Zoho Vault","b78a0a55-6ef8-d246-a042-ba0f6d55050c":"LastPass","de503f9c-21a4-4f76-b4b7-558eb55c6f89":"Devolutions","22248c4c-7a12-46e2-9a41-44291b373a4d":"LogMeOnce","a10c6dd9-465e-4226-8198-c7c44b91c555":"Kaspersky Password Manager","d350af52-0351-4ba2-acd3-dfeeadc3f764":"pwSafe","d3452668-01fd-4c12-926c-83a4204853aa":"Microsoft Password Manager","6d212b28-a2c1-4638-b375-5932070f62e9":"initial","d49b2120-b865-4191-8cea-be84a52b0485":"Heimlane Vault","e8b7f4a2-c3d5-e6f7-890a-b1c2d3e4f567":"Sherlocked"} +{"fcb1bcb4-f370-078c-6993-bc24d0ae3fbe":"Ledger Nano X FIDO2 Authenticator","6e8d1eae-8d40-4c25-bcf8-4633959afc71":"Veridium iOS SDK","9eb7eabc-9db5-49a1-b6c3-555a802093f4":"YubiKey 5 Series with NFC KVZR57","4d41190c-7beb-4a84-8018-adf265a6352d":"Thales IDPrime FIDO Bio","2772ce93-eb4b-4090-8b73-330f48477d73":"Security Key NFC by Yubico - Enterprise Edition Preview","6dae43be-af9c-417b-8b9f-1b611168ec60":"Dapple Authenticator from Dapple Security Inc.","5626bed4-e756-430b-a7ff-ca78c8b12738":"VALMIDO PRO FIDO","260e3021-482d-442d-838c-7edfbe153b7e":"Feitian ePass FIDO2-NFC Plus Authenticator","95e4d58c-056e-4a65-866d-f5a69659e880":"TruU Windows Authenticator","90636e1f-ef82-43bf-bdcf-5255f139d12f":"YubiKey Bio Series - Multi-protocol Edition","9c835346-796b-4c27-8898-d6032f515cc5":"Cryptnox FIDO2","c3f47802-de73-4dfc-ba22-671fe3304f90":"eToken Fusion NFC PIV Enterprise","0d9b2e56-566b-c393-2940-f821b7f15d6d":"Excelsecu eSecu FIDO2 Pro Security Key","2bff89f2-323a-48fc-b7c8-9ff7fe87c07e":"Feitian BioPass FIDO2 Pro (Enterprise Profile)","c5ef55ff-ad9a-4b9f-b580-adebafe026d0":"YubiKey 5 Series with Lightning","2194b428-9397-4046-8f39-007a1605a482":"IDPrime 931 Fido","39a5647e-1853-446c-a1f6-a79bae9f5bc7":"IDmelon","664d9f67-84a2-412a-9ff7-b4f7d8ee6d05":"OpenSK authenticator","3789da91-f943-46bc-95c3-50ea2012f03a":"NEOWAVE Winkeo FIDO2","fa2b99dc-9e39-4257-8f92-4a30d23c4118":"YubiKey 5 Series with NFC","341e4da9-3c2e-8103-5a9f-aad887135200":"Ledger Nano S FIDO2 Authenticator","69700f79-d1fb-472e-bd9b-a3a3b9a9eda0":"Pone Biometrics OFFPAD Authenticator","8da0e4dc-164b-454e-972e-88f362b23d59":"CardOS FIDO2 Token","89b19028-256b-4025-8872-255358d950e4":"Sentry Enterprises CTAP2 Authenticator","4e768f2c-5fab-48b3-b300-220eb487752b":"Hideez Key 4 FIDO2 SDK","47ab2fb4-66ac-4184-9ae1-86be814012d5":"Security Key NFC by Yubico - Enterprise Edition","931327dd-c89b-406c-a81e-ed7058ef36c6":"Swissbit iShield Key FIDO2","f8d5c4e9-e539-4c06-8662-ec2a4155a555":"StarSign Key Fob","b7d3f68e-88a6-471e-9ecf-2df26d041ede":"Security Key NFC by Yubico","8d1b1fcb-3c76-49a9-9129-5515b346aa02":"IDEMIA ID-ONE Card","30b5035e-d297-4ff7-020b-addc96ba6a98":"OneSpan DIGIPASS FX7","454e5346-4944-4ffd-6c93-8e9267193e9a":"Ensurity ThinC","e1a96183-5016-4f24-b55b-e3ae23614cc6":"ATKey.Pro CTAP2.0","9ff4cc65-6154-4fff-ba09-9e2af7882ad2":"Security Key NFC by Yubico - Enterprise Edition (Enterprise Profile)","4599062e-6926-4fe7-9566-9e8fb1aedaa0":"YubiKey 5 Series (Enterprise Profile)","9d3df6ba-282f-11ed-a261-0242ac120002":"Arculus FIDO2/U2F Key Card","fbefdf68-fe86-0106-213e-4d5fa24cbe2e":"Excelsecu eSecu FIDO2 NFC Security Key","62e54e98-c209-4df3-b692-de71bb6a8528":"YubiKey 5 FIPS Series with NFC Preview","ab32f0c6-2239-afbb-c470-d2ef4e254db7":"TOKEN2 FIDO2 Security Key","ce6bf97f-9f69-4ba7-9032-97adc6ca5cf1":"YubiKey 5 FIPS Series with NFC (RC Preview)","ad08c78a-4e41-49b9-86a2-ac15b06899e2":"YubiKey Bio Series - FIDO Edition (Enterprise Profile)","930b0c03-ef46-4ac4-935c-538dccd1fcdb":"Chipwon Clife Key","7787a482-13e8-4784-8a06-c7ed49a7aaf4":"Swissbit iShield Key 2","72c6b72d-8512-4c66-8359-9d3d10d9222f":"Security Key NFC by Yubico - Enterprise Edition (Enterprise Profile)","99ed6c29-4573-4847-816d-78ad8f1c75ef":"VeroCard FIDO2 Authenticator","973446ca-e21c-9a9b-99f5-9b985a67af0f":"ACS FIDO Authenticator Card","74820b05-a6c9-40f9-8fb0-9f86aca93998":"SafeNet eToken Fusion","1105e4ed-af1d-02ff-ffff-ffffffffffff":"Egomet FIDO2 Authenticator for Android","08987058-cadc-4b81-b6e1-30de50dcbe96":"Windows Hello","a4e9fc6d-4cbe-4758-b8ba-37598bb5bbaa":"Security Key NFC by Yubico","0acf3011-bc60-f375-fb53-6f05f43154e0":"Nymi FIDO2 Authenticator","d91c5288-0ef0-49b7-b8ae-21ca0aa6b3f3":"KEY-ID FIDO2 Authenticator","8eec9bf9-486c-46da-9a67-1fbb4f66b9ed":"HID Crescendo 4000 FIPS","4c50ff10-1057-4fc6-b8ed-43a529530c3c":"ImproveID Authenticator","c611b55c-77b2-4527-8082-590e931b2f08":"GoTrust Idem Key (Consumer profile)","ee041bce-25e5-4cdb-8f86-897fd6418464":"Feitian ePass FIDO2-NFC Authenticator","4b89f401-464e-4745-a520-486ddfc5d80e":"IIST FIDO2 Authenticator","2cd2f727-f6ca-44da-8f48-5c2e5da000a2":"Nitrokey 3 AM","10c70715-2a9a-4de1-b0aa-3cff6d496d39":"eToken Fusion NFC FIPS","efb96b10-a9ee-4b6c-a4a9-d32125ccd4a4":"Safenet eToken FIDO","4b3f8944-d4f2-4d21-bb19-764a986ec160":"KeyXentic FIDO2 Secp256R1 FIDO2 CTAP2 Authenticator","4c0cf95d-2f40-43b5-ba42-4c83a11c04ba":"Feitian BioPass FIDO2 Pro Authenticator","5343502d-5343-5343-6172-644649444f32":"ESS Smart Card Inc. Authenticator","69e7c36f-f2f6-9e0d-07a6-bcc243262e6b":"OneKey FIDO2 Authenticator","09591fc6-9811-48f7-8f57-b9f23df6413f":"Pone Biometrics OFFPAD Authenticator","912435d9-4a88-42f3-972d-1244b0d51420":"SI0X FIDO CL WRIST v1.0","7e3f3d30-3557-4442-bdae-139312178b39":"RSA DS100","73bb0cd4-e502-49b8-9c6f-b59445bf720b":"YubiKey 5 FIPS Series","39589099-9a75-49fc-afaa-801ca211c62a":"Feitian ePass FIDO-NFC (Enterprise Profile) (CTAP2.1, CTAP2.0, U2F)","149a2021-8ef6-4133-96b8-81f8d5b7f1f5":"Security Key by Yubico with NFC","5df66f62-5b47-43d3-aa1d-a6e31c8dbeb5":"Securitag Assembly Group FIDO Authenticator NFC","09619fbf-d75e-4a62-be1d-fe4d240864ae":"VeriMark(TM) Guard 2.1 Fingerprint Security Key","50cbf15a-238c-4457-8f16-812c43bf3c49":"Ensurity AUTH TouchPro","ee7fa1e0-9539-432f-bd43-9c2fc6d4f311":"VeriMark NFC+ USB-C Security Key","b90e7dc1-316e-4fee-a25a-56a666a670fe":"YubiKey 5 Series with Lightning (Enterprise Profile)","175cd298-83d2-4a26-b637-313c07a6434e":"Chunghwa Telecom FIDO2 Smart Card Authenticator","34744913-4f57-4e6e-a527-e9ec3c4b94e6":"YubiKey Bio Series - Multi-protocol Edition","5ea308b2-7ac7-48b9-ac09-7e2da9015f8c":"Veridium Android SDK","3b1adb99-0dfe-46fd-90b8-7f7614a4de2a":"GoTrust Idem Key FIDO2 Authenticator","46544d5d-8f5d-4db4-89ac-ea8977073fff":"Foongtone FIDO Authenticator","998f358b-2dd2-4cbe-a43a-e8107438dfb3":"OnlyKey Secp256R1 FIDO2 CTAP2 Authenticator","30b5035e-d297-4ff2-010b-addc96ba6a98":"OneSpan DIGIPASS FX2-A","817cdab8-0d51-4de1-a821-e25b88519cf3":"Swissbit iShield Key 2 FIPS","61250591-b2bc-4456-b719-0b17be90bb30":"eWBM eFPA FIDO2 Authenticator","8c39ee86-7f9a-4a95-9ba3-f6b097e5c2ee":"YubiKey Bio Series - FIDO Edition (Enterprise Profile)","f8a011f3-8c0a-4d15-8006-17111f9edc7d":"Security Key by Yubico","8976631b-d4a0-427f-5773-0ec71c9e0279":"Solo Tap Secp256R1 FIDO2 CTAP2 Authenticator","516d3969-5a57-5651-5958-4e7a49434167":"SmartDisplayer BobeePass FIDO2 Authenticator","8681a073-5f50-4d52-bce4-e21658d207b3":"RSA Authenticator 4 for iOS","30b5035e-d297-4ff7-030b-addc96ba6a98":"OneSpan DIGIPASS FX7-C","e41b42a3-60ac-4afb-8757-a98f2d7f6c9f":"Deepnet SafeKey/Classic (FP)","c89e6a38-6c00-5426-5aa5-c9cbf48f0382":"ACS FIDO Authenticator NFC","a02167b9-ae71-4ac7-9a07-06432ebb6f1c":"YubiKey 5 Series with Lightning","82b0a720-127a-4788-b56d-d1d4b2d82eac":"ID-One Key","2c0df832-92de-4be1-8412-88a8f074df4a":"Feitian FIDO Smart Card","59f85fe7-faa5-4c92-9f52-697b9d4d5473":"RSA Authenticator 4 for Android","79f3c8ba-9e35-484b-8f47-53a5a0f5c630":"YubiKey 5 FIPS Series with NFC (Enterprise Profile)","def8ab1a-9f91-44f1-a103-088d8dc7d681":"IDEMIA SOLVO Fly 80 R3 FIDO Card e","970c8d9c-19d2-46af-aa32-3f448db49e35":"WinMagic FIDO Eazy - TPM","c5703116-972b-4851-a3e7-ae1259843399":"NEOWAVE Badgeo FIDO2","c80dbd9a-533f-4a17-b941-1a2f1c7cedff":"HID Crescendo C3000","0b8b05a4-ebd4-4b0b-8f5f-33d7b6e606ab":"HID Crescendo 4000","5b0e46ba-db02-44ac-b979-ca9b84f5e335":"YubiKey 5 FIPS Series with Lightning Preview","12755c32-8ad1-46eb-881c-e0b38d848b09":"Feitian ePass FIDO Authenticator (CTAP2.1, CTAP2.0, U2F)","2a55aee6-27cb-42c0-bc6e-04efe999e88a":"HID Crescendo 4000","820d89ed-d65a-409e-85cb-f73f0578f82a":"IDmelon Authenticator","019614a3-2703-7e35-a453-285fd06c5d24":"ATLKey Authenticator","3124e301-f14e-4e38-876d-fbeeb090e7bf":"YubiKey 5 Series with Lightning Preview","b6ede29c-3772-412c-8a78-539c1f4c62d2":"Feitian BioPass FIDO2 Plus Authenticator","ed042a3a-4b22-4455-bb69-a267b652ae7e":"Security Key NFC by Yubico - Enterprise Edition","b2c1a50b-dad8-4dc7-ba4d-0ce9597904bc":"YubiKey 5 Series with NFC - Enhanced PIN (Enterprise Profile)","85203421-48f9-4355-9bc8-8a53846e5083":"YubiKey 5 FIPS Series with Lightning","fcc0118f-cd45-435b-8da1-9782b2da0715":"YubiKey 5 FIPS Series with NFC","d821a7d4-e97c-4cb6-bd82-4237731fd4be":"Hyper FIDO Bio Security Key","9876631b-d4a0-427f-5773-0ec71c9e0279":"Somu Secp256R1 FIDO2 CTAP2 Authenticator","f56f58b3-d711-4afc-ba7d-6ac05f88cb19":"WinMagic FIDO Eazy - Phone","6ec5cff2-a0f9-4169-945b-f33b563f7b99":"YubiKey Bio Series - Multi-protocol Edition (Enterprise Profile)","882adaf5-3aa9-4708-8e7d-3957103775b4":"T-Shield TrustSec FIDO2 Bio and client PIN version","49a15c1c-3f63-3f51-23a7-b9e00096edd1":"IDEX CTAP2.1 Biometrics","f4c63eff-d26c-4248-801c-3736c7eaa93a":"FIDO KeyPass S3","d384db22-4d50-ebde-2eac-5765cf1e2a44":"Excelsecu eSecu FIDO2 Fingerprint Security Key","0db01cd6-5618-455b-bb46-1ec203d3213e":"GoldKey Security Token","b93fd961-f2e6-462f-b122-82002247de78":"Android Authenticator","aa79f476-ea00-417e-9628-1e8365123922":"HID Crescendo 4000 FIDO","1e906e14-77af-46bc-ae9f-fe6ef18257e4":"VeridiumID Passkey iOS SDK","2fc0579f-8113-47ea-b116-bb5a8db9202a":"YubiKey 5 Series with NFC","31c3f7ff-bf15-4327-83ec-9336abcbcd34":"WinMagic FIDO Eazy - Software","9ddd1817-af5a-4672-a2b9-3e3dd95000a9":"Windows Hello","d8522d9f-575b-4866-88a9-ba99fa02f35b":"YubiKey Bio Series - FIDO Edition","050dd0bc-ff20-4265-8d5d-305c4b215192":"eToken Fusion FIPS","50a45b0c-80e7-f944-bf29-f552bfa2e048":"ACS FIDO Authenticator","f7c558a0-f465-11e8-b568-0800200c9a66":"KONAI Secp256R1 FIDO2 Conformance Testing CTAP2 Authenticator","3f59672f-20aa-4afe-b6f4-7e5e916b6d98":"Arculus FIDO 2.1 Key Card [P71]","42b4fb4a-2866-43b2-9bf7-6c6669c2e5d3":"Google Titan Security Key v2","361a3082-0278-4583-a16f-72a527f973e4":"eWBM eFA500 FIDO2 Authenticator","2ffd6452-01da-471f-821b-ea4bf6c8676a":"IDPrime 941 Fido","30b5035e-d297-4ff7-b00b-addc96ba6a98":"OneSpan DIGIPASS FX7","5eaff75a-dd43-451f-af9f-87c9eeae293e":"Swissbit iShield Key 2 FIPS Enterprise","b415094c-49d3-4c8b-b3fe-7d0ad28a6bc4":"ZTPass SmartAuth","692db549-7ae5-44d5-a1e5-dd20a493b723":"HID Crescendo Key","23315ad0-6aca-4ba1-952e-f044f1e36976":"Clife Key 2 NFC","1d1b4e33-76a1-47fb-97a0-14b10d0933f1":"Cryptnox FIDO2.1","bbf4b6a7-679d-f6fc-c4f2-8ac0ddf9015a":"Excelsecu eSecu FIDO2 PRO Security Key","3e22415d-7fdf-4ea4-8a0c-dd60c4249b9d":"Feitian iePass FIDO Authenticator","23786452-f02d-4344-87ed-aaf703726881":"SafeNet eToken Fusion CC","5e264d9d-28ef-4d34-95b4-5941e7a4faa8":"Ideem ZSM FIDO2 Authenticator","d2fbd093-ee62-488d-9dad-1e36389f8826":"YubiKey 5 FIPS Series (RC Preview)","234cd403-35a2-4cc2-8015-77ea280c77f5":"Feitian ePass FIDO2-NFC Series (CTAP2.1, CTAP2.0, U2F)","6999180d-630c-442d-b8f7-424b90a43fae":"Hyper FIDO Pro (CTAP2.1, CTAP2.0, U2F)","662ef48a-95e2-4aaa-a6c1-5b9c40375824":"YubiKey 5 Series with NFC - Enhanced PIN","aeb6569c-f8fb-4950-ac60-24ca2bbe2e52":"HID Crescendo C2300","87dbc5a1-4c94-4dc8-8a47-97d800fd1f3c":"eWBM eFA320 FIDO2 Authenticator","58276709-bb4b-4bb3-baf1-60eea99282a7":"YubiKey Bio Series - Multi-protocol Edition 1VDJSN","7d2afadd-bf6b-44a2-a66b-e831fceb8eff":"Taglio CTAP2.1 EP","30b5035e-d297-4ff1-020b-addc96ba6a98":"OneSpan DIGIPASS FX1-C","20ac7a17-c814-4833-93fe-539f0d5e3389":"YubiKey 5 Series (Enterprise Profile)","9012593f-43e4-4461-a97a-d92777b55d74":"VinCSS FIDO2 Fingerprint","d7781e5d-e353-46aa-afe2-3ca49f13332a":"YubiKey 5 Series with NFC","9f0d8150-baa5-4c00-9299-ad62c8bb4e87":"GoTrust Idem Card FIDO2 Authenticator","12ded745-4bed-47d4-abaa-e713f51d6393":"Feitian AllinOne FIDO2 Authenticator","88bbd2f0-342a-42e7-9729-dd158be5407a":"Precision InnaIT Key FIDO 2 Level 2 certified","1d8cac46-47a1-3386-af50-e88ae46fe802":"Ledger Flex FIDO2 Authenticator","dd86a2da-86a0-4cbe-b462-4bd31f57bc6f":"YubiKey Bio Series - FIDO Edition","773c30d9-5919-4e96-a4f5-db65e95cf890":"GSTAG OAK FIDO2 Authenticator","34f5766d-1536-4a24-9033-0e294e510fb0":"YubiKey 5 Series with NFC Preview","83c47309-aabb-4108-8470-8be838b573cb":"YubiKey Bio Series - FIDO Edition (Enterprise Profile)","4e2ddbc2-2687-4709-8551-cb66c9776bfe":"SECORA ID V2 FIDO2.1 L1","be727034-574a-f799-5c76-0929e0430973":"Crayonic KeyVault K1 (USB-NFC-BLE FIDO2 Authenticator)","092277e5-8437-46b5-b911-ea64b294acb7":"Taglio CTAP2.1 CS","ca87cb70-4c1b-4579-a8e8-4efdd7c007e0":"FIDO Alliance TruU Sample FIDO2 Authenticator","23195a52-62d9-40fa-8ee5-23b173f4fb52":"Hyper FIDO Pro NFC","a7fc3f84-86a3-4da4-a3d7-eb6485a066d8":"NEOWAVE Badgeo FIDO2 (CTAP 2.1)","9e66c661-e428-452a-a8fb-51f7ed088acf":"YubiKey 5 FIPS Series with Lightning (RC Preview)","58b44d0b-0a7c-f33a-fd48-f7153c871352":"Ledger Nano S Plus FIDO2 Authenticator","454e5346-4944-4ffd-6c93-8e9267193e9b":"Ensurity AUTH BioPro","146e77ef-11eb-4423-b847-ce77864e9411":"eToken Fusion NFC PIV","13ac47cf-1d78-4fd5-9060-aedaabacf826":"HID Crescendo Key V3 - Enterprise Edition","e77e3c64-05e3-428b-8824-0cbeb04b829d":"Security Key NFC by Yubico","33d6d7d0-279f-4ef3-96b3-2d3282f4bde6":"Thales eToken Fusion BIO Enterprise","8d4378b0-725d-4432-b3c2-01fcdaf46286":"VeridiumID Passkey Android SDK","7409272d-1ff9-4e10-9fc9-ac0019c124fd":"YubiKey Bio Series - FIDO Edition","bb66c294-de08-47e4-b7aa-d12c2cd3fb20":"Mettlesemi Vishwaas Hawk Authenticator using FIDO2","c4ddaf11-3032-4e77-b3b9-3a340369b9ad":"HID Crescendo Fusion","7d1351a6-e097-4852-b8bf-c9ac5c9ce4a3":"YubiKey Bio Series - Multi-protocol Edition","07a9f89c-6407-4594-9d56-621d5f1e358b":"NXP Semiconductros FIDO2 Conformance Testing CTAP2 Authenticator","d61d3b87-3e7c-4aea-9c50-441c371903ad":"KeyVault Secp256R1 FIDO2 CTAP2 Authenticator","c62100de-759b-4bf8-b22b-63b3e3a80401":"Token Ring 3 FIDO2 Authenticator","5ca1ab1e-1337-fa57-f1d0-a117e71ca702":"Allthenticator iOS App: roaming BLE FIDO2 Allthenticator for Windows, Mac, Linux, and Allthenticate door readers","b92c3f9a-c014-4056-887f-140a2501163b":"Security Key by Yubico","54d9fee8-e621-4291-8b18-7157b99c5bec":"HID Crescendo Enabled","a25342c0-3cdc-4414-8e46-f4807fca511c":"YubiKey 5 Series with NFC","3a662962-c6d4-4023-bebb-98ae92e78e20":"YubiKey 5 FIPS Series with Lightning (Enterprise Profile)","20f0be98-9af9-986a-4b42-8eca4acb28e4":"Excelsecu eSecu FIDO2 Fingerprint Security Key","ca4cff1b-5a81-4404-8194-59aabcf1660b":"IDPrime 3930 FIDO","ab32f0c6-2239-afbb-c470-d2ef4e254db6":"TEST (DUMMY RECORD)","760eda36-00aa-4d29-855b-4012a182cdeb":"Security Key NFC by Yubico Preview","6028b017-b1d4-4c02-b4b3-afcdafc96bb2":"Windows Hello","b12eac35-586c-4809-a4b1-d81af6c305cf":"Deepnet SafeKey/Classic (NFC)","30b5035e-d297-4fc1-b00b-addc96ba6a97":"OneSpan FIDO Touch","560a780c-b6ae-4f03-b110-082f856425b4":"KQC QuKey Bio FIDO2 Authenticator","1ac71f64-468d-4fe0-bef1-0e5f2f551f18":"YubiKey 5 Series with NFC (Enterprise Profile)","6d44ba9b-f6ec-2e49-b930-0c8fe920cb73":"Security Key by Yubico with NFC","9eb85bb6-9625-4a72-815d-0487830ccab2":"Ensurity AUTH BioPro Desktop","30b5035e-d297-4ff7-010b-addc96ba6a98":"OneSpan DIGIPASS FX7-B","5ca1ab1e-fa57-1337-f1d0-a117371ca702":"Allthenticator Android App: roaming BLE FIDO2 Allthenticator for Windows, Mac, Linux, and Allthenticate door readers","eabb46cc-e241-80bf-ae9e-96fa6d2975cf":"TOKEN2 PIN Plus Security Key Series ","53414d53-554e-4700-0000-000000000000":"Samsung Pass","e416201b-afeb-41ca-a03d-2281c28322aa":"ATKey.Pro CTAP2.1","905b4cb4-ed6f-4da9-92fc-45e0d4e9b5c7":"YubiKey 5 FIPS Series (Enterprise Profile)","cfcb13a2-244f-4b36-9077-82b79d6a7de7":"USB/NFC Passcode Authenticator","76692dc1-c56a-48d9-8e7d-31b5ced430ac":"VeriMark NFC+ USB-A Security Key","91ad6b93-264b-4987-8737-3a690cad6917":"Token Ring FIDO2 Authenticator","a02140b7-0cbd-42e1-a9b5-a39da2545114":"Feitian BioPass FIDO2 Plus (Enterprise Profile)","5753362b-4e6b-6345-7b2f-255438404c75":"WiSECURE Blentity FIDO2 Authenticator","9f77e279-a6e2-4d58-b700-31e5943c6a98":"Hyper FIDO Pro","b9f6b7b6-f929-4189-bca9-dd951240c132":"Deepnet SafeKey/Classic (USB)","cc45f64e-52a2-451b-831a-4edd8022a202":"ToothPic Passkey Provider","0bb43545-fd2c-4185-87dd-feb0b2916ace":"Security Key NFC by Yubico - Enterprise Edition","73402251-f2a8-4f03-873e-3cb6db604b03":"uTrust FIDO2 Security Key","c1f9a0bc-1dd2-404a-b27f-8e29047a43fd":"YubiKey 5 FIPS Series with NFC","70e7c36f-f2f6-9e0d-07a6-bcc243262e6b":"OneKey FIDO2 Bluetooth Authenticator","4fc84f16-2545-4e53-b8fc-7bf4d7282a10":"YubiKey 5 CCN Series with NFC (Enterprise Profile)","6ab56fad-881f-4a43-acb2-0be065924522":"YubiKey 5 Series with NFC (Enterprise Profile)","504d7149-4e4c-3841-4555-55445a677357":"WiSECURE AuthTron USB FIDO2 Authenticator","2c2aeed8-8174-4159-814b-486e92a261d0":"NEOWAVE WINKEO V2.0","f2145e86-211e-4931-b874-e22bba7d01cc":"ID-One Key","a3975549-b191-fd67-b8fb-017e2917fdb3":"Excelsecu eSecu FIDO2 NFC Security Key","19083c3d-8383-4b18-bc03-8f1c9ab2fd1b":"YubiKey 5 Series","da1fa263-8b25-42b6-a820-c0036f21ba7f":"ATKey.Card NFC","6002f033-3c07-ce3e-d0f7-0ffe5ed42543":"Excelsecu eSecu FIDO2 Fingerprint Key","5fdb81b8-53f0-4967-a881-f5ec26fe4d18":"VinCSS FIDO2 Authenticator","78ba3993-d784-4f44-8d6e-cc0a8ad5230e":"Feitian ePass FIDO-NFC(CTAP2.1, CTAP2.0, U2F)","57f7de54-c807-4eab-b1c6-1c9be7984e92":"YubiKey 5 FIPS Series","bb405265-40cf-4115-93e5-a332c1968d8c":"ID-One Card","2d3bec26-15ee-4f5d-88b2-53622490270b":"HID Crescendo Key V2","489ff376-b48d-6640-bb69-782a860ca795":"Mettlesemi Vishwaas Eagle Authenticator using FIDO2","3b24bf49-1d45-4484-a917-13175df0867b":"YubiKey 5 Series with Lightning (Enterprise Profile)","30b5035e-d297-4ff1-010b-addc96ba6a98":"OneSpan DIGIPASS FX1a","cb69481e-8ff7-4039-93ec-0a2729a154a8":"YubiKey 5 Series","0076631b-d4a0-427f-5773-0ec71c9e0279":"HYPR FIDO2 Authenticator","d716019a-9f4e-4041-9750-17c78f8ae81a":"eToken Fusion BIO","57235694-51a5-4a4d-a81a-f42185df6502":"SHALO AUTH","24673149-6c86-42e7-98d9-433fb5b73296":"YubiKey 5 Series with Lightning","42df17de-06ba-4177-a2bb-6701be1380d6":"Feitian BioPass FIDO2 Plus Authenticator","d7a423ad-3e19-4492-9200-78137dccc136":"VivoKey Apex FIDO2","ba76a271-6eb6-4171-874d-b6428dbe3437":"ATKey.ProS","97e6a830-c952-4740-95fc-7c78dc97ce47":"YubiKey Bio Series - Multi-protocol Edition (Enterprise Profile)","f573f209-b7fb-b261-671a-d7cf624cc812":"Excelsecu eSecu FIDO2 PRO+ Security Key","6e24d385-004a-16a0-7bfe-efd963845b34":"Ledger Stax FIDO2 Authenticator","ee882879-721c-4913-9775-3dfcce97072a":"YubiKey 5 Series","8876631b-d4a0-427f-5773-0ec71c9e0279":"Solo Secp256R1 FIDO2 CTAP2 Authenticator","fec067a1-f1d0-4c5e-b4c0-cc3237475461":"KX701 SmartToken FIDO","30b5035e-d297-4ff1-b00b-addc96ba6a98":"OneSpan DIGIPASS FX1 BIO","b267239b-954f-4041-a01b-ee4f33c145b6":"authenton1 - CTAP2.1","b50d5e0a-7f81-4959-9b12-f45407407503":"IDPrime 3940 FIDO","8c97a730-3f7b-41a6-87d6-1e9b62bda6f0":"FT-JCOS FIDO Fingerprint Card","99bf4610-ec26-4252-b31f-7380ccd59db5":"ZTPass SmartAuth","a1f52be5-dfab-4364-b51c-2bd496b14a56":"OCTATCO EzFinger2 FIDO2 AUTHENTICATOR","0f00cc22-4640-41e7-9585-384ec73ffe9b":"Taglio CTAP2.1 BIO","ff4dac45-ede8-4ec2-aced-cf66103f4335":"YubiKey 5 Series","ba86dc56-635f-4141-aef6-00227b1b9af6":"TruU Windows Authenticator","3e078ffd-4c54-4586-8baa-a77da113aec5":"Hideez Key 3 FIDO2","fc5ca237-69a0-4f3c-afe4-1ebc66def6df":"Clife Key 2","ec31b4cc-2acc-4b8e-9c01-bade00ccbe26":"KeyXentic FIDO2 Secp256R1 FIDO2 CTAP2 Authenticator","5d629218-d3a5-11ed-afa1-0242ac120002":"Swissbit iShield Key Pro","bb878d7b-cf54-4784-b390-357030497043":"TruU FIDO2 Authenticator","d41f5a69-b817-4144-a13c-9ebd6d9254d6":"ATKey.Card CTAP2.0","e86addcd-7711-47e5-b42a-c18257b0bf61":"IDCore 3121 Fido","b113a455-cfb6-4c17-8cba-cd952feb7d48":"eToken FIDO NFC","95442b2e-f15e-4def-b270-efb106facb4e":"eWBM eFA310 FIDO2 Authenticator","dda9aa35-aaf1-4d3c-b6db-7902fd7dbbbf":"IDEMIA SOLVO Fly 80 R3 FIDO Card c","cdbdaea2-c415-5073-50f7-c04e968640b6":"Excelsecu eSecu FIDO2 Security Key","3aa78eb1-ddd8-46a8-a821-8f8ec57a7bd5":"YubiKey 5 CCN Series with NFC","bc2fe499-0d8e-4ffe-96f3-94a82840cf8c":"OCTATCO EzQuant FIDO2 AUTHENTICATOR","eb3b131e-59dc-536a-d176-cb7306da10f5":"ellipticSecure MIRkey USB Authenticator","3fd410dc-8ab7-4b86-a1cb-c7174620b2dc":"IDEMIA SOLVO Fly 80 R1 FIDO Card Draft","a6c5f5d8-2ad0-48b6-8257-e502c8970931":"eToken FIDO NFC Enterprise","e400ef8c-711d-4692-af46-7f2cf7da23ad":"Swissbit iShield Key 2 Enterprise","87c13177-85d6-40ac-8c61-fe7ab3de9dfb":"HID Crescendo Key V3","1c086528-58d5-f211-823c-356786e36140":"Atos CardOS FIDO2","77010bd7-212a-4fc9-b236-d2ca5e9d4084":"Feitian BioPass FIDO2 Authenticator","d94a29d9-52dd-4247-9c2d-8b818b610389":"VeriMark Guard Fingerprint Key","7b96457d-e3cd-432b-9ceb-c9fdd7ef7432":"YubiKey 5 FIPS Series with Lightning","7991798a-a7f3-487f-98c0-3faf7a458a04":"HID Crescendo Key V3","833b721a-ff5f-4d00-bb2e-bdda3ec01e29":"Feitian ePass FIDO2 Authenticator","c89674e3-a765-4b07-888a-7c086fbdf04b":"StarSign FIDO Card","a11a5faa-9f32-4b8c-8c5d-2f7d13e8c942":"AliasVault","ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4":"Google Password Manager","adce0002-35bc-c60a-648b-0b25f1f05503":"Chrome on Mac","dd4ec289-e01d-41c9-bb89-70fa845d4bf2":"iCloud Keychain (Managed)","531126d6-e717-415c-9320-3d9aa6981239":"Dashlane","bada5566-a7aa-401f-bd96-45619a55120d":"1Password","b84e4048-15dc-4dd0-8640-f4f60813c8af":"NordPass","0ea242b4-43c4-4a1b-8b17-dd6d0b6baec6":"Keeper","891494da-2c90-4d31-a9cd-4eab0aed1309":"Sésame","f3809540-7f14-49c1-a8b3-8f813b225541":"Enpass","b5397666-4885-aa6b-cebf-e52262a439a2":"Chromium Browser","771b48fd-d3d4-4f74-9232-fc157ab0507a":"Edge on Mac","d548826e-79b4-db40-a3d8-11116f7e8349":"Bitwarden","fbfc3007-154e-4ecc-8c0b-6e020557d7bd":"Apple Passwords","66a0ccb3-bd6a-191f-ee06-e375c50b9846":"Thales Bio iOS SDK","8836336a-f590-0921-301d-46427531eee6":"Thales Bio Android SDK","cd69adb5-3c7a-deb9-3177-6800ea6cb72a":"Thales PIN Android SDK","17290f1e-c212-34d0-1423-365d729f09d9":"Thales PIN iOS SDK","50726f74-6f6e-5061-7373-50726f746f6e":"Proton Pass","fdb141b2-5d84-443e-8a35-4698c205a502":"KeePassXC","eaecdef2-1c31-5634-8639-f1cbd9c00a08":"KeePassDX","bfc748bb-3429-4faa-b9f9-7cfa9f3b76d0":"iPasswords","b35a26b2-8f6e-4697-ab1d-d44db4da28c6":"Zoho Vault","b78a0a55-6ef8-d246-a042-ba0f6d55050c":"LastPass","de503f9c-21a4-4f76-b4b7-558eb55c6f89":"Devolutions","22248c4c-7a12-46e2-9a41-44291b373a4d":"LogMeOnce","a10c6dd9-465e-4226-8198-c7c44b91c555":"Kaspersky Password Manager","d350af52-0351-4ba2-acd3-dfeeadc3f764":"pwSafe","d3452668-01fd-4c12-926c-83a4204853aa":"Microsoft Password Manager","6d212b28-a2c1-4638-b375-5932070f62e9":"initial","d49b2120-b865-4191-8cea-be84a52b0485":"Heimlane Vault","e8b7f4a2-c3d5-e6f7-890a-b1c2d3e4f567":"Sherlocked"} From d7f19ad5e5ac9c7d646323c60a13a50079a8c628 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 13:27:27 -0800 Subject: [PATCH 12/15] fix: wildcard callback URLs blocked by browser-native URL validation (#1359) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> --- .../settings/admin/oidc-clients/oidc-callback-url-input.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/routes/settings/admin/oidc-clients/oidc-callback-url-input.svelte b/frontend/src/routes/settings/admin/oidc-clients/oidc-callback-url-input.svelte index eb39257b..cc261fa9 100644 --- a/frontend/src/routes/settings/admin/oidc-clients/oidc-callback-url-input.svelte +++ b/frontend/src/routes/settings/admin/oidc-clients/oidc-callback-url-input.svelte @@ -31,7 +31,7 @@