From 599f7d118df0225abf96429c42c26d7c627942a3 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Wed, 22 Jul 2026 13:06:14 +0200 Subject: [PATCH 01/13] fix: horizontal shadow of cards in light mode cut off --- frontend/src/routes/settings/+layout.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/routes/settings/+layout.svelte b/frontend/src/routes/settings/+layout.svelte index ca62a70b..fa6b7506 100644 --- a/frontend/src/routes/settings/+layout.svelte +++ b/frontend/src/routes/settings/+layout.svelte @@ -63,7 +63,7 @@ -
+
{@render children()} From ad06ea6e008ea61edd5a84df0fbab3125ff047c7 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Wed, 22 Jul 2026 17:43:09 +0200 Subject: [PATCH 02/13] fix: datatype mismatch between postgres and sqlite causes import to fail --- backend/internal/service/e2etest_service.go | 55 +++++++ ...auth_storage_export_normalization.down.sql | 1 + ..._oauth_storage_export_normalization.up.sql | 1 + ...auth_storage_export_normalization.down.sql | 151 +++++++++++++++++ ..._oauth_storage_export_normalization.up.sql | 152 ++++++++++++++++++ tests/resources/export/database.json | 58 ++++++- tests/specs/cli.spec.ts | 4 +- 7 files changed, 418 insertions(+), 4 deletions(-) create mode 100644 backend/resources/migrations/postgres/20260722120000_oauth_storage_export_normalization.down.sql create mode 100644 backend/resources/migrations/postgres/20260722120000_oauth_storage_export_normalization.up.sql create mode 100644 backend/resources/migrations/sqlite/20260722120000_oauth_storage_export_normalization.down.sql create mode 100644 backend/resources/migrations/sqlite/20260722120000_oauth_storage_export_normalization.up.sql diff --git a/backend/internal/service/e2etest_service.go b/backend/internal/service/e2etest_service.go index 21572adc..0d25f85a 100644 --- a/backend/internal/service/e2etest_service.go +++ b/backend/internal/service/e2etest_service.go @@ -282,6 +282,61 @@ func (s *TestService) SeedDatabase(baseURL string) error { } } + farFuture := datatype.DateTime(time.Date(2099, 1, 1, 0, 0, 0, 0, time.UTC)) + oauth2Session := oidc.OAuth2Session{ + Base: model.Base{ + ID: "551ab785-c830-47d3-8a07-60c9f3bb4859", + }, + Kind: "access_token", + Key: "cross-database-test-session", + RequestID: "cross-database-test-request", + AccessTokenSignature: "", + Active: true, + RequestData: `{"request":"value"}`, + ExpiresAt: &farFuture, + } + if err := tx.Create(&oauth2Session).Error; err != nil { + return err + } + + if err := tx.Table("oauth2_jtis").Create(map[string]any{ + "id": "bd0c8bf2-66ec-487a-9dd5-7d9d78d73543", + "created_at": datatype.DateTime(time.Now()), + "jti": "cross-database-test-jti", + "expires_at": farFuture, + }).Error; err != nil { + return err + } + + interactionSession := oidc.InteractionSession{ + Base: model.Base{ + ID: "aaf5dd23-cd1f-4748-a2aa-baa6af94d800", + }, + Scopes: datatype.StringList{"openid"}, + ClientID: oidcClients[0].ID, + UserID: new(users[0].ID), + ConsentRequired: true, + RequestedAt: farFuture, + Parameters: oidc.InteractionSessionParameters{ + "client_id": oidcClients[0].ID, + }, + } + if err := tx.Create(&interactionSession).Error; err != nil { + return err + } + + reauthenticationToken := webauthn.ReauthenticationToken{ + Base: model.Base{ + ID: "71839ace-d978-4e6f-8fb1-b8648a21031b", + }, + Token: "cross-database-reauthentication-token", + ExpiresAt: farFuture, + UserID: users[0].ID, + } + if err := tx.Create(&reauthenticationToken).Error; err != nil { + return err + } + accessToken := model.OneTimeAccessToken{ Token: "one-time-token", ExpiresAt: datatype.DateTime(time.Now().Add(1 * time.Hour)), diff --git a/backend/resources/migrations/postgres/20260722120000_oauth_storage_export_normalization.down.sql b/backend/resources/migrations/postgres/20260722120000_oauth_storage_export_normalization.down.sql new file mode 100644 index 00000000..46a3724d --- /dev/null +++ b/backend/resources/migrations/postgres/20260722120000_oauth_storage_export_normalization.down.sql @@ -0,0 +1 @@ +-- No-op on PostgreSQL diff --git a/backend/resources/migrations/postgres/20260722120000_oauth_storage_export_normalization.up.sql b/backend/resources/migrations/postgres/20260722120000_oauth_storage_export_normalization.up.sql new file mode 100644 index 00000000..85df0180 --- /dev/null +++ b/backend/resources/migrations/postgres/20260722120000_oauth_storage_export_normalization.up.sql @@ -0,0 +1 @@ +-- No-op on PostgreSQL because its OAuth storage types already match the export format diff --git a/backend/resources/migrations/sqlite/20260722120000_oauth_storage_export_normalization.down.sql b/backend/resources/migrations/sqlite/20260722120000_oauth_storage_export_normalization.down.sql new file mode 100644 index 00000000..632ab194 --- /dev/null +++ b/backend/resources/migrations/sqlite/20260722120000_oauth_storage_export_normalization.down.sql @@ -0,0 +1,151 @@ +PRAGMA foreign_keys = OFF; +BEGIN; + +CREATE TABLE reauthentication_tokens_old ( + id TEXT PRIMARY KEY, + created_at DATETIME NOT NULL, + token TEXT NOT NULL UNIQUE, + expires_at INTEGER NOT NULL, + user_id TEXT NOT NULL REFERENCES users ON DELETE CASCADE +); + +INSERT INTO reauthentication_tokens_old ( + id, + created_at, + token, + expires_at, + user_id +) +SELECT + id, + created_at, + token, + expires_at, + user_id +FROM reauthentication_tokens; + +DROP TABLE reauthentication_tokens; +ALTER TABLE reauthentication_tokens_old RENAME TO reauthentication_tokens; + +CREATE INDEX idx_reauthentication_tokens_token ON reauthentication_tokens (token); +CREATE INDEX idx_reauthentication_tokens_expires_at ON reauthentication_tokens (expires_at); + +CREATE TABLE oauth2_sessions_old ( + id TEXT NOT NULL PRIMARY KEY, + created_at INTEGER NOT NULL, + kind TEXT NOT NULL, + key TEXT NOT NULL, + request_id TEXT NOT NULL, + access_token_signature TEXT NOT NULL DEFAULT '', + active BOOLEAN NOT NULL DEFAULT TRUE, + request_data TEXT NOT NULL, + expires_at INTEGER +); + +INSERT INTO oauth2_sessions_old ( + id, + created_at, + kind, + key, + request_id, + access_token_signature, + active, + request_data, + expires_at +) +SELECT + id, + created_at, + kind, + key, + request_id, + access_token_signature, + active, + CAST(request_data AS TEXT), + expires_at +FROM oauth2_sessions; + +DROP TABLE oauth2_sessions; +ALTER TABLE oauth2_sessions_old RENAME TO oauth2_sessions; + +CREATE UNIQUE INDEX idx_oauth2_sessions_kind_key ON oauth2_sessions (kind, key); +CREATE INDEX idx_oauth2_sessions_kind_request ON oauth2_sessions (kind, request_id); +CREATE INDEX idx_oauth2_sessions_expires_at ON oauth2_sessions (expires_at); + +CREATE TABLE oauth2_jtis_old ( + id TEXT NOT NULL PRIMARY KEY, + created_at INTEGER NOT NULL, + jti TEXT NOT NULL UNIQUE, + expires_at INTEGER NOT NULL +); + +INSERT INTO oauth2_jtis_old ( + id, + created_at, + jti, + expires_at +) +SELECT + id, + created_at, + jti, + expires_at +FROM oauth2_jtis; + +DROP TABLE oauth2_jtis; +ALTER TABLE oauth2_jtis_old RENAME TO oauth2_jtis; + +CREATE INDEX idx_oauth2_jtis_expires_at ON oauth2_jtis (expires_at); + +CREATE TABLE interaction_sessions_old ( + id TEXT NOT NULL PRIMARY KEY, + created_at INTEGER NOT NULL, + consent_required BOOLEAN NOT NULL DEFAULT FALSE, + reauthentication_required BOOLEAN NOT NULL DEFAULT FALSE, + authentication_required BOOLEAN NOT NULL DEFAULT FALSE, + account_selection_required BOOLEAN NOT NULL DEFAULT FALSE, + scopes TEXT NOT NULL DEFAULT '[]', + client_id TEXT NOT NULL REFERENCES oidc_clients(id) ON DELETE CASCADE, + user_id TEXT REFERENCES users(id) ON DELETE CASCADE, + requested_at INTEGER NOT NULL, + reauthenticated_at INTEGER, + parameters TEXT NOT NULL DEFAULT '{}' +); + +INSERT INTO interaction_sessions_old ( + id, + created_at, + consent_required, + reauthentication_required, + authentication_required, + account_selection_required, + scopes, + client_id, + user_id, + requested_at, + reauthenticated_at, + parameters +) +SELECT + id, + created_at, + consent_required, + reauthentication_required, + authentication_required, + account_selection_required, + CAST(scopes AS TEXT), + client_id, + user_id, + requested_at, + reauthenticated_at, + CAST(parameters AS TEXT) +FROM interaction_sessions; + +DROP TABLE interaction_sessions; +ALTER TABLE interaction_sessions_old RENAME TO interaction_sessions; + +CREATE INDEX idx_interaction_sessions_client_id ON interaction_sessions (client_id); +CREATE INDEX idx_interaction_sessions_user_id ON interaction_sessions (user_id); + +COMMIT; +PRAGMA foreign_keys = ON; diff --git a/backend/resources/migrations/sqlite/20260722120000_oauth_storage_export_normalization.up.sql b/backend/resources/migrations/sqlite/20260722120000_oauth_storage_export_normalization.up.sql new file mode 100644 index 00000000..4fba5aea --- /dev/null +++ b/backend/resources/migrations/sqlite/20260722120000_oauth_storage_export_normalization.up.sql @@ -0,0 +1,152 @@ +PRAGMA foreign_keys = OFF; +BEGIN; + +-- Align JSON and timestamp column types with the export format used for PostgreSQL +CREATE TABLE reauthentication_tokens_new ( + id TEXT PRIMARY KEY, + created_at DATETIME NOT NULL, + token TEXT NOT NULL UNIQUE, + expires_at DATETIME NOT NULL, + user_id TEXT NOT NULL REFERENCES users ON DELETE CASCADE +); + +INSERT INTO reauthentication_tokens_new ( + id, + created_at, + token, + expires_at, + user_id +) +SELECT + id, + created_at, + token, + expires_at, + user_id +FROM reauthentication_tokens; + +DROP TABLE reauthentication_tokens; +ALTER TABLE reauthentication_tokens_new RENAME TO reauthentication_tokens; + +CREATE INDEX idx_reauthentication_tokens_token ON reauthentication_tokens (token); +CREATE INDEX idx_reauthentication_tokens_expires_at ON reauthentication_tokens (expires_at); + +CREATE TABLE oauth2_sessions_new ( + id TEXT NOT NULL PRIMARY KEY, + created_at DATETIME NOT NULL, + kind TEXT NOT NULL, + key TEXT NOT NULL, + request_id TEXT NOT NULL, + access_token_signature TEXT NOT NULL DEFAULT '', + active BOOLEAN NOT NULL DEFAULT TRUE, + request_data BLOB NOT NULL, + expires_at DATETIME +); + +INSERT INTO oauth2_sessions_new ( + id, + created_at, + kind, + key, + request_id, + access_token_signature, + active, + request_data, + expires_at +) +SELECT + id, + created_at, + kind, + key, + request_id, + access_token_signature, + active, + CAST(request_data AS BLOB), + expires_at +FROM oauth2_sessions; + +DROP TABLE oauth2_sessions; +ALTER TABLE oauth2_sessions_new RENAME TO oauth2_sessions; + +CREATE UNIQUE INDEX idx_oauth2_sessions_kind_key ON oauth2_sessions (kind, key); +CREATE INDEX idx_oauth2_sessions_kind_request ON oauth2_sessions (kind, request_id); +CREATE INDEX idx_oauth2_sessions_expires_at ON oauth2_sessions (expires_at); + +CREATE TABLE oauth2_jtis_new ( + id TEXT NOT NULL PRIMARY KEY, + created_at DATETIME NOT NULL, + jti TEXT NOT NULL UNIQUE, + expires_at DATETIME NOT NULL +); + +INSERT INTO oauth2_jtis_new ( + id, + created_at, + jti, + expires_at +) +SELECT + id, + created_at, + jti, + expires_at +FROM oauth2_jtis; + +DROP TABLE oauth2_jtis; +ALTER TABLE oauth2_jtis_new RENAME TO oauth2_jtis; + +CREATE INDEX idx_oauth2_jtis_expires_at ON oauth2_jtis (expires_at); + +CREATE TABLE interaction_sessions_new ( + id TEXT NOT NULL PRIMARY KEY, + created_at DATETIME NOT NULL, + consent_required BOOLEAN NOT NULL DEFAULT FALSE, + reauthentication_required BOOLEAN NOT NULL DEFAULT FALSE, + authentication_required BOOLEAN NOT NULL DEFAULT FALSE, + account_selection_required BOOLEAN NOT NULL DEFAULT FALSE, + scopes BLOB NOT NULL DEFAULT X'5B5D', + client_id TEXT NOT NULL REFERENCES oidc_clients(id) ON DELETE CASCADE, + user_id TEXT REFERENCES users(id) ON DELETE CASCADE, + requested_at DATETIME NOT NULL, + reauthenticated_at DATETIME, + parameters BLOB NOT NULL DEFAULT X'7B7D' +); + +INSERT INTO interaction_sessions_new ( + id, + created_at, + consent_required, + reauthentication_required, + authentication_required, + account_selection_required, + scopes, + client_id, + user_id, + requested_at, + reauthenticated_at, + parameters +) +SELECT + id, + created_at, + consent_required, + reauthentication_required, + authentication_required, + account_selection_required, + CAST(scopes AS BLOB), + client_id, + user_id, + requested_at, + reauthenticated_at, + CAST(parameters AS BLOB) +FROM interaction_sessions; + +DROP TABLE interaction_sessions; +ALTER TABLE interaction_sessions_new RENAME TO interaction_sessions; + +CREATE INDEX idx_interaction_sessions_client_id ON interaction_sessions (client_id); +CREATE INDEX idx_interaction_sessions_user_id ON interaction_sessions (user_id); + +COMMIT; +PRAGMA foreign_keys = ON; diff --git a/tests/resources/export/database.json b/tests/resources/export/database.json index 37ebf554..e02a1bdb 100644 --- a/tests/resources/export/database.json +++ b/tests/resources/export/database.json @@ -1,7 +1,15 @@ { "provider": "sqlite", - "version": 20260708130000, - "tableOrder": ["users", "user_groups", "oidc_clients", "signup_tokens", "apis", "api_permissions", "oidc_clients_allowed_api_permissions"], + "version": 20260722120000, + "tableOrder": [ + "users", + "user_groups", + "oidc_clients", + "signup_tokens", + "apis", + "api_permissions", + "oidc_clients_allowed_api_permissions" + ], "tables": { "apis": [ { @@ -264,6 +272,52 @@ "user_id": "f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e" } ], + "oauth2_jtis": [ + { + "id": "bd0c8bf2-66ec-487a-9dd5-7d9d78d73543", + "created_at": "2026-07-22T12:00:00Z", + "jti": "cross-database-test-jti", + "expires_at": "2099-01-01T00:00:00Z" + } + ], + "oauth2_sessions": [ + { + "id": "551ab785-c830-47d3-8a07-60c9f3bb4859", + "created_at": "2026-07-22T12:00:00Z", + "kind": "access_token", + "key": "cross-database-test-session", + "request_id": "cross-database-test-request", + "access_token_signature": "", + "active": true, + "request_data": "eyJyZXF1ZXN0IjoidmFsdWUifQ==", + "expires_at": "2099-01-01T00:00:00Z" + } + ], + "interaction_sessions": [ + { + "id": "aaf5dd23-cd1f-4748-a2aa-baa6af94d800", + "created_at": "2026-07-22T12:00:00Z", + "consent_required": true, + "reauthentication_required": false, + "authentication_required": false, + "account_selection_required": false, + "scopes": "WyJvcGVuaWQiXQ==", + "client_id": "3654a746-35d4-4321-ac61-0bdcff2b4055", + "user_id": "f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e", + "requested_at": "2099-01-01T00:00:00Z", + "reauthenticated_at": null, + "parameters": "eyJjbGllbnRfaWQiOiIzNjU0YTc0Ni0zNWQ0LTQzMjEtYWM2MS0wYmRjZmYyYjQwNTUifQ==" + } + ], + "reauthentication_tokens": [ + { + "id": "71839ace-d978-4e6f-8fb1-b8648a21031b", + "created_at": "2026-07-22T12:00:00Z", + "token": "cross-database-reauthentication-token", + "expires_at": "2099-01-01T00:00:00Z", + "user_id": "f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e" + } + ], "signup_tokens": [ { "created_at": "2025-11-25T12:39:02Z", diff --git a/tests/specs/cli.spec.ts b/tests/specs/cli.spec.ts index 0e6e7234..5a730af9 100644 --- a/tests/specs/cli.spec.ts +++ b/tests/specs/cli.spec.ts @@ -59,7 +59,7 @@ test('Export via stdout', async ({ baseURL }) => { compareExports(exampleExportPath, stdoutExtractPath); }); -test('Import', async () => { +test('Import SQLite export', async () => { // Reset the backend without seeding await cleanupBackend({ skipSeed: true }); @@ -83,7 +83,7 @@ test('Import', async () => { compareExports(exampleExportPath, exportExtracted); }); -test('Import via stdin', async () => { +test('Import SQLite export via stdin', async () => { await cleanupBackend({ skipSeed: true }); const exampleExportArchivePath = path.join(tmpDir, 'example-export-stdin.zip'); From e10f66c07a8f4a8d42a03bb7edda14d4bf9ba31d Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Wed, 22 Jul 2026 18:19:07 +0200 Subject: [PATCH 03/13] fix: show only accessible clients on "My Apps" page --- backend/internal/service/e2etest_service.go | 3 ++ backend/internal/service/oidc_service.go | 22 +++------- backend/internal/service/oidc_service_test.go | 43 +++++++++++++++++++ 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/backend/internal/service/e2etest_service.go b/backend/internal/service/e2etest_service.go index 0d25f85a..22b36638 100644 --- a/backend/internal/service/e2etest_service.go +++ b/backend/internal/service/e2etest_service.go @@ -220,6 +220,9 @@ func (s *TestService) SeedDatabase(baseURL string) error { LogoutCallbackURLs: model.UrlList{"http://tailscale.localhost/auth/logout/callback"}, IsGroupRestricted: true, CreatedByID: new(users[0].ID), + AllowedUserGroups: []model.UserGroup{ + userGroups[0], + }, }, { Base: model.Base{ diff --git a/backend/internal/service/oidc_service.go b/backend/internal/service/oidc_service.go index 5f259a48..805e6076 100644 --- a/backend/internal/service/oidc_service.go +++ b/backend/internal/service/oidc_service.go @@ -587,23 +587,11 @@ func (s *OidcService) ListAccessibleOidcClients(ctx context.Context, userID stri query := tx. WithContext(ctx). Model(&model.OidcClient{}). - Preload("UserAuthorizedOidcClients", "user_id = ?", userID) - - // If user has no groups, only return clients with no allowed user groups - if len(userGroupIDs) == 0 { - query = query.Where(`NOT EXISTS ( - SELECT 1 FROM oidc_clients_allowed_user_groups - WHERE oidc_clients_allowed_user_groups.oidc_client_id = oidc_clients.id)`) - } else { - query = query.Where(` - NOT EXISTS ( - SELECT 1 FROM oidc_clients_allowed_user_groups - WHERE oidc_clients_allowed_user_groups.oidc_client_id = oidc_clients.id - ) OR EXISTS ( - SELECT 1 FROM oidc_clients_allowed_user_groups - WHERE oidc_clients_allowed_user_groups.oidc_client_id = oidc_clients.id - AND oidc_clients_allowed_user_groups.user_group_id IN (?))`, userGroupIDs) - } + Preload("UserAuthorizedOidcClients", "user_id = ?", userID). + Where(`oidc_clients.is_group_restricted = ? OR EXISTS ( + SELECT 1 FROM oidc_clients_allowed_user_groups + WHERE oidc_clients_allowed_user_groups.oidc_client_id = oidc_clients.id + AND oidc_clients_allowed_user_groups.user_group_id IN (?))`, false, userGroupIDs) var clients []model.OidcClient diff --git a/backend/internal/service/oidc_service_test.go b/backend/internal/service/oidc_service_test.go index 2d769e71..0f233c8a 100644 --- a/backend/internal/service/oidc_service_test.go +++ b/backend/internal/service/oidc_service_test.go @@ -14,6 +14,7 @@ import ( "github.com/pocket-id/pocket-id/backend/internal/dto" "github.com/pocket-id/pocket-id/backend/internal/model" "github.com/pocket-id/pocket-id/backend/internal/storage" + "github.com/pocket-id/pocket-id/backend/internal/utils" testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing" ) @@ -538,3 +539,45 @@ func TestOidcService_UpdateClient_description(t *testing.T) { require.NoError(t, err) assert.Empty(t, fetched.Description) } + +func TestOidcService_ListAccessibleOidcClients_requiresExplicitGroupPermission(t *testing.T) { + db := testutils.NewDatabaseForTest(t) + s, err := NewOidcService(db, nil, nil, nil, nil, nil) + require.NoError(t, err) + + allowedGroup := model.UserGroup{Name: "allowed", FriendlyName: "Allowed"} + otherGroup := model.UserGroup{Name: "other", FriendlyName: "Other"} + require.NoError(t, db.Create(&allowedGroup).Error) + require.NoError(t, db.Create(&otherGroup).Error) + + userWithGroup := model.User{Username: "with-group", UserGroups: []model.UserGroup{allowedGroup}} + userWithoutGroup := model.User{Username: "without-group"} + require.NoError(t, db.Create(&userWithGroup).Error) + require.NoError(t, db.Create(&userWithoutGroup).Error) + + clients := []model.OidcClient{ + {Name: "Unrestricted", CallbackURLs: model.UrlList{"https://unrestricted.example.com/callback"}}, + {Name: "Restricted without groups", CallbackURLs: model.UrlList{"https://empty.example.com/callback"}, IsGroupRestricted: true}, + {Name: "Restricted to user group", CallbackURLs: model.UrlList{"https://allowed.example.com/callback"}, IsGroupRestricted: true, AllowedUserGroups: []model.UserGroup{allowedGroup}}, + {Name: "Restricted to other group", CallbackURLs: model.UrlList{"https://other.example.com/callback"}, IsGroupRestricted: true, AllowedUserGroups: []model.UserGroup{otherGroup}}, + } + for i := range clients { + require.NoError(t, db.Create(&clients[i]).Error) + } + + groupClients, _, err := s.ListAccessibleOidcClients(t.Context(), userWithGroup.ID, utils.ListRequestOptions{}) + require.NoError(t, err) + assert.ElementsMatch(t, []string{"Unrestricted", "Restricted to user group"}, accessibleClientNames(groupClients)) + + noGroupClients, _, err := s.ListAccessibleOidcClients(t.Context(), userWithoutGroup.ID, utils.ListRequestOptions{}) + require.NoError(t, err) + assert.Equal(t, []string{"Unrestricted"}, accessibleClientNames(noGroupClients)) +} + +func accessibleClientNames(clients []dto.AccessibleOidcClientDto) []string { + names := make([]string, len(clients)) + for i := range clients { + names[i] = clients[i].Name + } + return names +} From f6b02efe4542cd3a57ba222c42fafe1e968a32d8 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Wed, 22 Jul 2026 18:40:51 +0200 Subject: [PATCH 04/13] chore: upgrade vulnerable dependencies --- backend/go.mod | 8 ++++---- backend/go.sum | 16 ++++++++-------- pnpm-lock.yaml | 12 ++++++------ tests/package.json | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index fa3d4e29..b92ffbb0 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -66,8 +66,8 @@ require ( require ( filippo.io/edwards25519 v1.2.0 // indirect github.com/Azure/go-ntlmssp v0.1.1 // indirect - github.com/ClickHouse/ch-go v0.61.5 // indirect - github.com/ClickHouse/clickhouse-go/v2 v2.30.0 // indirect + github.com/ClickHouse/ch-go v0.65.0 // indirect + github.com/ClickHouse/clickhouse-go/v2 v2.32.0 // indirect github.com/alphadose/haxmap v1.4.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect @@ -136,7 +136,7 @@ require ( github.com/h2non/filetype v1.1.3 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -172,7 +172,7 @@ require ( github.com/paulmach/orb v0.11.1 // indirect github.com/pelletier/go-toml/v2 v2.3.1 // indirect github.com/philhofer/fwd v1.2.0 // indirect - github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pkg/errors v0.9.1 // 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 101b5802..91323417 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -4,10 +4,10 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw= github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= -github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4= -github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg= -github.com/ClickHouse/clickhouse-go/v2 v2.30.0 h1:AG4D/hW39qa58+JHQIFOSnxyL46H6h2lrmGGk17dhFo= -github.com/ClickHouse/clickhouse-go/v2 v2.30.0/go.mod h1:i9ZQAojcayW3RsdCb3YR+n+wC2h65eJsZCscZ1Z1wyo= +github.com/ClickHouse/ch-go v0.65.0 h1:vZAXfTQliuNNefqkPDewX3kgRxN6Q4vUENnnY+ynTRY= +github.com/ClickHouse/ch-go v0.65.0/go.mod h1:tCM0XEH5oWngoi9Iu/8+tjPBo04I/FxNIffpdjtwx3k= +github.com/ClickHouse/clickhouse-go/v2 v2.32.0 h1:zVWJUmUGdtCApM/vRfQhruGXIm1M643bk68B3IYbR1I= +github.com/ClickHouse/clickhouse-go/v2 v2.32.0/go.mod h1:rGFIgeNbJVggBp2C+0FXOdfjsMlpsKx7FUYnHHyy2KE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= @@ -257,8 +257,8 @@ github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVU github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -419,8 +419,8 @@ github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7ol github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd0de042..a4ea037c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -211,8 +211,8 @@ importers: tests: dependencies: adm-zip: - specifier: ^0.5.17 - version: 0.5.18 + specifier: ^0.6.0 + version: 0.6.0 devDependencies: '@playwright/test': specifier: ^1.60.0 @@ -1476,9 +1476,9 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - adm-zip@0.5.18: - resolution: {integrity: sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==} - engines: {node: '>=12.0'} + adm-zip@0.6.0: + resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==} + engines: {node: '>=14.0'} agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} @@ -4146,7 +4146,7 @@ snapshots: acorn@8.17.0: {} - adm-zip@0.5.18: {} + adm-zip@0.6.0: {} agent-base@6.0.2: dependencies: diff --git a/tests/package.json b/tests/package.json index 9f9dcbcd..b7009527 100644 --- a/tests/package.json +++ b/tests/package.json @@ -15,6 +15,6 @@ "prettier": "^3.8.3" }, "dependencies": { - "adm-zip": "^0.5.17" + "adm-zip": "^0.6.0" } } From 2341f4fc4a55008103dfa0fb07f231ee537d9861 Mon Sep 17 00:00:00 2001 From: James18232 <180368042+James18232@users.noreply.github.com> Date: Thu, 23 Jul 2026 02:50:45 +1000 Subject: [PATCH 05/13] fix: autofocus one time input fields (#1605) Co-authored-by: james Co-authored-by: Kyle Mendell --- frontend/src/routes/login/alternative/code/+page.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/routes/login/alternative/code/+page.svelte b/frontend/src/routes/login/alternative/code/+page.svelte index 8799644d..d8f10a81 100644 --- a/frontend/src/routes/login/alternative/code/+page.svelte +++ b/frontend/src/routes/login/alternative/code/+page.svelte @@ -87,10 +87,11 @@ placeholder={m.code()} aria-label={m.code()} bind:value={code} + autofocus type="text" /> {:else} - + {#snippet children({ cells })} {#each cells as cell} From 6e859de2dd55dfc59d9cde3fab5d7a9789c6aca9 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Wed, 22 Jul 2026 19:01:17 +0200 Subject: [PATCH 06/13] tests(e2e): fix missing data in `database.json` --- tests/resources/export/database.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/resources/export/database.json b/tests/resources/export/database.json index e02a1bdb..23fdbba9 100644 --- a/tests/resources/export/database.json +++ b/tests/resources/export/database.json @@ -237,6 +237,10 @@ "oidc_client_id": "606c7782-f2b1-49e5-8ea9-26eb1b06d018", "user_group_id": "adab18bf-f89d-4087-9ee1-70ff15b48211" }, + { + "oidc_client_id": "7c21a609-96b5-4011-9900-272b8d31a9d1", + "user_group_id": "c7ae7c01-28a3-4f3c-9572-1ee734ea8368" + }, { "oidc_client_id": "c46d2090-37a0-4f2b-8748-6aa53b0c1afa", "user_group_id": "adab18bf-f89d-4087-9ee1-70ff15b48211" From c57c27a0041096a8ec05c244ecb7fad7b8f5894c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:21:42 +0200 Subject: [PATCH 07/13] chore(deps): Bump the "all-dependencies" group with 3 updates across multiple ecosystems (#1621) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-next.yml | 6 +- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/release.yml | 6 +- .github/workflows/svelte-check.yml | 2 +- backend/go.mod | 30 +- backend/go.sum | 64 +-- email-templates/package.json | 2 +- pnpm-lock.yaml | 820 +++++++++++++++++------------ 8 files changed, 534 insertions(+), 398 deletions(-) diff --git a/.github/workflows/build-next.yml b/.github/workflows/build-next.yml index e9316a5b..1e6742fe 100644 --- a/.github/workflows/build-next.yml +++ b/.github/workflows/build-next.yml @@ -33,7 +33,7 @@ jobs: run_install: false - name: Setup Node.js - uses: actions/setup-node@v6.4.0 + uses: actions/setup-node@v6.5.0 with: node-version: 24 cache: pnpm @@ -82,11 +82,11 @@ jobs: MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} - name: Binary attestation - uses: actions/attest@v4.1.1 + uses: actions/attest@v4.2.0 with: subject-checksums: ./dist/checksums.txt - name: Container image attestation - uses: actions/attest@v4.1.1 + uses: actions/attest@v4.2.0 with: subject-checksums: ./dist/digests.txt diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 37b6684b..dc84c9d3 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -45,7 +45,7 @@ jobs: uses: pnpm/action-setup@v5 - name: Setup Node.js - uses: actions/setup-node@v6.4.0 + uses: actions/setup-node@v6.5.0 with: node-version: 24 cache: "pnpm" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27f12087..5e405446 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: run_install: false - name: Setup Node.js - uses: actions/setup-node@v6.4.0 + uses: actions/setup-node@v6.5.0 with: node-version: 24 cache: pnpm @@ -76,12 +76,12 @@ jobs: DISCORD_WEBHOOK_TOKEN: ${{ secrets.DISCORD_WEBHOOK_TOKEN }} - name: Binary attestation - uses: actions/attest@v4.1.1 + uses: actions/attest@v4.2.0 with: subject-checksums: ./dist/checksums.txt - name: Container image attestation - uses: actions/attest@v4.1.1 + uses: actions/attest@v4.2.0 with: subject-checksums: ./dist/digests.txt diff --git a/.github/workflows/svelte-check.yml b/.github/workflows/svelte-check.yml index 20caaf37..0a055610 100644 --- a/.github/workflows/svelte-check.yml +++ b/.github/workflows/svelte-check.yml @@ -41,7 +41,7 @@ jobs: uses: pnpm/action-setup@v5 - name: Setup Node.js - uses: actions/setup-node@v6.4.0 + uses: actions/setup-node@v6.5.0 with: node-version: 24 cache: "pnpm" diff --git a/backend/go.mod b/backend/go.mod index b92ffbb0..e18eaf34 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -4,10 +4,10 @@ go 1.26.5 require ( github.com/aws/aws-sdk-go-v2 v1.42.1 - github.com/aws/aws-sdk-go-v2/config v1.32.29 - github.com/aws/aws-sdk-go-v2/credentials v1.19.28 - github.com/aws/aws-sdk-go-v2/service/s3 v1.105.0 - github.com/aws/smithy-go v1.27.3 + github.com/aws/aws-sdk-go-v2/config v1.32.30 + github.com/aws/aws-sdk-go-v2/credentials v1.19.29 + github.com/aws/aws-sdk-go-v2/service/s3 v1.105.2 + github.com/aws/smithy-go v1.27.4 github.com/caarlos0/env/v11 v11.4.1 github.com/cenkalti/backoff/v5 v5.0.3 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf @@ -19,7 +19,7 @@ require ( github.com/gin-gonic/gin v1.12.0 github.com/go-co-op/gocron/v2 v2.22.0 github.com/go-jose/go-jose/v4 v4.1.4 - github.com/go-ldap/ldap/v3 v3.4.13 + github.com/go-ldap/ldap/v3 v3.4.14 github.com/go-playground/validator/v10 v10.30.3 github.com/go-webauthn/webauthn v0.17.4 github.com/golang-migrate/migrate/v4 v4.19.1 @@ -33,8 +33,8 @@ require ( github.com/lestrrat-go/httprc/v3 v3.0.6 github.com/lestrrat-go/jwx/v3 v3.1.1 github.com/libtnb/sqlite v1.2.0 - github.com/lmittmann/tint v1.1.3 - github.com/mattn/go-isatty v0.0.22 + github.com/lmittmann/tint v1.2.0 + github.com/mattn/go-isatty v0.0.23 github.com/mileusna/useragent v1.3.5 github.com/orandin/slog-gorm v1.4.0 github.com/ory/fosite v0.49.1-0.20250703093431-a5f0b09bf31c @@ -60,7 +60,7 @@ require ( gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.31.2 gorm.io/plugin/opentelemetry v0.1.16 - modernc.org/sqlite v1.53.0 + modernc.org/sqlite v1.54.0 ) require ( @@ -80,10 +80,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.23 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.31 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.4.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.32.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.44.0 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.4.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.32.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.44.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/bytedance/gopkg v0.1.4 // indirect @@ -110,7 +110,7 @@ require ( github.com/fxamacker/cbor/v2 v2.9.2 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.1 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.7.1 // indirect @@ -229,7 +229,7 @@ require ( golang.org/x/arch v0.27.0 // indirect golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect golang.org/x/mod v0.37.0 // indirect - golang.org/x/net v0.56.0 // indirect + golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/time v0.15.0 // indirect @@ -243,7 +243,7 @@ require ( gorm.io/driver/clickhouse v0.7.0 // indirect gorm.io/driver/mysql v1.5.7 // indirect k8s.io/utils v0.0.0-20260617174310-a95e086a2553 // indirect - modernc.org/libc v1.73.5 // indirect + modernc.org/libc v1.74.1 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index 91323417..fdd57ba0 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -22,10 +22,10 @@ github.com/aws/aws-sdk-go-v2 v1.42.1 h1:9eOTgu1z/dVtYpNZ3/8/XbbaX0x/BqE3HUzAzs6K github.com/aws/aws-sdk-go-v2 v1.42.1/go.mod h1:5pKeft2eJj+gElQ38Jqg4ibCqh+/AK33/0X3hip7IjM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14 h1:3IZY0XAJquT3aHzbkHfPzy4ACPcEjVG0x87KOwtpqGY= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14/go.mod h1:zwM6veDkhGgQFqkBy+uT28AAYpLu+uFMlPl+rCg/73E= -github.com/aws/aws-sdk-go-v2/config v1.32.29 h1:BcMHHnpiWKogf+gGfpj3K1w+Sktz29XDo/cPSAPO3FU= -github.com/aws/aws-sdk-go-v2/config v1.32.29/go.mod h1:+Kbhn8Es4kPUph3F/0W7avykytc+Jh2Ld9/msv9ljV4= -github.com/aws/aws-sdk-go-v2/credentials v1.19.28 h1:zTXJSsNcoO91/mTXsZoYf0AK8dvNPiA58/VtyGXR+wM= -github.com/aws/aws-sdk-go-v2/credentials v1.19.28/go.mod h1:Kd9E0JzDBW/q1xbsHFrev/GnbAf5J0Ng8xoyc7HZ91Q= +github.com/aws/aws-sdk-go-v2/config v1.32.30 h1:XwsEzpTJfQYJbFicz/QMLwAZdyeNVVoOEkbF7R3gPJk= +github.com/aws/aws-sdk-go-v2/config v1.32.30/go.mod h1:Ud32SuMc+/9BGxfpSVld7HrE2o05JwKmXY4M3jOQNZU= +github.com/aws/aws-sdk-go-v2/credentials v1.19.29 h1:WHZGssHH887cO0ox07SIQZsFx3MKD4ps6w0xUEmnKYQ= +github.com/aws/aws-sdk-go-v2/credentials v1.19.29/go.mod h1:Mhl0xR6zjguiuj00XRx2wMx22sAltk7oya39sT7fdg8= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30 h1:/hi1JADLEW9YYryEz1w4GQu0EtP23pP553Cf9KgsDV4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.30/go.mod h1:/3AOgy4K17Dm4ucMZVC/MJkzy5kmfKUcINRHZyo0koQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 h1:xM/Is9cKMHa8Jj8zkvWhvrFkZsXJV9E+BB4g0HW0duQ= @@ -42,18 +42,18 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30 h1:/Z5jmNrK github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30/go.mod h1:lEzEZnOosE7zi8Z6royW1cFJTD9fpab4Ul1SBrllewk= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.31 h1:uao4A3QZ5UmB326V6KF+qRpv9Tjz7IlnlnTbbANntlU= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.31/go.mod h1:I/1+z0VwL1GhQyLgkoHDlygpUZ+iTAwOQ/NsftiUL2I= -github.com/aws/aws-sdk-go-v2/service/s3 v1.105.0 h1:XptwLL+UHXgafYMIHTy59IRovLbhz3znkxY2uS/pbXU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.105.0/go.mod h1:zdmCoFO/dSI7GlrwsPqFJI+WlFnSU4Tc8TJnlXrM1Do= -github.com/aws/aws-sdk-go-v2/service/signin v1.4.0 h1:sLzmJGCMv+C8KqiJgEqDLB6vxaJGmobRh4rr//ZpA3w= -github.com/aws/aws-sdk-go-v2/service/signin v1.4.0/go.mod h1:mxC0nT/C8wMMS97DemZPzvUZxvIt+2Iq+eS3JdFZGgg= -github.com/aws/aws-sdk-go-v2/service/sso v1.32.0 h1:qjMmry/cBDee1E/2gyvel0uRYCi3mwRZ2hf6N+GAodo= -github.com/aws/aws-sdk-go-v2/service/sso v1.32.0/go.mod h1:u8af9Nqkmqnr96f7v9nHqzZT9XBwbXEkTiqT4ROuJSE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.0 h1:fpOlDPI55HdszaxapEGk6HsGosOUaM2YPWJpjMgp8UI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.0/go.mod h1:DMPWJBjYs6+3+f/qhBFEFPPlQ6NlhWjai3dJNvipJ84= -github.com/aws/aws-sdk-go-v2/service/sts v1.44.0 h1:bLZ0PolJ8J+HkJHztcXORUpHXBye2U8298lCEMi6ZCU= -github.com/aws/aws-sdk-go-v2/service/sts v1.44.0/go.mod h1:9gdl4RrflIdpDb2TlXshWgR1F9TeCkvqDx77Vpr4Z/Q= -github.com/aws/smithy-go v1.27.3 h1:F3Zb497UhhskkfpJmfkXswyo+t0sh9OTBnIHjogWbVY= -github.com/aws/smithy-go v1.27.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/aws-sdk-go-v2/service/s3 v1.105.2 h1:5C00eQYpTrgQXnp6V3P6P7zPElna3AXvlukbANE6nJI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.105.2/go.mod h1:zdmCoFO/dSI7GlrwsPqFJI+WlFnSU4Tc8TJnlXrM1Do= +github.com/aws/aws-sdk-go-v2/service/signin v1.4.1 h1:V7ZZ300WPXGjvkyore5DGe0ljVPOxCXie/thWdtSBXE= +github.com/aws/aws-sdk-go-v2/service/signin v1.4.1/go.mod h1:mxC0nT/C8wMMS97DemZPzvUZxvIt+2Iq+eS3JdFZGgg= +github.com/aws/aws-sdk-go-v2/service/sso v1.32.1 h1:gYFYh4iLLcAOJRLNPY2aD2g9DIhKn4eof8UkIrr1rTk= +github.com/aws/aws-sdk-go-v2/service/sso v1.32.1/go.mod h1:u8af9Nqkmqnr96f7v9nHqzZT9XBwbXEkTiqT4ROuJSE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.1 h1:arjT9Cm3/WYbGmD5TUZHk4UQn4Lle1fUNZs5FC6CtF0= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.37.1/go.mod h1:DMPWJBjYs6+3+f/qhBFEFPPlQ6NlhWjai3dJNvipJ84= +github.com/aws/aws-sdk-go-v2/service/sts v1.44.1 h1:RvfHDg+xvAeZ+5741vUEjpOVtYSIm93W2zhx10Xtydw= +github.com/aws/aws-sdk-go-v2/service/sts v1.44.1/go.mod h1:9gdl4RrflIdpDb2TlXshWgR1F9TeCkvqDx77Vpr4Z/Q= +github.com/aws/smithy-go v1.27.4 h1:JQcphmBN4f0q/sPqXqROIItRNV/hy10cgu7CsFy616M= +github.com/aws/smithy-go v1.27.4/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= 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.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= @@ -159,8 +159,8 @@ github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= -github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= -github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ= +github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-co-op/gocron/v2 v2.22.0 h1:uEuH2F7k7VoESb1BYSaffuuV+T0kkpzsC0aXk7/z79I= github.com/go-co-op/gocron/v2 v2.22.0/go.mod h1:hiH/U9RMhTi1BBZJmef9s3KC9QwhpBF6PFrvUKaXY9M= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -174,8 +174,8 @@ github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AY github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= -github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ= -github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0= +github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs= +github.com/go-ldap/ldap/v3 v3.4.14/go.mod h1:S4eJUMUNjDkE0ZJtIZdybwyb03sGGLW6gxXT1Hs8VKA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -350,12 +350,12 @@ github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/libtnb/sqlite v1.2.0 h1:XsA7jsXHH2qmFkTWoy5YCKJybzHzWQ0flqDFP5Y9Yto= github.com/libtnb/sqlite v1.2.0/go.mod h1:O6vURH5fa5IgSmXd/qLAL2zSnYFUd7xSIumsmX3BrSI= -github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I= -github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/lmittmann/tint v1.2.0 h1:AogHRHy8HUJUnNJBHJlYa+fR4YY8mko2cnCp67xn9JY= +github.com/lmittmann/tint v1.2.0/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= -github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= +github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo= github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/mattn/goveralls v0.0.12 h1:PEEeF0k1SsTjOBQ8FOmrOAoCu4ytuMaWCnWe94zxbCg= @@ -655,8 +655,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 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.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= @@ -771,8 +771,8 @@ k8s.io/utils v0.0.0-20260617174310-a95e086a2553 h1:hmGqDecjc8d7HVzWzRFl0QD9bYuYK k8s.io/utils v0.0.0-20260617174310-a95e086a2553/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc= modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= -modernc.org/ccgo/v4 v4.34.5 h1:hcwnthv2/LBl+mRLOYwnQA/LuW44Oln1NQlWppNaS1Q= -modernc.org/ccgo/v4 v4.34.5/go.mod h1:aow0HNkO30OSA/2NrtDXkis92ff8ZFiDOmDOPhqhF8U= +modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU= +modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk= modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= @@ -781,8 +781,8 @@ modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI= modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.73.5 h1:G34rN/cRqL+zOUnrbz9uPq/+OxJ8/vzQ2CQwTJ42Wmw= -modernc.org/libc v1.73.5/go.mod h1:+Aoyx4M0etg6GikzCrip1VtvAtUlMlo2Aq+GHwQSqOA= +modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ= +modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= @@ -791,8 +791,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M= -modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s= +modernc.org/sqlite v1.54.0 h1:JCxR4qwkJvOaqAoYcgDoO25Nc+ROg6EJ2LfBVzdrgog= +modernc.org/sqlite v1.54.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/email-templates/package.json b/email-templates/package.json index 1adba44f..e71ecd2c 100644 --- a/email-templates/package.json +++ b/email-templates/package.json @@ -19,7 +19,7 @@ "@types/node": "^25.9.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "react-email": "6.7.0", + "react-email": "6.9.0", "tsx": "^4.22.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4ea037c..90013632 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,11 +57,11 @@ importers: specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.17) react-email: - specifier: 6.7.0 - version: 6.7.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 6.9.0 + version: 6.9.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) tsx: specifier: ^4.22.2 - version: 4.23.0 + version: 4.23.1 frontend: dependencies: @@ -85,7 +85,7 @@ importers: version: 13.3.0 '@tailwindcss/vite': specifier: ^4.3.0 - version: 4.3.2(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) + version: 4.3.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) axios: specifier: ^1.16.1 version: 1.18.1 @@ -100,10 +100,10 @@ importers: version: 1.5.4 runed: specifier: ^0.37.1 - version: 0.37.1(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(zod@4.4.3) + version: 0.37.1(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(zod@4.4.3) sveltekit-superforms: specifier: ^2.30.1 - version: 2.30.2(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(@types/json-schema@7.0.15)(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3) + version: 2.30.2(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(@types/json-schema@7.0.15)(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3) tailwind-merge: specifier: ^3.6.0 version: 3.6.0 @@ -113,7 +113,7 @@ importers: devDependencies: '@inlang/paraglide-js': specifier: ^2.18.0 - version: 2.21.0(typescript@6.0.3) + version: 2.22.0(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) '@inlang/plugin-m-function-matcher': specifier: ^2.2.6 version: 2.2.9 @@ -125,16 +125,16 @@ importers: version: 3.12.2 '@lucide/svelte': specifier: ^1.16.0 - version: 1.24.0(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + version: 1.25.0(svelte@5.56.6(@typescript-eslint/types@8.64.0)) '@sveltejs/adapter-static': specifier: ^3.0.10 - version: 3.0.10(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0))) + version: 3.0.10(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))) '@sveltejs/kit': specifier: ^2.60.1 - version: 2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) + version: 2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) '@sveltejs/vite-plugin-svelte': specifier: ^7.1.2 - version: 7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) + version: 7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) '@types/node': specifier: ^25.9.0 version: 25.9.5 @@ -143,52 +143,52 @@ importers: version: 1.5.6 bits-ui: specifier: ^2.18.1 - version: 2.18.1(@internationalized/date@3.12.2)(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + version: 2.18.1(@internationalized/date@3.12.2)(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0)) eslint: specifier: ^10.4.0 - version: 10.6.0(jiti@2.7.0) + version: 10.7.0(jiti@2.7.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.6.0(jiti@2.7.0)) + version: 10.1.8(eslint@10.7.0(jiti@2.7.0)) eslint-plugin-svelte: specifier: ^3.17.1 - version: 3.20.0(eslint@10.6.0(jiti@2.7.0))(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + version: 3.20.0(eslint@10.7.0(jiti@2.7.0))(svelte@5.56.6(@typescript-eslint/types@8.64.0)) formsnap: specifier: ^2.0.1 - version: 2.0.1(svelte@5.56.4(@typescript-eslint/types@8.63.0))(sveltekit-superforms@2.30.2(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(@types/json-schema@7.0.15)(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)) + version: 2.0.1(svelte@5.56.6(@typescript-eslint/types@8.64.0))(sveltekit-superforms@2.30.2(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(@types/json-schema@7.0.15)(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)) globals: specifier: ^17.6.0 version: 17.7.0 mode-watcher: specifier: ^1.1.0 - version: 1.1.0(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + version: 1.1.0(svelte@5.56.6(@typescript-eslint/types@8.64.0)) prettier: specifier: ^3.8.3 version: 3.9.5 prettier-plugin-svelte: specifier: ^3.5.2 - version: 3.5.2(prettier@3.9.5)(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + version: 3.5.2(prettier@3.9.5)(svelte@5.56.6(@typescript-eslint/types@8.64.0)) prettier-plugin-tailwindcss: specifier: ^0.8.0 - version: 0.8.0(prettier-plugin-svelte@3.5.2(prettier@3.9.5)(svelte@5.56.4(@typescript-eslint/types@8.63.0)))(prettier@3.9.5) + version: 0.8.1(prettier-plugin-svelte@3.5.2(prettier@3.9.5)(svelte@5.56.6(@typescript-eslint/types@8.64.0)))(prettier@3.9.5) shadcn-svelte: specifier: ^1.3.0 - version: 1.4.1(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + version: 1.4.2(svelte@5.56.6(@typescript-eslint/types@8.64.0)) svelte: specifier: ^5.55.8 - version: 5.56.4(@typescript-eslint/types@8.63.0) + version: 5.56.6(@typescript-eslint/types@8.64.0) svelte-check: specifier: ^4.4.8 - version: 4.7.2(picomatch@4.0.5)(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3) + version: 4.7.3(picomatch@4.0.5)(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3) svelte-sonner: specifier: ^1.1.1 - version: 1.1.1(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + version: 1.1.1(svelte@5.56.6(@typescript-eslint/types@8.64.0)) tailwind-variants: specifier: ^3.2.2 - version: 3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.2) + version: 3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.3) tailwindcss: specifier: ^4.3.0 - version: 4.3.2 + version: 4.3.3 tslib: specifier: ^2.8.1 version: 2.8.1 @@ -200,13 +200,13 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.59.4 - version: 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + version: 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) vite: specifier: ^8.0.16 - version: 8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0) + version: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) vite-plugin-compression: specifier: ^0.5.1 - version: 0.5.1(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) + version: 0.5.1(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) tests: dependencies: @@ -249,6 +249,10 @@ packages: resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} engines: {node: '>=6.9.0'} + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} @@ -257,8 +261,8 @@ packages: resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.27.0': - resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} engines: {node: '>=6.0.0'} hasBin: true @@ -275,8 +279,8 @@ packages: resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.27.0': - resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} '@babel/types@7.29.7': @@ -451,6 +455,12 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -672,14 +682,17 @@ packages: cpu: [x64] os: [win32] - '@inlang/paraglide-js@2.21.0': - resolution: {integrity: sha512-t+OOui1i5p73zQ2Dk28yFzu6rZhh7hsDOCHjkXDmSwFFsUsS5HyUD4CvPJ+wf02qgmlCMu2z1qffJ5aWwgx1CA==} + '@inlang/paraglide-js@2.22.0': + resolution: {integrity: sha512-GSzG7KEKcYAhwuPNJczIPB+DzyndYxr4lsXAkkB7xh00jTrt80NF2KfgjEAkxuJvWcnscqXf7y7d1Q0SWtSu7A==} hasBin: true peerDependencies: typescript: '>=5.6' + vite: '>=5.0.0' peerDependenciesMeta: typescript: optional: true + vite: + optional: true '@inlang/plugin-m-function-matcher@2.2.9': resolution: {integrity: sha512-FqrEw6p5UKn0fVLedILx5vjEAPAl01Uuv1xlF6pQ9XMxpDsTUSOAPrjWuus1jiPBnYEdbK8cP43Ni0e3IPz9aQ==} @@ -720,8 +733,8 @@ packages: '@lix-js/server-protocol-schema@0.1.1': resolution: {integrity: sha512-jBeALB6prAbtr5q4vTuxnRZZv1M2rKe8iNqRQhFJ4Tv7150unEa0vKyz0hs8Gl3fUGsWaNJBh3J8++fpbrpRBQ==} - '@lucide/svelte@1.24.0': - resolution: {integrity: sha512-yXwewA7ANQ5hfaSDrvsecosWjZn5RglzeXUZRSnxeANBskpNwblOkEJTqD0ujDdNKIKL8E9eVc2U/P3ziJr7OA==} + '@lucide/svelte@1.25.0': + resolution: {integrity: sha512-v9m+dD68jxVnqkU3K59mG/RSRFlPGzmKCGSyMfnXcaGv9jODDQMyQkcp1CGvk3Y/cUj9v7f8rw1n//K0B53xGQ==} peerDependencies: svelte: ^5 @@ -1188,11 +1201,6 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@sveltejs/acorn-typescript@1.0.10': - resolution: {integrity: sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==} - peerDependencies: - acorn: ^8.9.0 - '@sveltejs/acorn-typescript@1.0.11': resolution: {integrity: sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw==} peerDependencies: @@ -1203,8 +1211,8 @@ packages: peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/kit@2.69.2': - resolution: {integrity: sha512-CMdPDbYjRwRu4KXTxBVMuOpFPCt1i/v0ANennotec+K9Cmb2e3w2yYzJiC6Vh/WSvm9Khi5sJMZa0rJPqfHlDw==} + '@sveltejs/kit@2.70.0': + resolution: {integrity: sha512-5pBnJwdNzxbrxp1TLK1NPMFF0Cx57iZUDKInznKcfifYR9m9poWfZI2Tfhw6BZIjYor5dXvcibt4EQgar3k6ww==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -1236,69 +1244,69 @@ packages: '@swc/helpers@0.5.23': resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} - '@tailwindcss/node@4.3.2': - resolution: {integrity: sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==} + '@tailwindcss/node@4.3.3': + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} - '@tailwindcss/oxide-android-arm64@4.3.2': - resolution: {integrity: sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==} + '@tailwindcss/oxide-android-arm64@4.3.3': + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.3.2': - resolution: {integrity: sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==} + '@tailwindcss/oxide-darwin-arm64@4.3.3': + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.3.2': - resolution: {integrity: sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==} + '@tailwindcss/oxide-darwin-x64@4.3.3': + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.3.2': - resolution: {integrity: sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==} + '@tailwindcss/oxide-freebsd-x64@4.3.3': + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.2': - resolution: {integrity: sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.3.2': - resolution: {integrity: sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==} + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-arm64-musl@4.3.2': - resolution: {integrity: sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==} + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [musl] - '@tailwindcss/oxide-linux-x64-gnu@4.3.2': - resolution: {integrity: sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==} + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-x64-musl@4.3.2': - resolution: {integrity: sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==} + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [musl] - '@tailwindcss/oxide-wasm32-wasi@4.3.2': - resolution: {integrity: sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==} + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -1309,24 +1317,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.3.2': - resolution: {integrity: sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==} + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.3.2': - resolution: {integrity: sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==} + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.3.2': - resolution: {integrity: sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==} + '@tailwindcss/oxide@4.3.3': + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} engines: {node: '>= 20'} - '@tailwindcss/vite@4.3.2': - resolution: {integrity: sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==} + '@tailwindcss/vite@4.3.3': + resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 @@ -1390,63 +1398,63 @@ packages: '@types/json-schema': optional: true - '@typescript-eslint/eslint-plugin@8.63.0': - resolution: {integrity: sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==} + '@typescript-eslint/eslint-plugin@8.64.0': + resolution: {integrity: sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.63.0 + '@typescript-eslint/parser': ^8.64.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.63.0': - resolution: {integrity: sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==} + '@typescript-eslint/parser@8.64.0': + resolution: {integrity: sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.63.0': - resolution: {integrity: sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==} + '@typescript-eslint/project-service@8.64.0': + resolution: {integrity: sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.63.0': - resolution: {integrity: sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==} + '@typescript-eslint/scope-manager@8.64.0': + resolution: {integrity: sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.63.0': - resolution: {integrity: sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==} + '@typescript-eslint/tsconfig-utils@8.64.0': + resolution: {integrity: sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.63.0': - resolution: {integrity: sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==} + '@typescript-eslint/type-utils@8.64.0': + resolution: {integrity: sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.63.0': - resolution: {integrity: sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==} + '@typescript-eslint/types@8.64.0': + resolution: {integrity: sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.63.0': - resolution: {integrity: sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==} + '@typescript-eslint/typescript-estree@8.64.0': + resolution: {integrity: sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.63.0': - resolution: {integrity: sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==} + '@typescript-eslint/utils@8.64.0': + resolution: {integrity: sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.63.0': - resolution: {integrity: sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==} + '@typescript-eslint/visitor-keys@8.64.0': + resolution: {integrity: sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@valibot/to-json-schema@1.7.1': @@ -1552,9 +1560,9 @@ packages: '@internationalized/date': ^3.8.1 svelte: ^5.33.0 - brace-expansion@5.0.7: - resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.8: + resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} + engines: {node: 20 || >=22} call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} @@ -1711,6 +1719,9 @@ packages: devalue@5.8.1: resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==} + devalue@5.8.2: + resolution: {integrity: sha512-DObPPAfdtFbXjxLqK8s2Xk9ZuWz5+ZoFEhC7J76es4GU/rEiXwHTmbImoCdyoCOcBH1UF3+Cz6Z2sYD4hyl5TA==} + dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} @@ -1730,8 +1741,8 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} - dot-prop@10.1.0: - resolution: {integrity: sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==} + dot-prop@10.2.0: + resolution: {integrity: sha512-BTJ9aZYL3vCfZlZOBLy9v8TUqWGQ0pzFnygKwFZt5udj6viBoFIBviKPUoZLDCPn1FoXffv6McQFDenrm5Krfw==} engines: {node: '>=20'} dotenv@17.4.2: @@ -1756,8 +1767,8 @@ packages: resolution: {integrity: sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==} engines: {node: '>=10.2.0'} - enhanced-resolve@5.21.6: - resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} + enhanced-resolve@5.24.3: + resolution: {integrity: sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==} engines: {node: '>=10.13.0'} entities@4.5.0: @@ -1829,8 +1840,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.6.0: - resolution: {integrity: sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==} + eslint@10.7.0: + resolution: {integrity: sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -1859,8 +1870,8 @@ packages: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} - esrap@2.2.13: - resolution: {integrity: sha512-m8jH5hZgJE2RRUK/jjkGPcJEDAV+dYnZYFkosQaPTcE+Yw4xynXHOo6FUdwaWBtdR3b1MMa7wEDTSHeR2VWsGA==} + esrap@2.3.0: + resolution: {integrity: sha512-GQ/7RN8uOtEfNpzZzBMTzW9JBcX42oaSVtPzdF+6cEL8pqIL094iUpr9jzYGn4O4P/1S60dJ6izyT8F4LYARng==} peerDependencies: '@typescript-eslint/types': ^8.2.0 peerDependenciesMeta: @@ -1892,8 +1903,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.1.3: - resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==} + fast-uri@3.1.4: + resolution: {integrity: sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==} fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} @@ -1925,8 +1936,8 @@ packages: engines: {node: '>=18'} hasBin: true - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + flatted@3.4.3: + resolution: {integrity: sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==} follow-redirects@1.16.0: resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} @@ -1985,10 +1996,6 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - globals@16.5.0: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} @@ -2075,8 +2082,8 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jiti@2.4.2: - resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true jiti@2.7.0: @@ -2141,8 +2148,8 @@ packages: known-css-properties@0.37.0: resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} - kysely@0.29.3: - resolution: {integrity: sha512-VHtBdW6XB/pgoTSqraM3UAa2rYoYdNXqnNPpX+8XXP+cwYbVEFuAp3HyPt1vpNfU9l7Y2kpUrA9QDPsy8uUqOQ==} + kysely@0.29.4: + resolution: {integrity: sha512-y5mVgQNkMbs1eK9Xyc0pmNdabN2wHhRYY/5r4W5HrUT1rYCEPeVNSj1RUJeSDKT3U0p+mXCvLgkrFuIafYI6BA==} engines: {node: '>=22.0.0'} leac@0.6.0: @@ -2161,30 +2168,60 @@ packages: cpu: [arm64] os: [android] + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + lightningcss-darwin-arm64@1.32.0: resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + lightningcss-darwin-x64@1.32.0: resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + lightningcss-freebsd-x64@1.32.0: resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + lightningcss-linux-arm-gnueabihf@1.32.0: resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} @@ -2192,6 +2229,13 @@ packages: os: [linux] libc: [glibc] + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} @@ -2199,6 +2243,13 @@ packages: os: [linux] libc: [musl] + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} @@ -2206,6 +2257,13 @@ packages: os: [linux] libc: [glibc] + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} @@ -2213,22 +2271,45 @@ packages: os: [linux] libc: [musl] + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + lightningcss-win32-x64-msvc@1.32.0: resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + lightningcss@1.32.0: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -2485,8 +2566,8 @@ packages: resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.19: - resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} + postcss@8.5.22: + resolution: {integrity: sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -2499,8 +2580,8 @@ packages: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - prettier-plugin-tailwindcss@0.8.0: - resolution: {integrity: sha512-V8ITGH87yuBDF6JpEZTOVlUz/saAwqb8f3HRgUj8Lh+tGCcrmorhsLpYqzygwFwK0PE2Ib6Mv3M7T/uE2tZV1g==} + prettier-plugin-tailwindcss@0.8.1: + resolution: {integrity: sha512-iaFMYqDsE4ffdDkn5qup0j5f2aCEBFZrdrZnvu9QKTlWx/iGPeQ4HHu7b7fCPMxeo9nwQBiOAh2nSypdFYWJkw==} engines: {node: '>=20.19'} peerDependencies: '@ianvs/prettier-plugin-sort-imports': '*' @@ -2559,6 +2640,11 @@ packages: engines: {node: '>=14'} hasBin: true + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} + engines: {node: '>=14'} + hasBin: true + prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} @@ -2591,8 +2677,8 @@ packages: peerDependencies: react: ^19.2.7 - react-email@6.7.0: - resolution: {integrity: sha512-lJFdxPB7A1bLgTFYSzGWxNYyP63IbJsCSe+2EOl51lSQzKCs95+dBgiJdmW+wk7+Oix7UdkWhyJMMvbdQt9HJQ==} + react-email@6.9.0: + resolution: {integrity: sha512-72jV+VkeeXgNWDycNDn2tIlHFLg4Hevi3pC77g63FAY1+bDgTs4b56jbZfHF1t5d+GVGb02sGS8bOwoptgvI1w==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: @@ -2680,8 +2766,8 @@ packages: set-cookie-parser@3.1.2: resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==} - shadcn-svelte@1.4.1: - resolution: {integrity: sha512-lpvnuHJOMf/CRlOMo5h7HpmdpcWmqVoPyvnCcgpJWJWzVkiwmisF80mR54GIAExSTSk1g4+ere3QrSty2PCIIg==} + shadcn-svelte@1.4.2: + resolution: {integrity: sha512-j7oDhXRmFuZ8bAhvF7Y65moMi9qM0iA68j5wEDNBiH0be9NWOEg1qo7myDeIf+h2oUSLBYHpVmeJIKYBmNT4Kg==} hasBin: true peerDependencies: svelte: ^5.0.0 @@ -2767,8 +2853,8 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - svelte-check@4.7.2: - resolution: {integrity: sha512-GoS4XJdGswlq0rIT1vtFLzJY1bvHtY37McY9H9Gkm1Ggw/ICdZYn8J/Z8Yi0BEL0i3R4+jtaWVePjyppMlij/A==} + svelte-check@4.7.3: + resolution: {integrity: sha512-DHdTCGX62R0fCxBEaT+USdASAnoaRBaaNczkRJl0K7o3WyoCeVUbVxo6fKqpOll/B+WMWCsiFK0eFrJSNBKZIg==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: @@ -2807,8 +2893,8 @@ packages: peerDependencies: svelte: ^5.0.0 - svelte@5.56.4: - resolution: {integrity: sha512-/d0QHehmRuJW8gVz395MTkPcPozxzdjBMBE8oEYGz8O3b9KTMzzQ9ZHJQLuFKOHOPQbU6kx/X4iid/EBBzH7iw==} + svelte@5.56.6: + resolution: {integrity: sha512-p4HDLDogGHKRKCrgckQHNs5PEfXkju6JI5jTywueaKJI5hAdjPohEhRtQ0M1SWC/+TA73SPln+r7srr+7e4nZA==} engines: {node: '>=18'} sveltekit-superforms@2.30.2: @@ -2837,9 +2923,6 @@ packages: tailwind-merge: optional: true - tailwindcss@4.3.2: - resolution: {integrity: sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==} - tailwindcss@4.3.3: resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} @@ -2885,8 +2968,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.23.0: - resolution: {integrity: sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==} + tsx@4.23.1: + resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==} engines: {node: '>=18.0.0'} hasBin: true @@ -2908,8 +2991,8 @@ packages: typebox@1.3.3: resolution: {integrity: sha512-URXGUE31PJDQC+PtRMJeLdF4kmmOdFoVPikPCtV2oOIhUpNpppEdIz7W8bH8cFYPYHdDpaRvqwdegMTmHliudg==} - typescript-eslint@8.63.0: - resolution: {integrity: sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==} + typescript-eslint@8.64.0: + resolution: {integrity: sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2969,8 +3052,8 @@ packages: peerDependencies: vite: '>=2.0.0' - vite@8.1.4: - resolution: {integrity: sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==} + vite@8.1.5: + resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3116,11 +3199,13 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/helper-globals@7.29.7': {} + '@babel/helper-string-parser@7.29.7': {} '@babel/helper-validator-identifier@7.29.7': {} - '@babel/parser@7.27.0': + '@babel/parser@7.29.2': dependencies: '@babel/types': 7.29.7 @@ -3137,15 +3222,15 @@ snapshots: '@babel/parser': 7.29.7 '@babel/types': 7.29.7 - '@babel/traverse@7.27.0': + '@babel/traverse@7.29.0': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 '@babel/parser': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.7 debug: 4.4.3 - globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3253,9 +3338,14 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0(jiti@2.7.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.7.0(jiti@2.7.0))': dependencies: - eslint: 10.6.0(jiti@2.7.0) + eslint: 10.7.0(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.9.1(eslint@10.7.0(jiti@2.7.0))': + dependencies: + eslint: 10.7.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -3418,7 +3508,7 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@inlang/paraglide-js@2.21.0(typescript@6.0.3)': + '@inlang/paraglide-js@2.22.0(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))': dependencies: '@inlang/recommend-sherlock': 0.2.1 '@inlang/sdk': 2.10.2 @@ -3429,6 +3519,7 @@ snapshots: urlpattern-polyfill: 10.1.0 optionalDependencies: typescript: 6.0.3 + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) transitivePeerDependencies: - babel-plugin-macros @@ -3450,8 +3541,8 @@ snapshots: dependencies: '@lix-js/sdk': 0.4.10 '@sinclair/typebox': 0.31.30 - kysely: 0.29.3 - sqlite-wasm-kysely: 0.3.0(kysely@0.29.3) + kysely: 0.29.4 + sqlite-wasm-kysely: 0.3.0(kysely@0.29.4) uuid: 14.0.1 transitivePeerDependencies: - babel-plugin-macros @@ -3485,17 +3576,17 @@ snapshots: dedent: 1.5.1 human-id: 4.2.0 js-sha256: 0.11.1 - kysely: 0.29.3 - sqlite-wasm-kysely: 0.3.0(kysely@0.29.3) + kysely: 0.29.4 + sqlite-wasm-kysely: 0.3.0(kysely@0.29.4) uuid: 14.0.1 transitivePeerDependencies: - babel-plugin-macros '@lix-js/server-protocol-schema@0.1.1': {} - '@lucide/svelte@1.24.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))': + '@lucide/svelte@1.25.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))': dependencies: - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: @@ -3734,7 +3825,7 @@ snapshots: entities: 4.5.0 html-to-text: 9.0.5 html5parser: 3.0.0 - prettier: 3.9.5 + prettier: 3.9.6 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -3844,49 +3935,45 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@sveltejs/acorn-typescript@1.0.10(acorn@8.17.0)': - dependencies: - acorn: 8.17.0 - '@sveltejs/acorn-typescript@1.0.11(acorn@8.17.0)': dependencies: acorn: 8.17.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))': dependencies: - '@sveltejs/kit': 2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) + '@sveltejs/kit': 2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) - '@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0))': + '@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.11(acorn@8.17.0) - '@sveltejs/vite-plugin-svelte': 7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) '@types/cookie': 0.6.0 acorn: 8.17.0 cookie: 0.7.2 - devalue: 5.8.1 + devalue: 5.8.2 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 mrmime: 2.0.1 set-cookie-parser: 3.1.2 sirv: 3.0.2 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) - vite: 8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) optionalDependencies: '@opentelemetry/api': 1.9.1 typescript: 6.0.3 '@sveltejs/load-config@0.2.0': {} - '@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))': dependencies: deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.3 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) - vite: 8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0) - vitefu: 1.1.3(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) + vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) '@swc/helpers@0.5.15': dependencies: @@ -3896,73 +3983,73 @@ snapshots: dependencies: tslib: 2.8.1 - '@tailwindcss/node@4.3.2': + '@tailwindcss/node@4.3.3': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.21.6 + enhanced-resolve: 5.24.3 jiti: 2.7.0 lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.3.2 + tailwindcss: 4.3.3 - '@tailwindcss/oxide-android-arm64@4.3.2': + '@tailwindcss/oxide-android-arm64@4.3.3': optional: true - '@tailwindcss/oxide-darwin-arm64@4.3.2': + '@tailwindcss/oxide-darwin-arm64@4.3.3': optional: true - '@tailwindcss/oxide-darwin-x64@4.3.2': + '@tailwindcss/oxide-darwin-x64@4.3.3': optional: true - '@tailwindcss/oxide-freebsd-x64@4.3.2': + '@tailwindcss/oxide-freebsd-x64@4.3.3': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.2': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.3.2': + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.3.2': + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.3.2': + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.3.2': + '@tailwindcss/oxide-linux-x64-musl@4.3.3': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.3.2': + '@tailwindcss/oxide-wasm32-wasi@4.3.3': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.3.2': + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.3.2': + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': optional: true - '@tailwindcss/oxide@4.3.2': + '@tailwindcss/oxide@4.3.3': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.3.2 - '@tailwindcss/oxide-darwin-arm64': 4.3.2 - '@tailwindcss/oxide-darwin-x64': 4.3.2 - '@tailwindcss/oxide-freebsd-x64': 4.3.2 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.2 - '@tailwindcss/oxide-linux-arm64-gnu': 4.3.2 - '@tailwindcss/oxide-linux-arm64-musl': 4.3.2 - '@tailwindcss/oxide-linux-x64-gnu': 4.3.2 - '@tailwindcss/oxide-linux-x64-musl': 4.3.2 - '@tailwindcss/oxide-wasm32-wasi': 4.3.2 - '@tailwindcss/oxide-win32-arm64-msvc': 4.3.2 - '@tailwindcss/oxide-win32-x64-msvc': 4.3.2 + '@tailwindcss/oxide-android-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-x64': 4.3.3 + '@tailwindcss/oxide-freebsd-x64': 4.3.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - '@tailwindcss/vite@4.3.2(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0))': + '@tailwindcss/vite@4.3.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))': dependencies: - '@tailwindcss/node': 4.3.2 - '@tailwindcss/oxide': 4.3.2 - tailwindcss: 4.3.2 - vite: 8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0) + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + tailwindcss: 4.3.3 + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) '@tybys/wasm-util@0.10.3': dependencies: @@ -4024,15 +4111,15 @@ snapshots: '@types/json-schema': 7.0.15 optional: true - '@typescript-eslint/eslint-plugin@8.63.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.64.0(@typescript-eslint/parser@8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.63.0 - '@typescript-eslint/type-utils': 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/utils': 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.63.0 - eslint: 10.6.0(jiti@2.7.0) + '@typescript-eslint/parser': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.64.0 + '@typescript-eslint/type-utils': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.64.0 + eslint: 10.7.0(jiti@2.7.0) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -4040,56 +4127,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/parser@8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.63.0 - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.63.0 + '@typescript-eslint/scope-manager': 8.64.0 + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.64.0 debug: 4.4.3 - eslint: 10.6.0(jiti@2.7.0) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.63.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.64.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.63.0(typescript@6.0.3) - '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/tsconfig-utils': 8.64.0(typescript@6.0.3) + '@typescript-eslint/types': 8.64.0 debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.63.0': + '@typescript-eslint/scope-manager@8.64.0': dependencies: - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/visitor-keys': 8.63.0 + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/visitor-keys': 8.64.0 - '@typescript-eslint/tsconfig-utils@8.63.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.64.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 10.6.0(jiti@2.7.0) + eslint: 10.7.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.63.0': {} + '@typescript-eslint/types@8.64.0': {} - '@typescript-eslint/typescript-estree@8.63.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.64.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.63.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.63.0(typescript@6.0.3) - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/visitor-keys': 8.63.0 + '@typescript-eslint/project-service': 8.64.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.64.0(typescript@6.0.3) + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/visitor-keys': 8.64.0 debug: 4.4.3 minimatch: 10.2.5 semver: 7.8.5 @@ -4099,20 +4186,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/utils@8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.63.0 - '@typescript-eslint/types': 8.63.0 - '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) - eslint: 10.6.0(jiti@2.7.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.7.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.64.0 + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.63.0': + '@typescript-eslint/visitor-keys@8.64.0': dependencies: - '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/types': 8.64.0 eslint-visitor-keys: 5.0.1 '@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@6.0.3))': @@ -4168,7 +4255,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.3 + fast-uri: 3.1.4 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -4219,20 +4306,20 @@ snapshots: baseline-browser-mapping@2.10.42: {} - bits-ui@2.18.1(@internationalized/date@3.12.2)(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + bits-ui@2.18.1(@internationalized/date@3.12.2)(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: '@floating-ui/core': 1.7.5 '@floating-ui/dom': 1.7.6 '@internationalized/date': 3.12.2 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0)) - svelte: 5.56.4(@typescript-eslint/types@8.63.0) - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + runed: 0.35.1(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0)) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0)) tabbable: 6.5.0 transitivePeerDependencies: - '@sveltejs/kit' - brace-expansion@5.0.7: + brace-expansion@5.0.8: dependencies: balanced-match: 4.0.4 @@ -4303,7 +4390,7 @@ snapshots: ajv-formats: 3.0.1(ajv@8.20.0) atomically: 2.1.1 debounce-fn: 6.0.0 - dot-prop: 10.1.0 + dot-prop: 10.2.0 env-paths: 3.0.0 json-schema-typed: 8.0.2 semver: 7.8.5 @@ -4364,6 +4451,8 @@ snapshots: devalue@5.8.1: {} + devalue@5.8.2: {} + dijkstrajs@1.0.3: {} dlv@1.1.3: @@ -4387,7 +4476,7 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 - dot-prop@10.1.0: + dot-prop@10.2.0: dependencies: type-fest: 5.8.0 @@ -4426,7 +4515,7 @@ snapshots: - supports-color - utf-8-validate - enhanced-resolve@5.21.6: + enhanced-resolve@5.24.3: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -4481,15 +4570,15 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.6.0(jiti@2.7.0)): + eslint-config-prettier@10.1.8(eslint@10.7.0(jiti@2.7.0)): dependencies: - eslint: 10.6.0(jiti@2.7.0) + eslint: 10.7.0(jiti@2.7.0) - eslint-plugin-svelte@3.20.0(eslint@10.6.0(jiti@2.7.0))(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + eslint-plugin-svelte@3.20.0(eslint@10.7.0(jiti@2.7.0))(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(jiti@2.7.0)) '@jridgewell/sourcemap-codec': 1.5.5 - eslint: 10.6.0(jiti@2.7.0) + eslint: 10.7.0(jiti@2.7.0) esutils: 2.0.3 globals: 16.5.0 known-css-properties: 0.37.0 @@ -4497,9 +4586,9 @@ snapshots: postcss-load-config: 3.1.4(postcss@8.5.16) postcss-safe-parser: 7.0.1(postcss@8.5.16) semver: 7.8.5 - svelte-eslint-parser: 1.8.0(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + svelte-eslint-parser: 1.8.0(svelte@5.56.6(@typescript-eslint/types@8.64.0)) optionalDependencies: - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) transitivePeerDependencies: - ts-node @@ -4521,9 +4610,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.6.0(jiti@2.7.0): + eslint@10.7.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.7.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.6.0 @@ -4578,11 +4667,11 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.2.13(@typescript-eslint/types@8.63.0): + esrap@2.3.0(@typescript-eslint/types@8.64.0): dependencies: '@jridgewell/sourcemap-codec': 1.5.5 optionalDependencies: - '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/types': 8.64.0 esrecurse@4.3.0: dependencies: @@ -4603,7 +4692,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.1.3: {} + fast-uri@3.1.4: {} fdir@6.5.0(picomatch@4.0.5): optionalDependencies: @@ -4625,12 +4714,12 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.2 + flatted: 3.4.3 keyv: 4.5.4 flat@6.0.1: {} - flatted@3.4.2: {} + flatted@3.4.3: {} follow-redirects@1.16.0: {} @@ -4642,11 +4731,11 @@ snapshots: hasown: 2.0.4 mime-types: 2.1.35 - formsnap@2.0.1(svelte@5.56.4(@typescript-eslint/types@8.63.0))(sveltekit-superforms@2.30.2(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(@types/json-schema@7.0.15)(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)): + formsnap@2.0.1(svelte@5.56.6(@typescript-eslint/types@8.64.0))(sveltekit-superforms@2.30.2(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(@types/json-schema@7.0.15)(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)): dependencies: - svelte: 5.56.4(@typescript-eslint/types@8.63.0) - svelte-toolbelt: 0.5.0(svelte@5.56.4(@typescript-eslint/types@8.63.0)) - sveltekit-superforms: 2.30.2(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(@types/json-schema@7.0.15)(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) + svelte-toolbelt: 0.5.0(svelte@5.56.6(@typescript-eslint/types@8.64.0)) + sveltekit-superforms: 2.30.2(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(@types/json-schema@7.0.15)(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3) fs-extra@10.1.0: dependencies: @@ -4692,8 +4781,6 @@ snapshots: minipass: 7.1.3 path-scurry: 2.0.2 - globals@11.12.0: {} - globals@16.5.0: {} globals@17.7.0: {} @@ -4764,7 +4851,7 @@ snapshots: isexe@2.0.0: {} - jiti@2.4.2: {} + jiti@2.6.1: {} jiti@2.7.0: {} @@ -4819,7 +4906,7 @@ snapshots: known-css-properties@0.37.0: {} - kysely@0.29.3: {} + kysely@0.29.4: {} leac@0.6.0: {} @@ -4834,36 +4921,69 @@ snapshots: lightningcss-android-arm64@1.32.0: optional: true + lightningcss-android-arm64@1.33.0: + optional: true + lightningcss-darwin-arm64@1.32.0: optional: true + lightningcss-darwin-arm64@1.33.0: + optional: true + lightningcss-darwin-x64@1.32.0: optional: true + lightningcss-darwin-x64@1.33.0: + optional: true + lightningcss-freebsd-x64@1.32.0: optional: true + lightningcss-freebsd-x64@1.33.0: + optional: true + lightningcss-linux-arm-gnueabihf@1.32.0: optional: true + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + lightningcss-linux-arm64-gnu@1.32.0: optional: true + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + lightningcss-linux-arm64-musl@1.32.0: optional: true + lightningcss-linux-arm64-musl@1.33.0: + optional: true + lightningcss-linux-x64-gnu@1.32.0: optional: true + lightningcss-linux-x64-gnu@1.33.0: + optional: true + lightningcss-linux-x64-musl@1.32.0: optional: true + lightningcss-linux-x64-musl@1.33.0: + optional: true + lightningcss-win32-arm64-msvc@1.32.0: optional: true + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + lightningcss-win32-x64-msvc@1.32.0: optional: true + lightningcss-win32-x64-msvc@1.33.0: + optional: true + lightningcss@1.32.0: dependencies: detect-libc: 2.1.2 @@ -4880,6 +5000,22 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + lilconfig@2.1.0: {} locate-character@3.0.0: {} @@ -4929,17 +5065,17 @@ snapshots: minimatch@10.2.5: dependencies: - brace-expansion: 5.0.7 + brace-expansion: 5.0.8 minimist@1.2.8: {} minipass@7.1.3: {} - mode-watcher@1.1.0(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + mode-watcher@1.1.0(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: - runed: 0.25.0(svelte@5.56.4(@typescript-eslint/types@8.63.0)) - svelte: 5.56.4(@typescript-eslint/types@8.63.0) - svelte-toolbelt: 0.7.1(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + runed: 0.25.0(svelte@5.56.6(@typescript-eslint/types@8.64.0)) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) + svelte-toolbelt: 0.7.1(svelte@5.56.6(@typescript-eslint/types@8.64.0)) mri@1.2.0: {} @@ -4961,7 +5097,7 @@ snapshots: '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.42 caniuse-lite: 1.0.30001800 - postcss: 8.5.19 + postcss: 8.5.22 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) styled-jsx: 5.1.6(react@19.2.7) @@ -5085,7 +5221,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.19: + postcss@8.5.22: dependencies: nanoid: 3.3.16 picocolors: 1.1.1 @@ -5093,19 +5229,21 @@ snapshots: prelude-ls@1.2.1: {} - prettier-plugin-svelte@3.5.2(prettier@3.9.5)(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + prettier-plugin-svelte@3.5.2(prettier@3.9.5)(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: prettier: 3.9.5 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) - prettier-plugin-tailwindcss@0.8.0(prettier-plugin-svelte@3.5.2(prettier@3.9.5)(svelte@5.56.4(@typescript-eslint/types@8.63.0)))(prettier@3.9.5): + prettier-plugin-tailwindcss@0.8.1(prettier-plugin-svelte@3.5.2(prettier@3.9.5)(svelte@5.56.6(@typescript-eslint/types@8.64.0)))(prettier@3.9.5): dependencies: prettier: 3.9.5 optionalDependencies: - prettier-plugin-svelte: 3.5.2(prettier@3.9.5)(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + prettier-plugin-svelte: 3.5.2(prettier@3.9.5)(svelte@5.56.6(@typescript-eslint/types@8.64.0)) prettier@3.9.5: {} + prettier@3.9.6: {} + prismjs@1.30.0: {} prompts@2.4.2: @@ -5134,10 +5272,10 @@ snapshots: react: 19.2.7 scheduler: 0.27.0 - react-email@6.7.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + react-email@6.9.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - '@babel/parser': 7.27.0 - '@babel/traverse': 7.27.0 + '@babel/parser': 7.29.2 + '@babel/traverse': 7.29.0 '@react-email/render': 2.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) chokidar: 4.0.3 commander: 13.1.0 @@ -5146,7 +5284,7 @@ snapshots: debounce: 2.2.0 esbuild: 0.28.1 glob: 13.0.6 - jiti: 2.4.2 + jiti: 2.6.1 log-symbols: 7.0.1 marked: 15.0.12 mime-types: 3.0.2 @@ -5196,38 +5334,38 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.1.5 '@rolldown/binding-win32-x64-msvc': 1.1.5 - runed@0.23.4(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + runed@0.23.4(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: esm-env: 1.2.2 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) - runed@0.25.0(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + runed@0.25.0(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: esm-env: 1.2.2 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) - runed@0.28.0(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + runed@0.28.0(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: esm-env: 1.2.2 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) - runed@0.35.1(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + runed@0.35.1(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) optionalDependencies: - '@sveltejs/kit': 2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) + '@sveltejs/kit': 2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) - runed@0.37.1(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(zod@4.4.3): + runed@0.37.1(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(zod@4.4.3): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) optionalDependencies: - '@sveltejs/kit': 2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) + '@sveltejs/kit': 2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) zod: 4.4.3 sade@1.8.1: @@ -5246,12 +5384,12 @@ snapshots: set-cookie-parser@3.1.2: {} - shadcn-svelte@1.4.1(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + shadcn-svelte@1.4.2(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: commander: 14.0.3 node-fetch-native: 1.6.7 - postcss: 8.5.19 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + postcss: 8.5.22 + svelte: 5.56.6(@typescript-eslint/types@8.64.0) tailwind-merge: 3.6.0 sharp@0.34.5: @@ -5332,10 +5470,10 @@ snapshots: source-map-js@1.2.1: {} - sqlite-wasm-kysely@0.3.0(kysely@0.29.3): + sqlite-wasm-kysely@0.3.0(kysely@0.29.4): dependencies: '@sqlite.org/sqlite-wasm': 3.48.0-build4 - kysely: 0.29.3 + kysely: 0.29.4 string-width@4.2.3: dependencies: @@ -5371,7 +5509,7 @@ snapshots: dependencies: has-flag: 4.0.0 - svelte-check@4.7.2(picomatch@4.0.5)(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3): + svelte-check@4.7.3(picomatch@4.0.5)(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 '@sveltejs/load-config': 0.2.0 @@ -5379,12 +5517,12 @@ snapshots: fdir: 6.5.0(picomatch@4.0.5) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) typescript: 6.0.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.8.0(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + svelte-eslint-parser@1.8.0(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -5394,49 +5532,49 @@ snapshots: postcss-selector-parser: 7.1.4 semver: 7.8.5 optionalDependencies: - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) - svelte-sonner@1.1.1(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + svelte-sonner@1.1.1(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: - runed: 0.28.0(svelte@5.56.4(@typescript-eslint/types@8.63.0)) - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + runed: 0.28.0(svelte@5.56.6(@typescript-eslint/types@8.64.0)) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) - svelte-toolbelt@0.10.6(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + runed: 0.35.1(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0)) style-to-object: 1.0.14 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) transitivePeerDependencies: - '@sveltejs/kit' - svelte-toolbelt@0.5.0(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + svelte-toolbelt@0.5.0(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: clsx: 2.1.1 style-to-object: 1.0.14 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) - svelte-toolbelt@0.7.1(svelte@5.56.4(@typescript-eslint/types@8.63.0)): + svelte-toolbelt@0.7.1(svelte@5.56.6(@typescript-eslint/types@8.64.0)): dependencies: clsx: 2.1.1 - runed: 0.23.4(svelte@5.56.4(@typescript-eslint/types@8.63.0)) + runed: 0.23.4(svelte@5.56.6(@typescript-eslint/types@8.64.0)) style-to-object: 1.0.14 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) - svelte@5.56.4(@typescript-eslint/types@8.63.0): + svelte@5.56.6(@typescript-eslint/types@8.64.0): dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 - '@sveltejs/acorn-typescript': 1.0.10(acorn@8.17.0) + '@sveltejs/acorn-typescript': 1.0.11(acorn@8.17.0) '@types/estree': 1.0.9 '@types/trusted-types': 2.0.7 acorn: 8.17.0 aria-query: 5.3.1 axobject-query: 4.1.0 clsx: 2.1.1 - devalue: 5.8.1 + devalue: 5.8.2 esm-env: 1.2.2 - esrap: 2.2.13(@typescript-eslint/types@8.63.0) + esrap: 2.3.0(@typescript-eslint/types@8.64.0) is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.21 @@ -5444,12 +5582,12 @@ snapshots: transitivePeerDependencies: - '@typescript-eslint/types' - sveltekit-superforms@2.30.2(@sveltejs/kit@2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(@types/json-schema@7.0.15)(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3): + sveltekit-superforms@2.30.2(@sveltejs/kit@2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(@types/json-schema@7.0.15)(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3): dependencies: - '@sveltejs/kit': 2.69.2(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.4(@typescript-eslint/types@8.63.0))(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)))(svelte@5.56.4(@typescript-eslint/types@8.63.0))(typescript@6.0.3)(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)) + '@sveltejs/kit': 2.70.0(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) devalue: 5.8.1 memoize-weak: 1.0.2 - svelte: 5.56.4(@typescript-eslint/types@8.63.0) + svelte: 5.56.6(@typescript-eslint/types@8.64.0) ts-deepmerge: 8.0.0 optionalDependencies: '@exodus/schemasafe': 1.3.0 @@ -5478,14 +5616,12 @@ snapshots: tailwind-merge@3.6.0: {} - tailwind-variants@3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.2): + tailwind-variants@3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.3): dependencies: - tailwindcss: 4.3.2 + tailwindcss: 4.3.3 optionalDependencies: tailwind-merge: 3.6.0 - tailwindcss@4.3.2: {} - tailwindcss@4.3.3: {} tapable@2.3.3: {} @@ -5522,7 +5658,7 @@ snapshots: tslib@2.8.1: {} - tsx@4.23.0: + tsx@4.23.1: dependencies: esbuild: 0.28.1 optionalDependencies: @@ -5544,13 +5680,13 @@ snapshots: typebox@1.3.3: optional: true - typescript-eslint@8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3): + typescript-eslint@8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.63.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/parser': 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.63.0(eslint@10.6.0(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.6.0(jiti@2.7.0) + '@typescript-eslint/eslint-plugin': 8.64.0(@typescript-eslint/parser@8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -5590,20 +5726,20 @@ snapshots: vary@1.1.2: {} - vite-plugin-compression@0.5.1(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)): + vite-plugin-compression@0.5.1(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)): dependencies: chalk: 4.1.2 debug: 4.4.3 fs-extra: 10.1.0 - vite: 8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) transitivePeerDependencies: - supports-color - vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0): + vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0): dependencies: - lightningcss: 1.32.0 + lightningcss: 1.33.0 picomatch: 4.0.5 - postcss: 8.5.19 + postcss: 8.5.22 rolldown: 1.1.5 tinyglobby: 0.2.17 optionalDependencies: @@ -5611,12 +5747,12 @@ snapshots: esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.7.0 - tsx: 4.23.0 + tsx: 4.23.1 yaml: 2.9.0 - vitefu@1.1.3(vite@8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0)): + vitefu@1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)): optionalDependencies: - vite: 8.1.4(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) webpack-virtual-modules@0.6.2: {} From 8563934fd5d410c13e9ff58e56a07bef0ea22726 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Fri, 24 Jul 2026 17:27:54 +0200 Subject: [PATCH 08/13] refactor: use `NewTextHandler` instead of `NewHandler` for tint --- backend/internal/bootstrap/observability_boostrap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/internal/bootstrap/observability_boostrap.go b/backend/internal/bootstrap/observability_boostrap.go index 6cb6874a..3c5e64d3 100644 --- a/backend/internal/bootstrap/observability_boostrap.go +++ b/backend/internal/bootstrap/observability_boostrap.go @@ -99,7 +99,7 @@ func initOtelLogging(ctx context.Context, resource *resource.Resource) (shutdown Level: level, }) } else { - handler = tint.NewHandler(os.Stdout, &tint.Options{ + handler = tint.NewTextHandler(os.Stdout, &tint.Options{ TimeFormat: time.Stamp, Level: level, NoColor: !isatty.IsTerminal(os.Stdout.Fd()), From 80c7c4aaf18f5537c36af7572b6f993631a68c1a Mon Sep 17 00:00:00 2001 From: Markus Schanz <3457747+schnz@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:43:16 +0200 Subject: [PATCH 09/13] feat: add declaritive user id configuration (#1622) --- backend/internal/dto/user_dto.go | 1 + backend/internal/dto/user_dto_test.go | 12 ++++++++++++ backend/internal/service/user_service.go | 3 +++ 3 files changed, 16 insertions(+) diff --git a/backend/internal/dto/user_dto.go b/backend/internal/dto/user_dto.go index e337528f..545e4ccc 100644 --- a/backend/internal/dto/user_dto.go +++ b/backend/internal/dto/user_dto.go @@ -23,6 +23,7 @@ type UserDto struct { } type UserCreateDto struct { + ID string `json:"id" binding:"omitempty,uuid"` Username string `json:"username" binding:"required,username,min=1,max=50" unorm:"nfc"` Email *string `json:"email" binding:"omitempty,email" unorm:"nfc"` EmailVerified bool `json:"emailVerified"` diff --git a/backend/internal/dto/user_dto_test.go b/backend/internal/dto/user_dto_test.go index 62dd6188..87b76b37 100644 --- a/backend/internal/dto/user_dto_test.go +++ b/backend/internal/dto/user_dto_test.go @@ -23,6 +23,18 @@ func TestUserCreateDto_Validate(t *testing.T) { }, wantErr: "", }, + { + name: "with custom id", + input: UserCreateDto{ + ID: "e8b81981-1c92-46e3-bfbf-07096560e6bb", + Username: "testuser", + Email: new("test@example.com"), + FirstName: "John", + LastName: "Doe", + DisplayName: "John Doe", + }, + wantErr: "", + }, { name: "missing username", input: UserCreateDto{ diff --git a/backend/internal/service/user_service.go b/backend/internal/service/user_service.go index 34a1ee07..09740f83 100644 --- a/backend/internal/service/user_service.go +++ b/backend/internal/service/user_service.go @@ -285,6 +285,9 @@ func (s *UserService) createUserInternal(ctx context.Context, input dto.UserCrea Disabled: input.Disabled, UserGroups: userGroups, } + if input.ID != "" { + user.ID = input.ID + } if input.LdapID != "" { user.LdapID = &input.LdapID } From 531bb5f0cf3a5a92fae117fcd551002fdf859c37 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Sun, 26 Jul 2026 15:24:56 +0200 Subject: [PATCH 10/13] chore(translations): update translations via Crowdin (#1623) --- frontend/messages/pt-BR.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/messages/pt-BR.json b/frontend/messages/pt-BR.json index 206baff0..1530ff5e 100644 --- a/frontend/messages/pt-BR.json +++ b/frontend/messages/pt-BR.json @@ -3,13 +3,13 @@ "my_account": "Minha Conta", "logout": "Sair", "confirm": "Confirmar", - "docs": "Documentos", + "docs": "Documentação", "key": "Chave", "value": "Valor", - "remove_custom_claim": "Tirar reivindicação personalizada", + "remove_custom_claim": "Remover reivindicação personalizada", "add_custom_claim": "Adicionar reivindicação personalizada", "add_another": "Adicionar outro", - "select_a_date": "Selecione a data", + "select_a_date": "Selecione uma data", "select_file": "Selecionar Arquivo", "profile_picture": "Foto de Perfil", "profile_picture_is_managed_by_ldap_server": "A foto de perfil é gerenciada pelo servidor LDAP e não pode ser alterada aqui.", @@ -28,7 +28,7 @@ "login_background": "Histórico de login", "logo": "Logotipo", "login_code": "Código de Login", - "create_a_login_code_to_sign_in_without_a_passkey_once": "Crie um código de login de uso único para que o usuário possa entrar sem precisar de uma chave de acesso.", + "create_a_login_code_to_sign_in_without_a_passkey_once": "Crie um código de login para o usuário entrar uma vez sem chave de acesso.", "one_hour": "1 hora", "twelve_hours": "12 horas", "one_day": "1 dia", @@ -46,9 +46,9 @@ "authenticator_does_not_support_any_of_the_requested_algorithms": "O autenticador não suporta nenhum dos algoritmos solicitados", "webauthn_error_invalid_rp_id": "A identificação da parte confiável configurada não está válida.", "webauthn_error_invalid_domain": "O domínio configurado não está certo.", - "contact_administrator_to_fix": "Fala com o administrador pra resolver esse problema.", - "webauthn_operation_not_allowed_or_timed_out": "A operação não foi permitida ou expirou.", - "webauthn_not_supported_by_browser": "As chaves de acesso não são suportadas por este navegador. Por favor, use um método alternativo de login.", + "contact_administrator_to_fix": "Fala com o administrador para resolver esse problema.", + "webauthn_operation_not_allowed_or_timed_out": "A operação não foi permitida ou expirou", + "webauthn_not_supported_by_browser": "Chaves de acesso não são suportadas por este navegador. Por favor, use um método de login alternativo.", "critical_error_occurred_contact_administrator": "Ocorreu um erro grave. Por favor, entre em contato com o administrador.", "sign_in_to": "Entrar em {name}", "account_selection_signin_confirmation": "Queres usar a seguinte conta para continuar {name}?", From a1b4e1d2b27bca699207c62cf2348fe53da3c0b2 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Sun, 26 Jul 2026 03:32:43 -1000 Subject: [PATCH 11/13] feat: migrate one-time and signup tokens to an actor (#1611) Co-authored-by: Elias Schneider --- backend/go.mod | 12 +- backend/go.sum | 24 +- .../internal/bootstrap/actors_bootstrap.go | 28 + backend/internal/bootstrap/bootstrap.go | 3 + .../bootstrap/e2etest_router_bootstrap.go | 2 +- .../internal/bootstrap/router_bootstrap.go | 8 +- .../internal/bootstrap/services_bootstrap.go | 68 +- .../internal/cmds/one_time_access_token.go | 74 +- .../internal/controller/user_controller.go | 199 +- backend/internal/job/db_cleanup_job.go | 29 - .../internal/model/one_time_access_token.go | 13 - backend/internal/onetimeaccess/actor.go | 167 + .../dto.go} | 12 +- backend/internal/onetimeaccess/handler.go | 198 + backend/internal/onetimeaccess/module.go | 86 + backend/internal/onetimeaccess/service.go | 283 + .../internal/onetimeaccess/service_test.go | 172 + backend/internal/service/e2etest_service.go | 196 +- .../service/one_time_access_email_sender.go | 29 + .../service/one_time_access_service.go | 261 - .../service/one_time_access_service_test.go | 53 - backend/internal/usersignup/actor.go | 217 + backend/internal/usersignup/actor_test.go | 173 + backend/internal/usersignup/cleanup.go | 19 - backend/internal/usersignup/handler.go | 20 +- backend/internal/usersignup/migration.go | 105 + backend/internal/usersignup/migration_test.go | 207 + backend/internal/usersignup/models.go | 16 +- backend/internal/usersignup/module.go | 29 +- backend/internal/usersignup/service.go | 386 +- backend/internal/usersignup/service_test.go | 127 + .../20260723000000_actor_tokens.down.sql | 56 + .../20260723000000_actor_tokens.up.sql | 28 + .../20260723000000_actor_tokens.down.sql | 62 + .../sqlite/20260723000000_actor_tokens.up.sql | 35 + frontend/package-lock.json | 5429 +++++++++++++++++ tests/resources/export/database.json | 68 +- 37 files changed, 7989 insertions(+), 905 deletions(-) delete mode 100644 backend/internal/model/one_time_access_token.go create mode 100644 backend/internal/onetimeaccess/actor.go rename backend/internal/{dto/one_time_access_dto.go => onetimeaccess/dto.go} (51%) create mode 100644 backend/internal/onetimeaccess/handler.go create mode 100644 backend/internal/onetimeaccess/module.go create mode 100644 backend/internal/onetimeaccess/service.go create mode 100644 backend/internal/onetimeaccess/service_test.go create mode 100644 backend/internal/service/one_time_access_email_sender.go delete mode 100644 backend/internal/service/one_time_access_service.go delete mode 100644 backend/internal/service/one_time_access_service_test.go create mode 100644 backend/internal/usersignup/actor.go create mode 100644 backend/internal/usersignup/actor_test.go delete mode 100644 backend/internal/usersignup/cleanup.go create mode 100644 backend/internal/usersignup/migration.go create mode 100644 backend/internal/usersignup/migration_test.go create mode 100644 backend/internal/usersignup/service_test.go create mode 100644 backend/resources/migrations/postgres/20260723000000_actor_tokens.down.sql create mode 100644 backend/resources/migrations/postgres/20260723000000_actor_tokens.up.sql create mode 100644 backend/resources/migrations/sqlite/20260723000000_actor_tokens.down.sql create mode 100644 backend/resources/migrations/sqlite/20260723000000_actor_tokens.up.sql create mode 100644 frontend/package-lock.json diff --git a/backend/go.mod b/backend/go.mod index e18eaf34..8aea1871 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -24,8 +24,8 @@ require ( github.com/go-webauthn/webauthn v0.17.4 github.com/golang-migrate/migrate/v4 v4.19.1 github.com/google/uuid v1.6.0 - github.com/italypaleale/francis v0.1.0-beta.11 - github.com/italypaleale/go-kit v0.0.0-20260708054611-e276b65dd3be + github.com/italypaleale/francis v0.1.0-beta.15 + github.com/italypaleale/go-kit v0.0.0-20260725195228-78f113702f86 github.com/italypaleale/go-sql-utils v0.2.4 github.com/jackc/pgx/v5 v5.10.0 github.com/jinzhu/copier v0.4.0 @@ -227,13 +227,13 @@ require ( go.uber.org/mock v0.6.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.27.0 // indirect - golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect - golang.org/x/mod v0.37.0 // indirect + golang.org/x/exp v0.0.0-20260718201538-764159d718ef // indirect + golang.org/x/mod v0.38.0 // indirect golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.47.0 // indirect + golang.org/x/tools v0.48.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7 // indirect google.golang.org/grpc v1.82.0 // indirect @@ -242,7 +242,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/clickhouse v0.7.0 // indirect gorm.io/driver/mysql v1.5.7 // indirect - k8s.io/utils v0.0.0-20260617174310-a95e086a2553 // indirect + k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 // indirect modernc.org/libc v1.74.1 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index fdd57ba0..bc7c44c8 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -263,10 +263,10 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/italypaleale/francis v0.1.0-beta.11 h1:FurXV2vMkRzJRFldQ6Z/bhLSJz8YXHm84uiASGeyWWU= -github.com/italypaleale/francis v0.1.0-beta.11/go.mod h1:vqKhwdLs5Sx+n6JCNknEKAODtEU51E9/LC1q9JAG3zk= -github.com/italypaleale/go-kit v0.0.0-20260708054611-e276b65dd3be h1:jgu+Mdsda++LqPxz8cj8vvgiFINQ8PhFB4Q1VZpyPjs= -github.com/italypaleale/go-kit v0.0.0-20260708054611-e276b65dd3be/go.mod h1:pl0r3F+thZIyDsyDo8aOUsAIVcsRuAeP1bB4GuAHLoY= +github.com/italypaleale/francis v0.1.0-beta.15 h1:yVFJCcD1pP91rIesAb06Gp1K1tPj0ruh6hha5YEhtRw= +github.com/italypaleale/francis v0.1.0-beta.15/go.mod h1:KKwS+57OBD/MoHBVfbbMelA2vUx65fPiG9fhdWWapFc= +github.com/italypaleale/go-kit v0.0.0-20260725195228-78f113702f86 h1:719T7W8hLVjelch856Sern60QAPMn0fIE87i91YVcfw= +github.com/italypaleale/go-kit v0.0.0-20260725195228-78f113702f86/go.mod h1:0Sy3bN3qnSy2kgcJ05A2CsP6os5wmLC9lPraFr+0jGk= github.com/italypaleale/go-sql-utils v0.2.4 h1:6CN8y3qEdNzvYlS/JK6N65E8cL9F8a6OBCJjzaQIv3c= github.com/italypaleale/go-sql-utils v0.2.4/go.mod h1:BJStxMfB6fzYVcOe0oZQCjGIPZQu76UBmg1Wuy6Z/7I= github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 h1:D/V0gu4zQ3cL2WKeVNVM4r2gLxGGf6McLwgXzRTo2RQ= @@ -620,8 +620,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= -golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M= -golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY= +golang.org/x/exp v0.0.0-20260718201538-764159d718ef h1:LkZ48HFgy/TvhTI0bcWkjgFkgLyKUwcTbDjS0DUjw+A= +golang.org/x/exp v0.0.0-20260718201538-764159d718ef/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I= golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY= @@ -633,8 +633,8 @@ golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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= @@ -724,8 +724,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= -golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -767,8 +767,8 @@ gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gorm.io/plugin/opentelemetry v0.1.16 h1:Kypj2YYAliJqkIczDZDde6P6sFMhKSlG5IpngMFQGpc= gorm.io/plugin/opentelemetry v0.1.16/go.mod h1:P3RmTeZXT+9n0F1ccUqR5uuTvEXDxF8k2UpO7mTIB2Y= -k8s.io/utils v0.0.0-20260617174310-a95e086a2553 h1:hmGqDecjc8d7HVzWzRFl0QD9bYuYKbBEG7t8xwnVxfI= -k8s.io/utils v0.0.0-20260617174310-a95e086a2553/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM= modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc= modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU= diff --git a/backend/internal/bootstrap/actors_bootstrap.go b/backend/internal/bootstrap/actors_bootstrap.go index d497364b..abdd833e 100644 --- a/backend/internal/bootstrap/actors_bootstrap.go +++ b/backend/internal/bootstrap/actors_bootstrap.go @@ -98,6 +98,34 @@ func (o *NewActorsOpts) getPSK() ([]byte, error) { return crypto.DeriveKey(o.EnvConfig.EncryptionKey, "pocketid/actors-psk/"+o.InstanceID) } +// NewActorStateStore creates a minimal actor host that can read and write actor state directly, without joining the cluster or binding a network port. +// It's meant for short-lived contexts such as CLI commands that need to persist actor state (for example, one-time access tokens) without running the full actor host. +// The returned host must NOT be Run(): only direct state operations (Get/Set/Delete on state) are supported, and they require the actor state tables to already exist, which is the case whenever the server has run at least once against this database. +func NewActorStateStore(db *gorm.DB, pg *pgxpool.Pool) (*local.Host, error) { + opts := &NewActorsOpts{DB: db, Postgres: pg} + if pg == nil { + sqlDB, err := db.DB() + if err != nil { + return nil, fmt.Errorf("failed to get *sql.DB connection from Gorm: %w", err) + } + opts.SQLite = sqlDB + } + + providerOpt, err := opts.getProvider() + if err != nil { + return nil, err + } + + return local.NewHost( + // The address is required by the host but never bound, since the host is not Run + local.WithAddress("127.0.0.1:1"), + local.WithLogger(slog.Default().With("scope", "actor-state-store")), + // The health-check deadline only needs to exceed the provider's query timeout to pass validation + local.WithHostHealthCheckDeadline(90*time.Second), + providerOpt, + ) +} + func (o *NewActorsOpts) getProvider() (local.HostOption, error) { switch { case o.Postgres != nil && o.SQLite != nil: diff --git a/backend/internal/bootstrap/bootstrap.go b/backend/internal/bootstrap/bootstrap.go index 8fd171e8..10b7e383 100644 --- a/backend/internal/bootstrap/bootstrap.go +++ b/backend/internal/bootstrap/bootstrap.go @@ -106,6 +106,9 @@ func Bootstrap(ctx context.Context) error { } services = append(services, svc.appLockService.RunRenewal) + // Migrate the pre-actor signup tokens into their actors, once the actor host is ready + services = append(services, actorsReady.Await(svc.userSignUpModule.RunSignupTokenMigration)) + // Acquire the lock from the app lock service waitUntil, err := svc.appLockService.Acquire(ctx, false) if errors.Is(err, service.ErrLockUnavailable) { diff --git a/backend/internal/bootstrap/e2etest_router_bootstrap.go b/backend/internal/bootstrap/e2etest_router_bootstrap.go index b87d4644..db1eb663 100644 --- a/backend/internal/bootstrap/e2etest_router_bootstrap.go +++ b/backend/internal/bootstrap/e2etest_router_bootstrap.go @@ -17,7 +17,7 @@ import ( func init() { registerTestControllers = []func(apiGroup *gin.RouterGroup, db *gorm.DB, svc *services){ func(apiGroup *gin.RouterGroup, db *gorm.DB, svc *services) { - testService, err := service.NewTestService(db, svc.appConfigService, svc.jwtService, svc.ldapService, svc.appLockService, svc.fileStorage) + testService, err := service.NewTestService(db, svc.actors, svc.appConfigService, svc.jwtService, svc.ldapService, svc.appLockService, svc.fileStorage) if err != nil { slog.Error("Failed to initialize test service", slog.Any("error", err)) os.Exit(1) diff --git a/backend/internal/bootstrap/router_bootstrap.go b/backend/internal/bootstrap/router_bootstrap.go index 11a71b96..fe68f019 100644 --- a/backend/internal/bootstrap/router_bootstrap.go +++ b/backend/internal/bootstrap/router_bootstrap.go @@ -158,7 +158,7 @@ func registerRoutes(r *gin.Engine, db *gorm.DB, svc *services, rateLimitServices rateLimitMiddleware.Add(middleware.RateLimitWebauthnReauthenticate), ) controller.NewOidcController(apiGroup, authMiddleware, fileSizeLimitMiddleware, svc.oidcService) - controller.NewUserController(apiGroup, authMiddleware, rateLimitMiddleware, svc.appConfigService, svc.userService, svc.oneTimeAccessService, svc.webauthnModule) + controller.NewUserController(apiGroup, authMiddleware, rateLimitMiddleware, svc.appConfigService, svc.userService, svc.webauthnModule) controller.NewAppConfigController(apiGroup, authMiddleware, svc.appConfigService, svc.emailService, svc.ldapService) controller.NewAppImagesController(apiGroup, authMiddleware, svc.appImagesService) controller.NewAuditLogController(apiGroup, svc.auditLogService, authMiddleware) @@ -171,6 +171,12 @@ func registerRoutes(r *gin.Engine, db *gorm.DB, svc *services, rateLimitServices authMiddleware.Add(), rateLimitMiddleware.Add(middleware.RateLimitSignup), ) + svc.oneTimeAccessModule.RegisterRoutes(apiGroup, + authMiddleware.Add(), + authMiddleware.WithAdminNotRequired().Add(), + rateLimitMiddleware.Add(middleware.RateLimitOneTimeAccessToken), + rateLimitMiddleware.Add(middleware.RateLimitOneTimeAccessEmail), + ) optionalBrowserAuth := authMiddleware.WithAdminNotRequired().WithSuccessOptional().WithApiKeyAuthDisabled().Add() browserAuth := authMiddleware.WithAdminNotRequired().WithApiKeyAuthDisabled().Add() diff --git a/backend/internal/bootstrap/services_bootstrap.go b/backend/internal/bootstrap/services_bootstrap.go index c3600f4f..8e2b363b 100644 --- a/backend/internal/bootstrap/services_bootstrap.go +++ b/backend/internal/bootstrap/services_bootstrap.go @@ -14,6 +14,7 @@ import ( "github.com/pocket-id/pocket-id/backend/internal/common" "github.com/pocket-id/pocket-id/backend/internal/job" "github.com/pocket-id/pocket-id/backend/internal/oidc" + "github.com/pocket-id/pocket-id/backend/internal/onetimeaccess" "github.com/pocket-id/pocket-id/backend/internal/service" "github.com/pocket-id/pocket-id/backend/internal/storage" "github.com/pocket-id/pocket-id/backend/internal/usersignup" @@ -21,28 +22,29 @@ import ( ) type services struct { - appConfigService *appconfig.AppConfigService - appImagesService *service.AppImagesService - emailService *service.EmailService - geoLiteService *service.GeoLiteService - auditLogService *service.AuditLogService - jwtService *service.JwtService - scimService *service.ScimService - userService *service.UserService - customClaimService *service.CustomClaimService - oidcService *service.OidcService - userGroupService *service.UserGroupService - ldapService *service.LdapService - versionService *service.VersionService - fileStorage storage.FileStorage - appLockService *service.AppLockService - oneTimeAccessService *service.OneTimeAccessService + appConfigService *appconfig.AppConfigService + appImagesService *service.AppImagesService + emailService *service.EmailService + geoLiteService *service.GeoLiteService + auditLogService *service.AuditLogService + jwtService *service.JwtService + scimService *service.ScimService + userService *service.UserService + customClaimService *service.CustomClaimService + oidcService *service.OidcService + userGroupService *service.UserGroupService + ldapService *service.LdapService + versionService *service.VersionService + fileStorage storage.FileStorage + appLockService *service.AppLockService - apiKeyModule *apikey.Module - oidcModule *oidc.Module - webauthnModule *webauthn.Module - userSignUpModule *usersignup.Module - apiModule *api.Module + apiKeyModule *apikey.Module + oidcModule *oidc.Module + webauthnModule *webauthn.Module + userSignUpModule *usersignup.Module + oneTimeAccessModule *onetimeaccess.Module + apiModule *api.Module + actors *local.Host } // Initializes all services @@ -56,7 +58,9 @@ func initServices( fileStorage storage.FileStorage, scheduler *job.Scheduler, ) (svc *services, err error) { - svc = &services{} + svc = &services{ + actors: actors, + } // Init the app config service svc.appConfigService, err = appconfig.NewService(ctx, actors, db) @@ -132,14 +136,30 @@ func initServices( return nil, fmt.Errorf("failed to create API key module: %w", err) } - svc.userSignUpModule = usersignup.New(usersignup.Dependencies{ + svc.userSignUpModule, err = usersignup.New(usersignup.Dependencies{ DB: db, + Actors: actors, Signer: svc.jwtService, AuditLog: svc.auditLogService, UserCreator: svc.userService, AppConfig: svc.appConfigService, }) - svc.oneTimeAccessService = service.NewOneTimeAccessService(db, svc.userService, svc.jwtService, svc.auditLogService, svc.emailService) + if err != nil { + return nil, fmt.Errorf("failed to create user signup module: %w", err) + } + + svc.oneTimeAccessModule, err = onetimeaccess.New(onetimeaccess.Dependencies{ + DB: db, + Actors: actors, + Signer: svc.jwtService, + AuditLog: svc.auditLogService, + UserProvider: svc.userService, + EmailSender: service.NewOneTimeAccessEmailSender(svc.emailService), + AppConfig: svc.appConfigService, + }) + if err != nil { + return nil, fmt.Errorf("failed to create one-time access module: %w", err) + } svc.versionService = service.NewVersionService(httpClient) diff --git a/backend/internal/cmds/one_time_access_token.go b/backend/internal/cmds/one_time_access_token.go index dd3f392c..dd64024d 100644 --- a/backend/internal/cmds/one_time_access_token.go +++ b/backend/internal/cmds/one_time_access_token.go @@ -12,7 +12,7 @@ import ( "github.com/pocket-id/pocket-id/backend/internal/bootstrap" "github.com/pocket-id/pocket-id/backend/internal/common" "github.com/pocket-id/pocket-id/backend/internal/model" - "github.com/pocket-id/pocket-id/backend/internal/service" + "github.com/pocket-id/pocket-id/backend/internal/onetimeaccess" ) var oneTimeAccessTokenCmd = &cobra.Command{ @@ -24,57 +24,47 @@ var oneTimeAccessTokenCmd = &cobra.Command{ userArg := args[0] // Connect to the database - db, _, err := bootstrap.NewDatabase(cmd.Context()) + db, pg, err := bootstrap.NewDatabase(cmd.Context()) if err != nil { return err } - // Create the access token - var oneTimeAccessToken *model.OneTimeAccessToken - err = db.Transaction(func(tx *gorm.DB) error { - // Load the user to retrieve the user ID - var user model.User - queryCtx, queryCancel := context.WithTimeout(cmd.Context(), 10*time.Second) - defer queryCancel() - txErr := tx. - WithContext(queryCtx). - Where("username = ? OR email = ?", userArg, userArg). - First(&user). - Error - switch { - case errors.Is(txErr, gorm.ErrRecordNotFound): - return errors.New("user not found") - case txErr != nil: - return fmt.Errorf("failed to query for user: %w", txErr) - case user.ID == "": - return errors.New("invalid user loaded: ID is empty") - } + // Load the user to retrieve the user ID + var user model.User + queryCtx, queryCancel := context.WithTimeout(cmd.Context(), 10*time.Second) + defer queryCancel() + err = db. + WithContext(queryCtx). + Where("username = ? OR email = ?", userArg, userArg). + First(&user). + Error + switch { + case errors.Is(err, gorm.ErrRecordNotFound): + return errors.New("user not found") + case err != nil: + return fmt.Errorf("failed to query for user: %w", err) + case user.ID == "": + return errors.New("invalid user loaded: ID is empty") + } - // Create a new access token that expires in 1 hour - oneTimeAccessToken, txErr = service.NewOneTimeAccessToken(user.ID, time.Hour, false) - if txErr != nil { - return fmt.Errorf("failed to generate access token: %w", txErr) - } - - queryCtx, queryCancel = context.WithTimeout(cmd.Context(), 10*time.Second) - defer queryCancel() - txErr = tx. - WithContext(queryCtx). - Create(oneTimeAccessToken). - Error - if txErr != nil { - return fmt.Errorf("failed to save access token: %w", txErr) - } - - return nil - }) + // One-time access tokens are stored in the actor state store + // The CLI doesn't run the full actor host, so it uses a minimal state store to persist the token directly + actorStore, err := bootstrap.NewActorStateStore(db, pg) if err != nil { - return err + return fmt.Errorf("failed to initialize the actor state store: %w", err) + } + + // Create a new access token that expires in 1 hour + tokenCtx, tokenCancel := context.WithTimeout(cmd.Context(), 10*time.Second) + defer tokenCancel() + token, _, err := onetimeaccess.StoreToken(tokenCtx, actorStore, user.ID, time.Hour, false) + if err != nil { + return fmt.Errorf("failed to create access token: %w", err) } // Print the result fmt.Printf(`A one-time access token valid for 1 hour has been created for "%s".`+"\n", userArg) - fmt.Printf("Use the following URL to sign in once: %s/lc/%s\n", common.EnvConfig.AppURL, oneTimeAccessToken.Token) + fmt.Printf("Use the following URL to sign in once: %s/lc/%s\n", common.EnvConfig.AppURL, token) return nil }, diff --git a/backend/internal/controller/user_controller.go b/backend/internal/controller/user_controller.go index 57ea9095..5aab14d7 100644 --- a/backend/internal/controller/user_controller.go +++ b/backend/internal/controller/user_controller.go @@ -6,8 +6,6 @@ import ( "time" "github.com/pocket-id/pocket-id/backend/internal/appconfig" - "github.com/pocket-id/pocket-id/backend/internal/common" - "github.com/pocket-id/pocket-id/backend/internal/utils/cookie" "github.com/gin-gonic/gin" "github.com/pocket-id/pocket-id/backend/internal/dto" @@ -17,18 +15,15 @@ import ( "github.com/pocket-id/pocket-id/backend/internal/webauthn" ) -const defaultOneTimeAccessTokenDuration = 15 * time.Minute - // NewUserController creates a new controller for user management endpoints // @Summary User management controller // @Description Initializes all user-related API endpoints // @Tags Users -func NewUserController(group *gin.RouterGroup, authMiddleware *middleware.AuthMiddleware, rateLimitMiddleware *middleware.RateLimitMiddleware, appConfigService *appconfig.AppConfigService, userService *service.UserService, oneTimeAccessService *service.OneTimeAccessService, webAuthnService *webauthn.Module) { +func NewUserController(group *gin.RouterGroup, authMiddleware *middleware.AuthMiddleware, rateLimitMiddleware *middleware.RateLimitMiddleware, appConfigService *appconfig.AppConfigService, userService *service.UserService, webAuthnService *webauthn.Module) { uc := UserController{ - appConfigService: appConfigService, - userService: userService, - oneTimeAccessService: oneTimeAccessService, - webAuthnService: webAuthnService, + appConfigService: appConfigService, + userService: userService, + webAuthnService: webAuthnService, } group.GET("/users", authMiddleware.Add(), uc.listUsersHandler) @@ -49,12 +44,6 @@ func NewUserController(group *gin.RouterGroup, authMiddleware *middleware.AuthMi group.PUT("/users/:id/profile-picture", authMiddleware.Add(), uc.updateUserProfilePictureHandler) group.PUT("/users/me/profile-picture", authMiddleware.WithAdminNotRequired().Add(), uc.updateCurrentUserProfilePictureHandler) - group.POST("/users/me/one-time-access-token", authMiddleware.WithAdminNotRequired().Add(), uc.createOwnOneTimeAccessTokenHandler) - group.POST("/users/:id/one-time-access-token", authMiddleware.Add(), uc.createAdminOneTimeAccessTokenHandler) - group.POST("/users/:id/one-time-access-email", authMiddleware.Add(), uc.RequestOneTimeAccessEmailAsAdminHandler) - group.POST("/one-time-access-token/:token", rateLimitMiddleware.Add(middleware.RateLimitOneTimeAccessToken), uc.exchangeOneTimeAccessTokenHandler) - group.POST("/one-time-access-email", rateLimitMiddleware.Add(middleware.RateLimitOneTimeAccessEmail), uc.RequestOneTimeAccessEmailAsUnauthenticatedUserHandler) - group.DELETE("/users/:id/profile-picture", authMiddleware.Add(), uc.resetUserProfilePictureHandler) group.DELETE("/users/me/profile-picture", authMiddleware.WithAdminNotRequired().Add(), uc.resetCurrentUserProfilePictureHandler) @@ -63,10 +52,9 @@ func NewUserController(group *gin.RouterGroup, authMiddleware *middleware.AuthMi } type UserController struct { - appConfigService *appconfig.AppConfigService - userService *service.UserService - oneTimeAccessService *service.OneTimeAccessService - webAuthnService *webauthn.Module + appConfigService *appconfig.AppConfigService + userService *service.UserService + webAuthnService *webauthn.Module } // getUserGroupsHandler godoc @@ -394,179 +382,6 @@ func (uc *UserController) updateCurrentUserProfilePictureHandler(c *gin.Context) c.Status(http.StatusNoContent) } -func (uc *UserController) createOneTimeAccessTokenHandler(c *gin.Context, own bool) { - var input dto.OneTimeAccessTokenCreateDto - err := c.ShouldBindJSON(&input) - if err != nil { - _ = c.Error(err) - return - } - - var ( - userID string - ttl time.Duration - ) - if own { - // Get user ID from context and force the default TTL - userID = c.GetString("userID") - ttl = defaultOneTimeAccessTokenDuration - } else { - // Get user ID from URL parameter, and optional TTL from body - userID = c.Param("id") - ttl = input.TTL.Duration - if ttl <= 0 { - ttl = defaultOneTimeAccessTokenDuration - } - } - if userID == "" { - _ = c.Error(&common.UserIdNotProvidedError{}) - return - } - - token, err := uc.oneTimeAccessService.CreateOneTimeAccessToken(c.Request.Context(), userID, ttl) - if err != nil { - _ = c.Error(err) - return - } - - c.JSON(http.StatusCreated, gin.H{"token": token}) -} - -// createOwnOneTimeAccessTokenHandler godoc -// @Summary Create one-time access token for current user -// @Description Generate a one-time access token for the currently authenticated user -// @Tags Users -// @Param id path string true "User ID" -// @Param body body dto.OneTimeAccessTokenCreateDto true "Token options" -// @Success 201 {object} object "{ \"token\": \"string\" }" -// @Router /api/users/{id}/one-time-access-token [post] -func (uc *UserController) createOwnOneTimeAccessTokenHandler(c *gin.Context) { - uc.createOneTimeAccessTokenHandler(c, true) -} - -// createAdminOneTimeAccessTokenHandler godoc -// @Summary Create one-time access token for user (admin) -// @Description Generate a one-time access token for a specific user (admin only) -// @Tags Users -// @Param id path string true "User ID" -// @Param body body dto.OneTimeAccessTokenCreateDto true "Token options" -// @Success 201 {object} object "{ \"token\": \"string\" }" -// @Router /api/users/{id}/one-time-access-token [post] -func (uc *UserController) createAdminOneTimeAccessTokenHandler(c *gin.Context) { - uc.createOneTimeAccessTokenHandler(c, false) -} - -// RequestOneTimeAccessEmailAsUnauthenticatedUserHandler godoc -// @Summary Request one-time access email -// @Description Request a one-time access email for unauthenticated users -// @Tags Users -// @Accept json -// @Produce json -// @Param body body dto.OneTimeAccessEmailAsUnauthenticatedUserDto true "Email request information" -// @Success 204 "No Content" -// @Router /api/one-time-access-email [post] -func (uc *UserController) RequestOneTimeAccessEmailAsUnauthenticatedUserHandler(c *gin.Context) { - dbConfig, err := uc.appConfigService.GetConfig(c.Request.Context()) - if err != nil { - _ = c.Error(fmt.Errorf("error loading app configuration: %w", err)) - return - } - - var input dto.OneTimeAccessEmailAsUnauthenticatedUserDto - if err := dto.ShouldBindWithNormalizedJSON(c, &input); err != nil { - _ = c.Error(err) - return - } - - deviceToken, err := uc.oneTimeAccessService.RequestOneTimeAccessEmailAsUnauthenticatedUser(c.Request.Context(), dbConfig, input.Email, input.RedirectPath) - if err != nil { - _ = c.Error(err) - return - } - - cookie.AddDeviceTokenCookie(c, deviceToken) - c.Status(http.StatusNoContent) -} - -// RequestOneTimeAccessEmailAsAdminHandler godoc -// @Summary Request one-time access email (admin) -// @Description Request a one-time access email for a specific user (admin only) -// @Tags Users -// @Accept json -// @Produce json -// @Param id path string true "User ID" -// @Param body body dto.OneTimeAccessEmailAsAdminDto true "Email request options" -// @Success 204 "No Content" -// @Router /api/users/{id}/one-time-access-email [post] -func (uc *UserController) RequestOneTimeAccessEmailAsAdminHandler(c *gin.Context) { - dbConfig, err := uc.appConfigService.GetConfig(c.Request.Context()) - if err != nil { - _ = c.Error(fmt.Errorf("error loading app configuration: %w", err)) - return - } - - var input dto.OneTimeAccessEmailAsAdminDto - if err := c.ShouldBindJSON(&input); err != nil { - _ = c.Error(err) - return - } - - userID := c.Param("id") - - ttl := input.TTL.Duration - if ttl <= 0 { - ttl = defaultOneTimeAccessTokenDuration - } - err = uc.oneTimeAccessService.RequestOneTimeAccessEmailAsAdmin(c.Request.Context(), dbConfig, userID, ttl) - if err != nil { - _ = c.Error(err) - return - } - - c.Status(http.StatusNoContent) -} - -// exchangeOneTimeAccessTokenHandler godoc -// @Summary Exchange one-time access token -// @Description Exchange a one-time access token for a session token -// @Tags Users -// @Param token path string true "One-time access token" -// @Success 200 {object} dto.UserDto -// @Router /api/one-time-access-token/{token} [post] -func (uc *UserController) exchangeOneTimeAccessTokenHandler(c *gin.Context) { - cfg, err := uc.appConfigService.GetConfig(c.Request.Context()) - if err != nil { - _ = c.Error(fmt.Errorf("error loading app configuration: %w", err)) - return - } - - loginCode := c.Param("token") - // reject invalid length login codes - if len(loginCode) != 6 && len(loginCode) != 16 { - _ = c.Error(&common.TokenInvalidOrExpiredError{}) - return - } - - deviceToken, _ := c.Cookie(cookie.DeviceTokenCookieName) - user, token, err := uc.oneTimeAccessService.ExchangeOneTimeAccessToken(c.Request.Context(), cfg, loginCode, deviceToken, c.ClientIP(), c.Request.UserAgent()) - if err != nil { - _ = c.Error(err) - return - } - - var userDto dto.UserDto - err = dto.MapStruct(user, &userDto) - if err != nil { - _ = c.Error(err) - return - } - - maxAge := int(cfg.SessionDuration.AsDurationMinutes().Seconds()) - cookie.AddAccessTokenCookie(c, maxAge, token) - - c.JSON(http.StatusOK, userDto) -} - // updateUserGroups godoc // @Summary Update user groups // @Description Update the groups a specific user belongs to diff --git a/backend/internal/job/db_cleanup_job.go b/backend/internal/job/db_cleanup_job.go index 24bbc4d3..7c682891 100644 --- a/backend/internal/job/db_cleanup_job.go +++ b/backend/internal/job/db_cleanup_job.go @@ -15,7 +15,6 @@ import ( datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" "github.com/pocket-id/pocket-id/backend/internal/oidc" "github.com/pocket-id/pocket-id/backend/internal/service" - "github.com/pocket-id/pocket-id/backend/internal/usersignup" "github.com/pocket-id/pocket-id/backend/internal/webauthn" ) @@ -34,8 +33,6 @@ func (s *Scheduler) RegisterDbCleanupJobs(ctx context.Context, db *gorm.DB) erro // Use exponential backoff for each DB cleanup job so transient query failures are retried automatically rather than causing an immediate job failure return errors.Join( s.RegisterJob(ctx, "ClearWebauthnSessions", jobDefWithJitter(24*time.Hour), jobs.clearWebauthnSessions, service.RegisterJobOpts{RunImmediately: true, BackOff: newBackOff()}), - s.RegisterJob(ctx, "ClearOneTimeAccessTokens", jobDefWithJitter(24*time.Hour), jobs.clearOneTimeAccessTokens, service.RegisterJobOpts{RunImmediately: true, BackOff: newBackOff()}), - s.RegisterJob(ctx, "ClearSignupTokens", jobDefWithJitter(24*time.Hour), jobs.clearSignupTokens, service.RegisterJobOpts{RunImmediately: true, BackOff: newBackOff()}), s.RegisterJob(ctx, "ClearEmailVerificationTokens", jobDefWithJitter(24*time.Hour), jobs.clearEmailVerificationTokens, service.RegisterJobOpts{RunImmediately: true, BackOff: newBackOff()}), s.RegisterJob(ctx, "ClearOAuth2Sessions", jobDefWithJitter(24*time.Hour), jobs.clearOAuth2Sessions, service.RegisterJobOpts{RunImmediately: true, BackOff: newBackOff()}), s.RegisterJob(ctx, "ClearOAuth2JTIs", jobDefWithJitter(24*time.Hour), jobs.clearOAuth2JTIs, service.RegisterJobOpts{RunImmediately: true, BackOff: newBackOff()}), @@ -61,32 +58,6 @@ func (j *DbCleanupJobs) clearWebauthnSessions(ctx context.Context) error { return nil } -// ClearOneTimeAccessTokens deletes one-time access tokens that have expired -func (j *DbCleanupJobs) clearOneTimeAccessTokens(ctx context.Context) error { - st := j.db. - WithContext(ctx). - Delete(&model.OneTimeAccessToken{}, "expires_at < ?", datatype.DateTime(time.Now())) - if st.Error != nil { - return fmt.Errorf("failed to clean expired one-time access tokens: %w", st.Error) - } - - slog.InfoContext(ctx, "Cleaned expired one-time access tokens", slog.Int64("count", st.RowsAffected)) - - return nil -} - -// clearSignupTokens deletes signup tokens that have expired -func (j *DbCleanupJobs) clearSignupTokens(ctx context.Context) error { - count, err := usersignup.CleanupExpiredSignupTokens(ctx, j.db) - if err != nil { - return fmt.Errorf("failed to clean expired signup tokens: %w", err) - } - - slog.InfoContext(ctx, "Cleaned expired signup tokens", slog.Int64("count", count)) - - return nil -} - // clearOAuth2Sessions deletes expired and invalidated OAuth2 sessions. func (j *DbCleanupJobs) clearOAuth2Sessions(ctx context.Context) error { count, err := oidc.CleanupExpiredOAuth2Sessions(ctx, j.db) diff --git a/backend/internal/model/one_time_access_token.go b/backend/internal/model/one_time_access_token.go deleted file mode 100644 index 3a3c095d..00000000 --- a/backend/internal/model/one_time_access_token.go +++ /dev/null @@ -1,13 +0,0 @@ -package model - -import datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" - -type OneTimeAccessToken struct { - Base - Token string - DeviceToken *string - ExpiresAt datatype.DateTime - - UserID string - User User -} diff --git a/backend/internal/onetimeaccess/actor.go b/backend/internal/onetimeaccess/actor.go new file mode 100644 index 00000000..61f2d91c --- /dev/null +++ b/backend/internal/onetimeaccess/actor.go @@ -0,0 +1,167 @@ +package onetimeaccess + +import ( + "context" + "fmt" + "log/slog" + "time" + + "github.com/italypaleale/francis/actor" + + "github.com/pocket-id/pocket-id/backend/internal/common" +) + +// One-time access tokens are stored entirely in the actor state store. +// Each token is its own actor, whose actor ID is the token value itself. +// The state is persisted with a TTL equal to the token's lifetime, so it's purged automatically when the token expires (there's no separate cleanup job). + +// TokenActorType is the actor type for the one-time access token actor +const TokenActorType = "OneTimeAccessToken" + +// Methods exposed by the one-time access token actor +// Because we cannot invoke an actor while a DB transaction is open (that would deadlock on SQLite), consuming a token is done by invoking the actor first (which atomically validates and deletes the token), and only afterwards performing the remaining work. +// On failure, the caller compensates by restoring the token via the "restore" method as best-effort. +const ( + // TokenMethodRestore stores a token's state, and is also how a consumed token is put back + TokenMethodRestore = "restore" + + tokenMethodConsume = "consume" +) + +// tokenConsumeStatus is the outcome of a "consume" invocation. +type tokenConsumeStatus string + +const ( + // tokenConsumeOK indicates the token was valid and has been consumed + tokenConsumeOK tokenConsumeStatus = "ok" + // tokenConsumeNotFound indicates the token doesn't exist (or has expired) + tokenConsumeNotFound tokenConsumeStatus = "not_found" + // tokenConsumeDeviceMismatch indicates the provided device token doesn't match + tokenConsumeDeviceMismatch tokenConsumeStatus = "device_mismatch" +) + +// TokenState is the persisted state of a one-time access token actor. +// The token value itself is the actor's ID, so it isn't repeated here. +type TokenState struct { + UserID string + DeviceToken *string + ExpiresAt time.Time +} + +// tokenConsumeRequest is the payload for the "consume" method +type tokenConsumeRequest struct { + DeviceToken string +} + +// tokenConsumeResponse is the response of the "consume" method +type tokenConsumeResponse struct { + Status tokenConsumeStatus + // State is included only when Status is "ok", so the caller can restore it if a later step fails + State TokenState +} + +// tokenActor is the actor that manages a single one-time access token +type tokenActor struct { + log *slog.Logger + client actor.Client[TokenState] +} + +// NewTokenActor allocates a new one-time access token actor +// It satisfies actor.Factory +func NewTokenActor(actorID string, service *actor.Service) actor.Actor { + return &tokenActor{ + log: slog.With( + slog.String("scope", "actor"), + slog.String("actorType", TokenActorType), + ), + client: actor.NewActorClient[TokenState](TokenActorType, actorID, service), + } +} + +// Invoke implements actor.ActorInvoke +func (a *tokenActor) Invoke(parentCtx context.Context, method string, data actor.Envelope) (any, error) { + switch method { + case tokenMethodConsume: + return a.consume(parentCtx, data) + case TokenMethodRestore: + return nil, a.restore(parentCtx, data) + default: + return nil, common.ErrUnsupportedActorMethod{Method: method} + } +} + +// consume atomically validates the token and, if valid, deletes it. +func (a *tokenActor) consume(parentCtx context.Context, data actor.Envelope) (tokenConsumeResponse, error) { + var req tokenConsumeRequest + if data != nil { + err := data.Decode(&req) + if err != nil { + return tokenConsumeResponse{}, fmt.Errorf("request body is not valid for method '%s': %w", tokenMethodConsume, err) + } + } + + ctx, cancel := context.WithTimeout(parentCtx, 10*time.Second) + defer cancel() + state, err := a.client.GetState(ctx) + if err != nil { + return tokenConsumeResponse{}, fmt.Errorf("error retrieving actor state: %w", err) + } + + // An empty UserID means there's no state: the token doesn't exist (or its state already expired and was purged) + if state.UserID == "" || state.ExpiresAt.Before(time.Now()) { + return tokenConsumeResponse{ + Status: tokenConsumeNotFound, + }, nil + } + + // If the token requires a device token, it must match + // A mismatch leaves the token untouched, mirroring the pre-actor behavior + if state.DeviceToken != nil && req.DeviceToken != *state.DeviceToken { + return tokenConsumeResponse{ + Status: tokenConsumeDeviceMismatch, + }, nil + } + + // The token is valid: delete the state (one-time use) + ctx, cancel = context.WithTimeout(parentCtx, 10*time.Second) + defer cancel() + err = a.client.DeleteState(ctx) + if err != nil { + return tokenConsumeResponse{}, fmt.Errorf("error deleting actor state: %w", err) + } + + return tokenConsumeResponse{ + Status: tokenConsumeOK, + State: state, + }, nil +} + +// restore re-creates the token state, used to compensate when a step after consuming the token fails. +func (a *tokenActor) restore(parentCtx context.Context, data actor.Envelope) error { + if data == nil { + return fmt.Errorf("request body is empty for method '%s'", TokenMethodRestore) + } + + var state TokenState + err := data.Decode(&state) + if err != nil { + return fmt.Errorf("request body is not valid for method '%s': %w", TokenMethodRestore, err) + } + + // If the token has meanwhile expired, there's nothing to restore + ttl := time.Until(state.ExpiresAt) + if ttl <= 0 { + return nil + } + + ctx, cancel := context.WithTimeout(parentCtx, 10*time.Second) + defer cancel() + err = a.client.SetState(ctx, state, &actor.SetStateOpts{ + TTL: ttl, + }) + if err != nil { + return fmt.Errorf("error saving actor state: %w", err) + } + + return nil +} diff --git a/backend/internal/dto/one_time_access_dto.go b/backend/internal/onetimeaccess/dto.go similarity index 51% rename from backend/internal/dto/one_time_access_dto.go rename to backend/internal/onetimeaccess/dto.go index a99dc5ac..d5eabbd3 100644 --- a/backend/internal/dto/one_time_access_dto.go +++ b/backend/internal/onetimeaccess/dto.go @@ -1,16 +1,18 @@ -package dto +package onetimeaccess -import "github.com/pocket-id/pocket-id/backend/internal/utils" +import ( + "github.com/pocket-id/pocket-id/backend/internal/utils" +) -type OneTimeAccessTokenCreateDto struct { +type tokenCreateDto struct { TTL utils.JSONDuration `json:"ttl" binding:"ttl"` } -type OneTimeAccessEmailAsUnauthenticatedUserDto struct { +type emailAsUnauthenticatedUserDto struct { Email string `json:"email" binding:"required,email" unorm:"nfc"` RedirectPath string `json:"redirectPath"` } -type OneTimeAccessEmailAsAdminDto struct { +type emailAsAdminDto struct { TTL utils.JSONDuration `json:"ttl" binding:"ttl"` } diff --git a/backend/internal/onetimeaccess/handler.go b/backend/internal/onetimeaccess/handler.go new file mode 100644 index 00000000..55cf5f62 --- /dev/null +++ b/backend/internal/onetimeaccess/handler.go @@ -0,0 +1,198 @@ +package onetimeaccess + +import ( + "fmt" + "net/http" + "time" + + "github.com/gin-gonic/gin" + + "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/utils/cookie" +) + +const defaultTokenDuration = 15 * time.Minute + +type handler struct { + service *Service + appConfig AppConfigResolver +} + +func newHandler(service *Service, appConfig AppConfigResolver) *handler { + return &handler{service: service, appConfig: appConfig} +} + +func (h *handler) createToken(c *gin.Context, own bool) { + var input tokenCreateDto + err := c.ShouldBindJSON(&input) + if err != nil { + _ = c.Error(err) + return + } + + var ( + userID string + ttl time.Duration + ) + if own { + // Get user ID from context and force the default TTL + userID = c.GetString("userID") + ttl = defaultTokenDuration + } else { + // Get user ID from URL parameter, and optional TTL from body + userID = c.Param("id") + ttl = input.TTL.Duration + if ttl <= 0 { + ttl = defaultTokenDuration + } + } + if userID == "" { + _ = c.Error(&common.UserIdNotProvidedError{}) + return + } + + token, err := h.service.CreateToken(c.Request.Context(), userID, ttl) + if err != nil { + _ = c.Error(err) + return + } + + c.JSON(http.StatusCreated, gin.H{"token": token}) +} + +// createOwnToken godoc +// @Summary Create one-time access token for current user +// @Description Generate a one-time access token for the currently authenticated user +// @Tags Users +// @Param body body tokenCreateDto true "Token options" +// @Success 201 {object} object "{ \"token\": \"string\" }" +// @Router /api/users/me/one-time-access-token [post] +func (h *handler) createOwnToken(c *gin.Context) { + h.createToken(c, true) +} + +// createTokenForUser godoc +// @Summary Create one-time access token for user (admin) +// @Description Generate a one-time access token for a specific user (admin only) +// @Tags Users +// @Param id path string true "User ID" +// @Param body body tokenCreateDto true "Token options" +// @Success 201 {object} object "{ \"token\": \"string\" }" +// @Router /api/users/{id}/one-time-access-token [post] +func (h *handler) createTokenForUser(c *gin.Context) { + h.createToken(c, false) +} + +// requestEmailAsUnauthenticatedUser godoc +// @Summary Request one-time access email +// @Description Request a one-time access email for unauthenticated users +// @Tags Users +// @Accept json +// @Produce json +// @Param body body emailAsUnauthenticatedUserDto true "Email request information" +// @Success 204 "No Content" +// @Router /api/one-time-access-email [post] +func (h *handler) requestEmailAsUnauthenticatedUser(c *gin.Context) { + dbConfig, err := h.appConfig.GetConfig(c.Request.Context()) + if err != nil { + _ = c.Error(fmt.Errorf("error loading app configuration: %w", err)) + return + } + + var input emailAsUnauthenticatedUserDto + err = dto.ShouldBindWithNormalizedJSON(c, &input) + if err != nil { + _ = c.Error(err) + return + } + + deviceToken, err := h.service.RequestOneTimeAccessEmailAsUnauthenticatedUser(c.Request.Context(), dbConfig, input.Email, input.RedirectPath) + if err != nil { + _ = c.Error(err) + return + } + + cookie.AddDeviceTokenCookie(c, deviceToken) + c.Status(http.StatusNoContent) +} + +// requestEmailAsAdmin godoc +// @Summary Request one-time access email (admin) +// @Description Request a one-time access email for a specific user (admin only) +// @Tags Users +// @Accept json +// @Produce json +// @Param id path string true "User ID" +// @Param body body emailAsAdminDto true "Email request options" +// @Success 204 "No Content" +// @Router /api/users/{id}/one-time-access-email [post] +func (h *handler) requestEmailAsAdmin(c *gin.Context) { + dbConfig, err := h.appConfig.GetConfig(c.Request.Context()) + if err != nil { + _ = c.Error(fmt.Errorf("error loading app configuration: %w", err)) + return + } + + var input emailAsAdminDto + err = c.ShouldBindJSON(&input) + if err != nil { + _ = c.Error(err) + return + } + + userID := c.Param("id") + + ttl := input.TTL.Duration + if ttl <= 0 { + ttl = defaultTokenDuration + } + err = h.service.RequestOneTimeAccessEmailAsAdmin(c.Request.Context(), dbConfig, userID, ttl) + if err != nil { + _ = c.Error(err) + return + } + + c.Status(http.StatusNoContent) +} + +// exchangeToken godoc +// @Summary Exchange one-time access token +// @Description Exchange a one-time access token for a session token +// @Tags Users +// @Param token path string true "One-time access token" +// @Success 200 {object} dto.UserDto +// @Router /api/one-time-access-token/{token} [post] +func (h *handler) exchangeToken(c *gin.Context) { + cfg, err := h.appConfig.GetConfig(c.Request.Context()) + if err != nil { + _ = c.Error(fmt.Errorf("error loading app configuration: %w", err)) + return + } + + loginCode := c.Param("token") + // reject invalid length login codes + if len(loginCode) != 6 && len(loginCode) != 16 { + _ = c.Error(&common.TokenInvalidOrExpiredError{}) + return + } + + deviceToken, _ := c.Cookie(cookie.DeviceTokenCookieName) + user, token, err := h.service.ExchangeToken(c.Request.Context(), cfg, loginCode, deviceToken, c.ClientIP(), c.Request.UserAgent()) + if err != nil { + _ = c.Error(err) + return + } + + var userDto dto.UserDto + err = dto.MapStruct(user, &userDto) + if err != nil { + _ = c.Error(err) + return + } + + maxAge := int(cfg.SessionDuration.AsDurationMinutes().Seconds()) + cookie.AddAccessTokenCookie(c, maxAge, token) + + c.JSON(http.StatusOK, userDto) +} diff --git a/backend/internal/onetimeaccess/module.go b/backend/internal/onetimeaccess/module.go new file mode 100644 index 00000000..9654921c --- /dev/null +++ b/backend/internal/onetimeaccess/module.go @@ -0,0 +1,86 @@ +package onetimeaccess + +import ( + "context" + "fmt" + "time" + + "github.com/gin-gonic/gin" + "github.com/italypaleale/francis/host/local" + "gorm.io/gorm" + + "github.com/pocket-id/pocket-id/backend/internal/appconfig" + "github.com/pocket-id/pocket-id/backend/internal/model" + "github.com/pocket-id/pocket-id/backend/internal/utils/email" +) + +// EmailData is the data rendered in the one-time access email +type EmailData struct { + Code string + LoginLink string + LoginLinkWithCode string + ExpirationString string +} + +// EmailSender sends the one-time access email +type EmailSender interface { + SendOneTimeAccessEmail(ctx context.Context, dbConfig *appconfig.AppConfigModel, to email.Address, data EmailData) error +} + +type TokenService interface { + GenerateAccessToken(user model.User, authenticationMethod string, sessionDuration time.Duration) (string, error) +} + +type AuditLogger interface { + Create(ctx context.Context, event model.AuditLogEvent, ipAddress, userAgent, userID string, data model.AuditLogData, tx *gorm.DB) (model.AuditLog, bool) +} + +type UserProvider interface { + GetUser(ctx context.Context, userID string) (model.User, error) +} + +// AppConfigResolver loads the current application configuration, so handlers can pass it explicitly to the service methods that need it +type AppConfigResolver interface { + GetConfig(ctx context.Context) (*appconfig.AppConfigModel, error) +} + +type Dependencies struct { + DB *gorm.DB + Actors *local.Host + + Signer TokenService + AuditLog AuditLogger + UserProvider UserProvider + EmailSender EmailSender + AppConfig AppConfigResolver +} + +type Module struct { + service *Service + handler *handler +} + +func New(deps Dependencies) (*Module, error) { + // Register the actor that manages a one-time access token + // Each token is its own actor, whose actor ID is the token's value + err := deps.Actors.RegisterActor(TokenActorType, NewTokenActor) + if err != nil { + return nil, fmt.Errorf("error registering the %s actor: %w", TokenActorType, err) + } + + service := newService(deps, deps.Actors.Service()) + return &Module{ + service: service, + handler: newHandler(service, deps.AppConfig), + }, nil +} + +// RegisterRoutes mounts the one-time access token endpoints +// auth guards the admin routes and ownAuth the current user's own token, while the rate limiters throttle the public exchange and email endpoints +func (m *Module) RegisterRoutes(apiGroup *gin.RouterGroup, auth, ownAuth, exchangeRateLimit, emailRateLimit gin.HandlerFunc) { + apiGroup.POST("/users/me/one-time-access-token", ownAuth, m.handler.createOwnToken) + apiGroup.POST("/users/:id/one-time-access-token", auth, m.handler.createTokenForUser) + apiGroup.POST("/users/:id/one-time-access-email", auth, m.handler.requestEmailAsAdmin) + apiGroup.POST("/one-time-access-token/:token", exchangeRateLimit, m.handler.exchangeToken) + apiGroup.POST("/one-time-access-email", emailRateLimit, m.handler.requestEmailAsUnauthenticatedUser) +} diff --git a/backend/internal/onetimeaccess/service.go b/backend/internal/onetimeaccess/service.go new file mode 100644 index 00000000..4c4a9cfd --- /dev/null +++ b/backend/internal/onetimeaccess/service.go @@ -0,0 +1,283 @@ +package onetimeaccess + +import ( + "context" + "errors" + "fmt" + "log/slog" + "net/url" + "strings" + "time" + + "github.com/italypaleale/francis/actor" + "gorm.io/gorm" + + "github.com/pocket-id/pocket-id/backend/internal/appconfig" + "github.com/pocket-id/pocket-id/backend/internal/common" + "github.com/pocket-id/pocket-id/backend/internal/model" + "github.com/pocket-id/pocket-id/backend/internal/utils" + "github.com/pocket-id/pocket-id/backend/internal/utils/email" +) + +// authenticationMethodOneTimePassword identifies one-time password/code authentication +// It must match the value emitted by the JWT service in the access token's "amr" claim +const authenticationMethodOneTimePassword = "otp" + +// TokenStore is the minimal interface needed to persist a one-time access token in the actor state store. +// It's satisfied by both *actor.Service (used by the running application) and *local.Host (used by CLI commands, which don't run the full actor host). +type TokenStore interface { + SetState(ctx context.Context, actorType string, actorID string, state any, opts *actor.SetStateOpts) error +} + +type Service struct { + db *gorm.DB + actorService *actor.Service + userProvider UserProvider + signer TokenService + auditLog AuditLogger + emailSender EmailSender +} + +func newService(deps Dependencies, actorService *actor.Service) *Service { + return &Service{ + db: deps.DB, + actorService: actorService, + userProvider: deps.UserProvider, + signer: deps.Signer, + auditLog: deps.AuditLog, + emailSender: deps.EmailSender, + } +} + +func (s *Service) RequestOneTimeAccessEmailAsAdmin(ctx context.Context, dbConfig *appconfig.AppConfigModel, userID string, ttl time.Duration) error { + if !dbConfig.EmailOneTimeAccessAsAdminEnabled.IsTrue() { + return &common.OneTimeAccessDisabledError{} + } + + _, err := s.requestOneTimeAccessEmailInternal(ctx, userID, "", ttl, false, dbConfig) + return err +} + +func (s *Service) RequestOneTimeAccessEmailAsUnauthenticatedUser(ctx context.Context, dbConfig *appconfig.AppConfigModel, userID, redirectPath string) (string, error) { + if !dbConfig.EmailOneTimeAccessAsUnauthenticatedEnabled.IsTrue() { + return "", &common.OneTimeAccessDisabledError{} + } + + var userId string + err := s.db.Model(&model.User{}).Select("id").Where("email = ?", userID).First(&userId).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + // Do not return error if user not found to prevent email enumeration + return "", nil + } else if err != nil { + return "", err + } + + deviceToken, err := s.requestOneTimeAccessEmailInternal(ctx, userId, redirectPath, 15*time.Minute, true, dbConfig) + if err != nil { + return "", err + } else if deviceToken == nil { + return "", errors.New("device token expected but not returned") + } + + return *deviceToken, nil +} + +func (s *Service) requestOneTimeAccessEmailInternal(ctx context.Context, userID, redirectPath string, ttl time.Duration, withDeviceToken bool, dbConfig *appconfig.AppConfigModel) (*string, error) { + // Load the user to ensure it exists and has an email address + user, err := s.userProvider.GetUser(ctx, userID) + if err != nil { + return nil, err + } + + if user.Email == nil { + return nil, &common.UserEmailNotSetError{} + } + + oneTimeAccessToken, deviceToken, err := StoreToken(ctx, s.actorService, user.ID, ttl, withDeviceToken) + if err != nil { + return nil, err + } + + go func() { + // This runs in background, so use a context without cancellation (or it would be stopped when the request ends) + // We still want to have a context derived from the request's to carry over tracing info + innerCtx := context.WithoutCancel(ctx) + + link := common.EnvConfig.AppURL + "/lc" + linkWithCode := link + "/" + oneTimeAccessToken + + // Add redirect path to the link + if strings.HasPrefix(redirectPath, "/") { + encodedRedirectPath := url.QueryEscape(redirectPath) + linkWithCode = linkWithCode + "?redirect=" + encodedRedirectPath + } + + innerErr := s.emailSender.SendOneTimeAccessEmail(innerCtx, dbConfig, email.Address{ + Name: user.FullName(), + Email: *user.Email, + }, EmailData{ + Code: oneTimeAccessToken, + LoginLink: link, + LoginLinkWithCode: linkWithCode, + ExpirationString: utils.DurationToString(ttl), + }) + if innerErr != nil { + slog.ErrorContext(innerCtx, "Failed to send one-time access token email", slog.Any("error", innerErr), slog.String("address", *user.Email)) + return + } + }() + + return deviceToken, nil +} + +func (s *Service) CreateToken(ctx context.Context, userID string, ttl time.Duration) (token string, err error) { + // Load the user to ensure it exists + _, err = s.userProvider.GetUser(ctx, userID) + if errors.Is(err, gorm.ErrRecordNotFound) { + return "", &common.UserNotFoundError{} + } else if err != nil { + return "", err + } + + token, _, err = StoreToken(ctx, s.actorService, userID, ttl, false) + if err != nil { + return "", err + } + + return token, nil +} + +func (s *Service) ExchangeToken(ctx context.Context, dbConfig *appconfig.AppConfigModel, token, deviceToken, ipAddress, userAgent string) (model.User, string, error) { + // Consume the token by invoking its actor: this atomically validates it and, if valid, deletes it. + // It must happen outside of a DB transaction, since invoking an actor while a transaction is open would deadlock on SQLite. + res, err := s.actorService.Invoke(ctx, TokenActorType, token, tokenMethodConsume, tokenConsumeRequest{ + DeviceToken: deviceToken, + }) + if err != nil { + return model.User{}, "", fmt.Errorf("error invoking one-time access token actor: %w", err) + } + + var consumeRes tokenConsumeResponse + err = res.Decode(&consumeRes) + if err != nil { + return model.User{}, "", fmt.Errorf("error decoding one-time access token actor response: %w", err) + } + + switch consumeRes.Status { + case tokenConsumeNotFound: + return model.User{}, "", &common.TokenInvalidOrExpiredError{} + case tokenConsumeDeviceMismatch: + return model.User{}, "", &common.DeviceCodeInvalid{} + case tokenConsumeOK: + // All good, continue below + default: + return model.User{}, "", fmt.Errorf("unexpected status from one-time access token actor: %s", consumeRes.Status) + } + + // The token has now been consumed. From this point on, if we hit an error we compensate by restoring the token (this is best-effort). + user, accessToken, err := s.completeTokenExchange(ctx, dbConfig, consumeRes.State, ipAddress, userAgent) + if err != nil { + s.restoreToken(ctx, token, consumeRes.State) + return model.User{}, "", err + } + + return user, accessToken, nil +} + +// completeTokenExchange performs the work that follows consuming a token: loading the user, validating it, and issuing an access token. +func (s *Service) completeTokenExchange(ctx context.Context, dbConfig *appconfig.AppConfigModel, state TokenState, ipAddress, userAgent string) (model.User, string, error) { + var user model.User + err := s.db. + WithContext(ctx). + Where("id = ?", state.UserID). + First(&user). + Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return model.User{}, "", &common.TokenInvalidOrExpiredError{} + } else if err != nil { + return model.User{}, "", err + } + + if user.Disabled { + return model.User{}, "", &common.UserDisabledError{} + } + + accessToken, err := s.signer.GenerateAccessToken( + user, + authenticationMethodOneTimePassword, + dbConfig.SessionDuration.AsDurationMinutes(), + ) + if err != nil { + return model.User{}, "", err + } + + s.auditLog.Create( + ctx, model.AuditLogEventOneTimeAccessTokenSignIn, + ipAddress, userAgent, + user.ID, + model.AuditLogData{}, + s.db, + ) + + return user, accessToken, nil +} + +// restoreToken restores a token that was consumed but whose exchange could not be completed. +// It's a best-effort compensation: if it fails (or the process crashes before it runs) we accept that the token was consumed unnecessarily. +func (s *Service) restoreToken(parentCtx context.Context, token string, state TokenState) { + // Use a context that is not canceled when the original request ends + ctx, cancel := context.WithTimeout(context.WithoutCancel(parentCtx), 10*time.Second) + defer cancel() + + _, err := s.actorService.Invoke(ctx, TokenActorType, token, TokenMethodRestore, state) + if err != nil { + slog.ErrorContext(ctx, "Failed to restore one-time access token after a failed exchange", slog.Any("error", err)) + } +} + +// StoreToken generates a new one-time access token and persists it in the actor state store, with a TTL matching its lifetime. +// It returns the token value and, when requested, the associated device token. +func StoreToken(ctx context.Context, store TokenStore, userID string, ttl time.Duration, withDeviceToken bool) (token string, deviceToken *string, err error) { + token, deviceToken, err = generateToken(ttl, withDeviceToken) + if err != nil { + return "", nil, err + } + + now := time.Now().Round(time.Second) + state := TokenState{ + UserID: userID, + DeviceToken: deviceToken, + ExpiresAt: now.Add(ttl), + } + + err = store.SetState(ctx, TokenActorType, token, state, &actor.SetStateOpts{TTL: ttl}) + if err != nil { + return "", nil, fmt.Errorf("error saving one-time access token state: %w", err) + } + + return token, deviceToken, nil +} + +// generateToken generates the random token value (and optional device token) for a one-time access token. +func generateToken(ttl time.Duration, withDeviceToken bool) (token string, deviceToken *string, err error) { + // If expires at is less than 15 minutes, use a 6-character token instead of 16 + tokenLength := 16 + if ttl <= 15*time.Minute { + tokenLength = 6 + } + + token, err = utils.GenerateRandomUnambiguousString(tokenLength) + if err != nil { + return "", nil, err + } + + if withDeviceToken { + dt, err := utils.GenerateRandomAlphanumericString(16) + if err != nil { + return "", nil, err + } + deviceToken = &dt + } + + return token, deviceToken, nil +} diff --git a/backend/internal/onetimeaccess/service_test.go b/backend/internal/onetimeaccess/service_test.go new file mode 100644 index 00000000..ccd1b42f --- /dev/null +++ b/backend/internal/onetimeaccess/service_test.go @@ -0,0 +1,172 @@ +package onetimeaccess + +import ( + "context" + "testing" + "time" + + "github.com/italypaleale/francis/actor" + "github.com/italypaleale/francis/host/local" + "github.com/stretchr/testify/require" + "gorm.io/gorm" + + "github.com/pocket-id/pocket-id/backend/internal/appconfig" + "github.com/pocket-id/pocket-id/backend/internal/common" + "github.com/pocket-id/pocket-id/backend/internal/model" + "github.com/pocket-id/pocket-id/backend/internal/utils/email" + testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing" +) + +type fakeSigner struct{} + +func (fakeSigner) GenerateAccessToken(_ model.User, _ string, _ time.Duration) (string, error) { + return "access-token", nil +} + +type fakeAuditLogger struct { + events []model.AuditLogEvent +} + +func (f *fakeAuditLogger) Create(_ context.Context, event model.AuditLogEvent, _, _, _ string, _ model.AuditLogData, _ *gorm.DB) (model.AuditLog, bool) { + f.events = append(f.events, event) + return model.AuditLog{}, true +} + +type fakeUserProvider struct { + db *gorm.DB +} + +func (f fakeUserProvider) GetUser(ctx context.Context, userID string) (model.User, error) { + var user model.User + err := f.db.WithContext(ctx).Where("id = ?", userID).First(&user).Error + return user, err +} + +type fakeEmailSender struct{} + +func (fakeEmailSender) SendOneTimeAccessEmail(_ context.Context, _ *appconfig.AppConfigModel, _ email.Address, _ EmailData) error { + return nil +} + +// newServiceForTest sets up a Service backed by an in-memory test actor host, and returns it together with the host and the audit logger it records into +func newServiceForTest(t *testing.T, db *gorm.DB) (*Service, *local.Host, *fakeAuditLogger) { + t.Helper() + + auditLog := &fakeAuditLogger{} + + var svc *Service + host := testutils.NewActorHostForTest(t, func(t *testing.T, h *local.Host) { + err := h.RegisterActor(TokenActorType, NewTokenActor) + require.NoError(t, err) + + svc = newService(Dependencies{ + DB: db, + Signer: fakeSigner{}, + AuditLog: auditLog, + UserProvider: fakeUserProvider{db: db}, + EmailSender: fakeEmailSender{}, + }, h.Service()) + }) + require.NotNil(t, svc) + + return svc, host, auditLog +} + +func TestExchangeTokenSuccess(t *testing.T) { + db := testutils.NewDatabaseForTest(t) + svc, host, auditLog := newServiceForTest(t, db) + + user := model.User{ + Base: model.Base{ID: "enabled-user"}, + Username: "enabled-user", + } + require.NoError(t, db.Create(&user).Error) + + token, _, err := StoreToken(t.Context(), svc.actorService, user.ID, time.Minute, false) + require.NoError(t, err) + + dbConfig := appconfig.NewTestConfig(nil) + exchangedUser, accessToken, err := svc.ExchangeToken(t.Context(), dbConfig, token, "", "1.2.3.4", "test-agent") + require.NoError(t, err) + require.Equal(t, user.ID, exchangedUser.ID) + require.NotEmpty(t, accessToken) + + // The token must have been consumed + var state TokenState + err = host.GetState(t.Context(), TokenActorType, token, &state) + require.ErrorIs(t, err, actor.ErrStateNotFound) + + // A sign-in audit log must have been created + require.Equal(t, []model.AuditLogEvent{model.AuditLogEventOneTimeAccessTokenSignIn}, auditLog.events) +} + +func TestExchangeTokenInvalidToken(t *testing.T) { + db := testutils.NewDatabaseForTest(t) + svc, _, _ := newServiceForTest(t, db) + + dbConfig := appconfig.NewTestConfig(nil) + _, _, err := svc.ExchangeToken(t.Context(), dbConfig, "does-not-exist", "", "", "") + + var invalidErr *common.TokenInvalidOrExpiredError + require.ErrorAs(t, err, &invalidErr) +} + +func TestExchangeTokenDeviceMismatch(t *testing.T) { + db := testutils.NewDatabaseForTest(t) + svc, host, _ := newServiceForTest(t, db) + + user := model.User{ + Base: model.Base{ID: "device-user"}, + Username: "device-user", + } + require.NoError(t, db.Create(&user).Error) + + // Store a token that requires a device token + token, deviceToken, err := StoreToken(t.Context(), svc.actorService, user.ID, time.Minute, true) + require.NoError(t, err) + require.NotNil(t, deviceToken) + + dbConfig := appconfig.NewTestConfig(nil) + _, _, err = svc.ExchangeToken(t.Context(), dbConfig, token, "wrong-device-token", "", "") + + var deviceErr *common.DeviceCodeInvalid + require.ErrorAs(t, err, &deviceErr) + + // The token must not have been consumed on a device-token mismatch + var state TokenState + err = host.GetState(t.Context(), TokenActorType, token, &state) + require.NoError(t, err) + require.Equal(t, user.ID, state.UserID) +} + +func TestExchangeTokenRejectsDisabledUser(t *testing.T) { + db := testutils.NewDatabaseForTest(t) + svc, host, auditLog := newServiceForTest(t, db) + + user := model.User{ + Base: model.Base{ID: "disabled-user"}, + Username: "disabled-user", + Disabled: true, + } + require.NoError(t, db.Create(&user).Error) + + // Store a one-time access token for the disabled user in the actor state store + token, _, err := StoreToken(t.Context(), svc.actorService, user.ID, time.Minute, false) + require.NoError(t, err) + + dbConfig := appconfig.NewTestConfig(nil) + exchangedUser, accessToken, err := svc.ExchangeToken(t.Context(), dbConfig, token, "", "", "") + + var userDisabledErr *common.UserDisabledError + require.ErrorAs(t, err, &userDisabledErr) + require.Empty(t, exchangedUser.ID) + require.Empty(t, accessToken) + + // The token must have been restored (not consumed), since the exchange failed because the user is disabled + var state TokenState + err = host.GetState(t.Context(), TokenActorType, token, &state) + require.NoError(t, err) + require.Equal(t, user.ID, state.UserID) + + require.Empty(t, auditLog.events) +} diff --git a/backend/internal/service/e2etest_service.go b/backend/internal/service/e2etest_service.go index 22b36638..aacbc1fd 100644 --- a/backend/internal/service/e2etest_service.go +++ b/backend/internal/service/e2etest_service.go @@ -15,6 +15,8 @@ import ( "github.com/go-webauthn/webauthn/protocol" "github.com/google/uuid" + "github.com/italypaleale/francis/actor" + "github.com/italypaleale/francis/host/local" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" @@ -31,6 +33,7 @@ import ( "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/oidc" + "github.com/pocket-id/pocket-id/backend/internal/onetimeaccess" "github.com/pocket-id/pocket-id/backend/internal/storage" "github.com/pocket-id/pocket-id/backend/internal/usersignup" "github.com/pocket-id/pocket-id/backend/internal/utils" @@ -41,6 +44,7 @@ import ( type TestService struct { db *gorm.DB + actors *local.Host jwtService *JwtService appConfigService *appconfig.AppConfigService ldapService *LdapService @@ -56,9 +60,10 @@ const ( e2eRefreshTokenExpiredFixtureToken = "X4vqwtRyCUaq51UafHea4Fsg8Km6CAns6vp3tuX4" ) -func NewTestService(db *gorm.DB, appConfigService *appconfig.AppConfigService, jwtService *JwtService, ldapService *LdapService, appLockService *AppLockService, fileStorage storage.FileStorage) (*TestService, error) { +func NewTestService(db *gorm.DB, actors *local.Host, appConfigService *appconfig.AppConfigService, jwtService *JwtService, ldapService *LdapService, appLockService *AppLockService, fileStorage storage.FileStorage) (*TestService, error) { s := &TestService{ db: db, + actors: actors, appConfigService: appConfigService, jwtService: jwtService, ldapService: ldapService, @@ -136,29 +141,6 @@ func (s *TestService) SeedDatabase(baseURL string) error { } } - oneTimeAccessTokens := []model.OneTimeAccessToken{{ - Base: model.Base{ - ID: "bf877753-4ea4-4c9c-bbbd-e198bb201cb8", - }, - Token: "HPe6k6uiDRRVuAQV", - ExpiresAt: datatype.DateTime(time.Now().Add(1 * time.Hour)), - UserID: users[0].ID, - }, - { - Base: model.Base{ - ID: "d3afae24-fe2d-4a98-abec-cf0b8525096a", - }, - Token: "YCGDtftvsvYWiXd0", - ExpiresAt: datatype.DateTime(time.Now().Add(-1 * time.Second)), // expired - UserID: users[0].ID, - }, - } - for _, token := range oneTimeAccessTokens { - if err := tx.Create(&token).Error; err != nil { - return err - } - } - userGroups := []model.UserGroup{ { Base: model.Base{ @@ -340,15 +322,6 @@ func (s *TestService) SeedDatabase(baseURL string) error { return err } - accessToken := model.OneTimeAccessToken{ - Token: "one-time-token", - ExpiresAt: datatype.DateTime(time.Now().Add(1 * time.Hour)), - UserID: users[0].ID, - } - if err := tx.Create(&accessToken).Error; err != nil { - return err - } - userAuthorizedClients := []model.UserAuthorizedOidcClient{ { Scope: datatype.StringList{"openid", "profile", "email"}, @@ -506,53 +479,6 @@ func (s *TestService) SeedDatabase(baseURL string) error { } } - signupTokens := []usersignup.SignupToken{ - { - Base: model.Base{ - ID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", - }, - Token: "VALID1234567890A", - ExpiresAt: datatype.DateTime(time.Now().Add(24 * time.Hour)), - UsageLimit: 1, - UsageCount: 0, - UserGroups: []model.UserGroup{ - userGroups[0], - }, - }, - { - Base: model.Base{ - ID: "dc3c9c96-714e-48eb-926e-2d7c7858e6cf", - }, - Token: "PARTIAL567890ABC", - ExpiresAt: datatype.DateTime(time.Now().Add(7 * 24 * time.Hour)), - UsageLimit: 5, - UsageCount: 2, - }, - { - Base: model.Base{ - ID: "44de1863-ffa5-4db1-9507-4887cd7a1e3f", - }, - Token: "EXPIRED34567890B", - ExpiresAt: datatype.DateTime(time.Now().Add(-24 * time.Hour)), // Expired - UsageLimit: 3, - UsageCount: 1, - }, - { - Base: model.Base{ - ID: "f1b1678b-7720-4d8b-8f91-1dbff1e2d02b", - }, - Token: "FULLYUSED567890C", - ExpiresAt: datatype.DateTime(time.Now().Add(24 * time.Hour)), - UsageLimit: 1, - UsageCount: 1, // Usage limit reached - }, - } - for _, token := range signupTokens { - if err := tx.Create(&token).Error; err != nil { - return err - } - } - emailVerificationTokens := []model.EmailVerificationToken{ { Base: model.Base{ @@ -599,6 +525,116 @@ func (s *TestService) SeedDatabase(baseURL string) error { return err } + // One-time access tokens and signup tokens live in the actor state store, so they're seeded separately from the DB transaction above. + err = s.seedOneTimeAccessTokens(context.Background()) + if err != nil { + return fmt.Errorf("failed to seed one-time access tokens: %w", err) + } + + err = s.seedSignupTokens(context.Background()) + if err != nil { + return fmt.Errorf("failed to seed signup tokens: %w", err) + } + + return nil +} + +// seedSignupTokens seeds the signup tokens used by E2E tests into the signup token singleton actor. +// The already-expired fixture token is intentionally not seeded, since the actor would purge it right away via its cleanup alarm. +func (s *TestService) seedSignupTokens(ctx context.Context) error { + now := time.Now().Round(time.Second) + tokens := map[string]usersignup.SignupTokenState{ + "VALID1234567890A": { + ID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + ExpiresAt: now.Add(24 * time.Hour), + UsageLimit: 1, + UsageCount: 0, + UserGroupIDs: []string{"c7ae7c01-28a3-4f3c-9572-1ee734ea8368"}, + CreatedAt: now, + }, + "PARTIAL567890ABC": { + ID: "dc3c9c96-714e-48eb-926e-2d7c7858e6cf", + ExpiresAt: now.Add(7 * 24 * time.Hour), + UsageLimit: 5, + UsageCount: 2, + CreatedAt: now, + }, + "FULLYUSED567890C": { + ID: "f1b1678b-7720-4d8b-8f91-1dbff1e2d02b", + ExpiresAt: now.Add(24 * time.Hour), + UsageLimit: 1, + UsageCount: 1, // Usage limit reached + CreatedAt: now, + }, + } + + // The actor state store isn't wiped by ResetDatabase, so remove any signup token left over from a previous test first + err := s.deleteAllSignupTokens(ctx) + if err != nil { + return err + } + + // Each signup token is its own actor, whose actor ID is the token's value + for token, state := range tokens { + _, err = s.actors.Service().Invoke(ctx, usersignup.SignupTokenActorType, token, usersignup.SignupTokenMethodCreate, state) + if err != nil { + return fmt.Errorf("failed to seed signup token %q: %w", token, err) + } + } + + return nil +} + +// deleteAllSignupTokens removes every signup token currently stored in the actor state store +func (s *TestService) deleteAllSignupTokens(ctx context.Context) error { + var after string + for { + res, err := s.actors.Service().ListStates(ctx, usersignup.SignupTokenActorType, &actor.ListStatesOpts{After: after}) + if err != nil { + return fmt.Errorf("failed to list signup tokens: %w", err) + } + + for _, st := range res.States { + _, err = s.actors.Service().Invoke(ctx, usersignup.SignupTokenActorType, st.ActorID, usersignup.SignupTokenMethodDelete, nil) + if err != nil { + return fmt.Errorf("failed to delete signup token %q: %w", st.ActorID, err) + } + } + + // An empty cursor means we've just read the last page + after = res.AfterID() + if after == "" { + return nil + } + } +} + +// seedOneTimeAccessTokens seeds the one-time access tokens used by E2E tests into the actor state store. +// Expired tokens are intentionally not seeded: with actor-backed storage an expired token is simply one that has no state, which the exchange flow already reports as invalid/expired. +func (s *TestService) seedOneTimeAccessTokens(ctx context.Context) error { + tokens := []struct { + token string + ttl time.Duration + }{ + {token: "HPe6k6uiDRRVuAQV", ttl: time.Hour}, + {token: "one-time-token", ttl: time.Hour}, + } + + for _, t := range tokens { + state := onetimeaccess.TokenState{ + UserID: e2eRefreshTokenUserID, + ExpiresAt: time.Now().Add(t.ttl).Round(time.Second), + } + // Seed through the actor's "restore" method (which sets the state) rather than writing the + // state directly: if an actor for this token is still active from a previous test (for + // example, one whose token was already consumed), invoking it refreshes its in-memory cache + // too, whereas a direct state write would leave that cache stale. + _, err := s.actors.Service().Invoke(ctx, onetimeaccess.TokenActorType, t.token, onetimeaccess.TokenMethodRestore, state) + if err != nil { + return fmt.Errorf("failed to seed one-time access token %q: %w", t.token, err) + } + } + return nil } diff --git a/backend/internal/service/one_time_access_email_sender.go b/backend/internal/service/one_time_access_email_sender.go new file mode 100644 index 00000000..1c558c4d --- /dev/null +++ b/backend/internal/service/one_time_access_email_sender.go @@ -0,0 +1,29 @@ +package service + +import ( + "context" + + "github.com/pocket-id/pocket-id/backend/internal/appconfig" + "github.com/pocket-id/pocket-id/backend/internal/onetimeaccess" + "github.com/pocket-id/pocket-id/backend/internal/utils/email" +) + +// OneTimeAccessEmailSender sends the one-time access email. +// It adapts the email service, which owns the email templates, to the interface the onetimeaccess module depends on. +type OneTimeAccessEmailSender struct { + emailService *EmailService +} + +func NewOneTimeAccessEmailSender(emailService *EmailService) *OneTimeAccessEmailSender { + return &OneTimeAccessEmailSender{emailService: emailService} +} + +// SendOneTimeAccessEmail implements onetimeaccess.EmailSender +func (s *OneTimeAccessEmailSender) SendOneTimeAccessEmail(ctx context.Context, dbConfig *appconfig.AppConfigModel, to email.Address, data onetimeaccess.EmailData) error { + return SendEmail(ctx, s.emailService, dbConfig, to, OneTimeAccessTemplate, &OneTimeAccessTemplateData{ + Code: data.Code, + LoginLink: data.LoginLink, + LoginLinkWithCode: data.LoginLinkWithCode, + ExpirationString: data.ExpirationString, + }) +} diff --git a/backend/internal/service/one_time_access_service.go b/backend/internal/service/one_time_access_service.go deleted file mode 100644 index af561ee3..00000000 --- a/backend/internal/service/one_time_access_service.go +++ /dev/null @@ -1,261 +0,0 @@ -package service - -import ( - "context" - "errors" - "fmt" - "log/slog" - "net/url" - "strings" - "time" - - "github.com/pocket-id/pocket-id/backend/internal/appconfig" - "github.com/pocket-id/pocket-id/backend/internal/common" - "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/utils" - "github.com/pocket-id/pocket-id/backend/internal/utils/email" - "gorm.io/gorm" - "gorm.io/gorm/clause" -) - -type OneTimeAccessService struct { - db *gorm.DB - userService *UserService - jwtService *JwtService - auditLogService *AuditLogService - emailService *EmailService -} - -func NewOneTimeAccessService(db *gorm.DB, userService *UserService, jwtService *JwtService, auditLogService *AuditLogService, emailService *EmailService) *OneTimeAccessService { - return &OneTimeAccessService{ - db: db, - userService: userService, - jwtService: jwtService, - auditLogService: auditLogService, - emailService: emailService, - } -} - -func (s *OneTimeAccessService) RequestOneTimeAccessEmailAsAdmin(ctx context.Context, dbConfig *appconfig.AppConfigModel, userID string, ttl time.Duration) error { - if !dbConfig.EmailOneTimeAccessAsAdminEnabled.IsTrue() { - return &common.OneTimeAccessDisabledError{} - } - - _, err := s.requestOneTimeAccessEmailInternal(ctx, userID, "", ttl, false, dbConfig) - return err -} - -func (s *OneTimeAccessService) RequestOneTimeAccessEmailAsUnauthenticatedUser(ctx context.Context, dbConfig *appconfig.AppConfigModel, userID, redirectPath string) (string, error) { - if !dbConfig.EmailOneTimeAccessAsUnauthenticatedEnabled.IsTrue() { - return "", &common.OneTimeAccessDisabledError{} - } - - var userId string - err := s.db.Model(&model.User{}).Select("id").Where("email = ?", userID).First(&userId).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - // Do not return error if user not found to prevent email enumeration - return "", nil - } else if err != nil { - return "", err - } - - deviceToken, err := s.requestOneTimeAccessEmailInternal(ctx, userId, redirectPath, 15*time.Minute, true, dbConfig) - if err != nil { - return "", err - } else if deviceToken == nil { - return "", errors.New("device token expected but not returned") - } - - return *deviceToken, nil -} - -func (s *OneTimeAccessService) requestOneTimeAccessEmailInternal(ctx context.Context, userID, redirectPath string, ttl time.Duration, withDeviceToken bool, dbConfig *appconfig.AppConfigModel) (*string, error) { - tx := s.db.Begin() - defer func() { - tx.Rollback() - }() - - user, err := s.userService.getUserInternal(ctx, userID, tx) - if err != nil { - return nil, err - } - - if user.Email == nil { - return nil, &common.UserEmailNotSetError{} - } - - oneTimeAccessToken, deviceToken, err := s.createOneTimeAccessTokenInternal(ctx, user.ID, ttl, withDeviceToken, tx) - if err != nil { - return nil, err - } - err = tx.Commit().Error - if err != nil { - return nil, err - } - - go func() { - // This runs in background, so use a context without cancellation (or it would be stopped when the request ends) - // We still want to have a context derived from the request's to carry over tracing info - innerCtx := context.WithoutCancel(ctx) - - link := common.EnvConfig.AppURL + "/lc" - linkWithCode := link + "/" + oneTimeAccessToken - - // Add redirect path to the link - if strings.HasPrefix(redirectPath, "/") { - encodedRedirectPath := url.QueryEscape(redirectPath) - linkWithCode = linkWithCode + "?redirect=" + encodedRedirectPath - } - - errInternal := SendEmail(innerCtx, s.emailService, dbConfig, email.Address{ - Name: user.FullName(), - Email: *user.Email, - }, OneTimeAccessTemplate, &OneTimeAccessTemplateData{ - Code: oneTimeAccessToken, - LoginLink: link, - LoginLinkWithCode: linkWithCode, - ExpirationString: utils.DurationToString(ttl), - }) - if errInternal != nil { - slog.ErrorContext(innerCtx, "Failed to send one-time access token email", slog.Any("error", errInternal), slog.String("address", *user.Email)) - return - } - }() - - return deviceToken, nil -} - -func (s *OneTimeAccessService) CreateOneTimeAccessToken(ctx context.Context, userID string, ttl time.Duration) (token string, err error) { - tx := s.db.Begin() - defer func() { - tx.Rollback() - }() - - // Load the user to ensure it exists - _, err = s.userService.getUserInternal(ctx, userID, tx) - if errors.Is(err, gorm.ErrRecordNotFound) { - return "", &common.UserNotFoundError{} - } else if err != nil { - return "", err - } - - // Create the one-time access token - token, _, err = s.createOneTimeAccessTokenInternal(ctx, userID, ttl, false, tx) - if err != nil { - return "", err - } - - // Commit - err = tx.Commit().Error - if err != nil { - return "", fmt.Errorf("error committing transaction: %w", err) - } - - return token, nil -} - -func (s *OneTimeAccessService) createOneTimeAccessTokenInternal(ctx context.Context, userID string, ttl time.Duration, withDeviceToken bool, tx *gorm.DB) (token string, deviceToken *string, err error) { - oneTimeAccessToken, err := NewOneTimeAccessToken(userID, ttl, withDeviceToken) - if err != nil { - return "", nil, err - } - - err = tx.WithContext(ctx).Create(oneTimeAccessToken).Error - if err != nil { - return "", nil, err - } - - return oneTimeAccessToken.Token, oneTimeAccessToken.DeviceToken, nil -} - -func (s *OneTimeAccessService) ExchangeOneTimeAccessToken(ctx context.Context, dbConfig *appconfig.AppConfigModel, token, deviceToken, ipAddress, userAgent string) (model.User, string, error) { - tx := s.db.Begin() - defer func() { - tx.Rollback() - }() - - var oneTimeAccessToken model.OneTimeAccessToken - err := tx. - WithContext(ctx). - Where("token = ? AND expires_at > ?", token, datatype.DateTime(time.Now())). - Preload("User"). - Clauses(clause.Locking{Strength: "UPDATE"}). - First(&oneTimeAccessToken). - Error - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return model.User{}, "", &common.TokenInvalidOrExpiredError{} - } - return model.User{}, "", err - } - if oneTimeAccessToken.DeviceToken != nil && deviceToken != *oneTimeAccessToken.DeviceToken { - return model.User{}, "", &common.DeviceCodeInvalid{} - } - if oneTimeAccessToken.User.Disabled { - return model.User{}, "", &common.UserDisabledError{} - } - - accessToken, err := s.jwtService.GenerateAccessToken( - oneTimeAccessToken.User, - AuthenticationMethodOneTimePassword, - dbConfig.SessionDuration.AsDurationMinutes(), - ) - if err != nil { - return model.User{}, "", err - } - - err = tx. - WithContext(ctx). - Delete(&oneTimeAccessToken). - Error - if err != nil { - return model.User{}, "", err - } - - s.auditLogService.Create( - ctx, model.AuditLogEventOneTimeAccessTokenSignIn, - ipAddress, userAgent, - oneTimeAccessToken.User.ID, model.AuditLogData{}, - tx, - ) - - err = tx.Commit().Error - if err != nil { - return model.User{}, "", fmt.Errorf("error committing transaction: %w", err) - } - - return oneTimeAccessToken.User, accessToken, nil -} - -func NewOneTimeAccessToken(userID string, ttl time.Duration, withDeviceToken bool) (*model.OneTimeAccessToken, error) { - // If expires at is less than 15 minutes, use a 6-character token instead of 16 - tokenLength := 16 - if ttl <= 15*time.Minute { - tokenLength = 6 - } - - token, err := utils.GenerateRandomUnambiguousString(tokenLength) - if err != nil { - return nil, err - } - - var deviceToken *string - if withDeviceToken { - dt, err := utils.GenerateRandomAlphanumericString(16) - if err != nil { - return nil, err - } - deviceToken = &dt - } - - now := time.Now().Round(time.Second) - o := &model.OneTimeAccessToken{ - UserID: userID, - ExpiresAt: datatype.DateTime(now.Add(ttl)), - Token: token, - DeviceToken: deviceToken, - } - - return o, nil -} diff --git a/backend/internal/service/one_time_access_service_test.go b/backend/internal/service/one_time_access_service_test.go deleted file mode 100644 index ff946043..00000000 --- a/backend/internal/service/one_time_access_service_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package service - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/pocket-id/pocket-id/backend/internal/appconfig" - "github.com/pocket-id/pocket-id/backend/internal/common" - "github.com/pocket-id/pocket-id/backend/internal/model" - datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" - testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing" -) - -func TestExchangeOneTimeAccessTokenRejectsDisabledUser(t *testing.T) { - db := testutils.NewDatabaseForTest(t) - appConfig := appconfig.NewTestAppConfigService(nil) - instanceID := newInstanceID(t, db) - jwtService := initJwtService(t, db, instanceID, appConfig, newTestEnvConfig()) - auditLogService := NewAuditLogService(db, nil, &GeoLiteService{}, appConfig) - oneTimeAccessService := NewOneTimeAccessService(db, nil, jwtService, auditLogService, nil) - - user := model.User{ - Base: model.Base{ID: "disabled-user"}, - Username: "disabled-user", - Disabled: true, - } - require.NoError(t, db.Create(&user).Error) - - loginCode := model.OneTimeAccessToken{ - Base: model.Base{ID: "disabled-user-login-code"}, - Token: "ABCDEF", - ExpiresAt: datatype.DateTime(time.Now().Add(time.Minute)), - UserID: user.ID, - } - require.NoError(t, db.Create(&loginCode).Error) - - dbConfig := appconfig.NewTestConfig(nil) - exchangedUser, accessToken, err := oneTimeAccessService.ExchangeOneTimeAccessToken(t.Context(), dbConfig, loginCode.Token, "", "", "") - - var userDisabledErr *common.UserDisabledError - require.ErrorAs(t, err, &userDisabledErr) - require.Empty(t, exchangedUser.ID) - require.Empty(t, accessToken) - - var remainingLoginCode model.OneTimeAccessToken - require.NoError(t, db.Where("token = ?", loginCode.Token).First(&remainingLoginCode).Error) - - var auditLogCount int64 - require.NoError(t, db.Model(&model.AuditLog{}).Where("user_id = ?", user.ID).Count(&auditLogCount).Error) - require.Zero(t, auditLogCount) -} diff --git a/backend/internal/usersignup/actor.go b/backend/internal/usersignup/actor.go new file mode 100644 index 00000000..68ba1b45 --- /dev/null +++ b/backend/internal/usersignup/actor.go @@ -0,0 +1,217 @@ +package usersignup + +import ( + "context" + "errors" + "fmt" + "log/slog" + "time" + + "github.com/italypaleale/francis/actor" + + "github.com/pocket-id/pocket-id/backend/internal/common" +) + +// Signup tokens are stored entirely in the actor state store. +// Each token is its own actor, whose actor ID is the token value itself. +// The state is persisted with a TTL equal to the token's lifetime, so it's purged automatically when the token expires (there's no separate cleanup job and no expiration alarm). +// Listing tokens uses ListStates, which only returns states that haven't expired yet. + +// SignupTokenActorType is the actor type for the signup token actor +const SignupTokenActorType = "SignupToken" + +// Methods exposed by the signup token actor +// Because we cannot invoke an actor while a DB transaction is open (that would deadlock on SQLite), consuming a token is done by invoking the actor first (which atomically validates it and increments its usage count), and only afterwards performing the remaining work. +// On failure, the caller compensates by releasing the token via the "release" method as best-effort. +const ( + // SignupTokenMethodCreate stores a new signup token, replacing any existing state + SignupTokenMethodCreate = "create" + // SignupTokenMethodDelete removes a signup token + SignupTokenMethodDelete = "delete" + + signupTokenMethodMigrate = "migrate" + signupTokenMethodConsume = "consume" + signupTokenMethodRelease = "release" +) + +// signupTokenConsumeStatus is the outcome of a "consume" invocation. +type signupTokenConsumeStatus string + +const ( + // signupTokenConsumeOK indicates the token was valid and one use has been consumed + signupTokenConsumeOK signupTokenConsumeStatus = "ok" + // signupTokenConsumeNotFound indicates the token doesn't exist (or has expired) + signupTokenConsumeNotFound signupTokenConsumeStatus = "not_found" + // signupTokenConsumeLimitReached indicates the token has no uses left + signupTokenConsumeLimitReached signupTokenConsumeStatus = "limit_reached" +) + +// SignupTokenState is the persisted state of a signup token actor. +// The token value itself is the actor's ID, so it isn't repeated here. +type SignupTokenState struct { + ID string + UsageLimit int + UsageCount int + UserGroupIDs []string + CreatedAt time.Time + ExpiresAt time.Time +} + +// signupTokenConsumeResponse is the response of the "consume" method +type signupTokenConsumeResponse struct { + Status signupTokenConsumeStatus + // UserGroupIDs is set only when Status is "ok", and contains the groups the new user should join + UserGroupIDs []string +} + +// signupTokenActor is the actor that manages a single signup token +type signupTokenActor struct { + log *slog.Logger + client actor.Client[SignupTokenState] +} + +// NewSignupTokenActor allocates a new signup token actor +// It satisfies actor.Factory +func NewSignupTokenActor(actorID string, service *actor.Service) actor.Actor { + return &signupTokenActor{ + log: slog.With( + slog.String("scope", "actor"), + slog.String("actorType", SignupTokenActorType), + ), + client: actor.NewActorClient[SignupTokenState](SignupTokenActorType, actorID, service), + } +} + +// Invoke implements actor.ActorInvoke +func (a *signupTokenActor) Invoke(parentCtx context.Context, method string, data actor.Envelope) (any, error) { + switch method { + case SignupTokenMethodCreate: + return nil, a.create(parentCtx, data, false) + case signupTokenMethodMigrate: + return nil, a.create(parentCtx, data, true) + case signupTokenMethodConsume: + return a.consume(parentCtx) + case signupTokenMethodRelease: + return nil, a.release(parentCtx) + case SignupTokenMethodDelete: + return nil, a.delete(parentCtx) + default: + return nil, common.ErrUnsupportedActorMethod{Method: method} + } +} + +// create stores the token's state. +// When onlyIfMissing is true the write is skipped if the actor already has state: this is used by the one-time migration of the pre-actor tokens, so a token that has already been migrated is never reset. +func (a *signupTokenActor) create(parentCtx context.Context, data actor.Envelope, onlyIfMissing bool) error { + if data == nil { + return fmt.Errorf("request body is empty for method '%s'", SignupTokenMethodCreate) + } + + var state SignupTokenState + err := data.Decode(&state) + if err != nil { + return fmt.Errorf("request body is not valid for method '%s': %w", SignupTokenMethodCreate, err) + } + + if onlyIfMissing { + ctx, cancel := context.WithTimeout(parentCtx, 10*time.Second) + defer cancel() + current, err := a.client.GetState(ctx) + if err != nil { + return fmt.Errorf("error retrieving actor state: %w", err) + } + + // An empty ID means there's no state yet + if current.ID != "" { + return nil + } + } + + return a.setState(parentCtx, state) +} + +// consume atomically validates the token and, if it's still usable, records one more use. +func (a *signupTokenActor) consume(parentCtx context.Context) (signupTokenConsumeResponse, error) { + ctx, cancel := context.WithTimeout(parentCtx, 10*time.Second) + defer cancel() + state, err := a.client.GetState(ctx) + if err != nil { + return signupTokenConsumeResponse{}, fmt.Errorf("error retrieving actor state: %w", err) + } + + // An empty ID means there's no state: the token doesn't exist (or its state already expired and was purged) + if state.ID == "" || state.ExpiresAt.Before(time.Now()) { + return signupTokenConsumeResponse{ + Status: signupTokenConsumeNotFound, + }, nil + } + + if state.UsageCount >= state.UsageLimit { + return signupTokenConsumeResponse{ + Status: signupTokenConsumeLimitReached, + }, nil + } + + // Consume one use of the token + state.UsageCount++ + err = a.setState(parentCtx, state) + if err != nil { + return signupTokenConsumeResponse{}, err + } + + return signupTokenConsumeResponse{ + Status: signupTokenConsumeOK, + UserGroupIDs: state.UserGroupIDs, + }, nil +} + +// release reverts the usage count increment performed while consuming the token, to compensate when the signup could not be completed. +func (a *signupTokenActor) release(parentCtx context.Context) error { + ctx, cancel := context.WithTimeout(parentCtx, 10*time.Second) + defer cancel() + state, err := a.client.GetState(ctx) + if err != nil { + return fmt.Errorf("error retrieving actor state: %w", err) + } + + // The token is gone (for example, it expired and was purged) or was never consumed: nothing to compensate + if state.ID == "" || state.UsageCount <= 0 { + return nil + } + + state.UsageCount-- + return a.setState(parentCtx, state) +} + +// delete removes the token. +func (a *signupTokenActor) delete(parentCtx context.Context) error { + ctx, cancel := context.WithTimeout(parentCtx, 10*time.Second) + defer cancel() + err := a.client.DeleteState(ctx) + if err != nil && !errors.Is(err, actor.ErrStateNotFound) { + // Deleting a token that doesn't exist (for example, one that expired in the meanwhile) already reaches the desired end state + return fmt.Errorf("error deleting actor state: %w", err) + } + + return nil +} + +// setState saves the state with a TTL matching the token's remaining lifetime, so it's purged automatically once the token expires. +// Saving is skipped if the token has already expired, since there would be nothing left to store. +func (a *signupTokenActor) setState(parentCtx context.Context, state SignupTokenState) error { + ttl := time.Until(state.ExpiresAt) + if ttl <= 0 { + return nil + } + + ctx, cancel := context.WithTimeout(parentCtx, 10*time.Second) + defer cancel() + err := a.client.SetState(ctx, state, &actor.SetStateOpts{ + TTL: ttl, + }) + if err != nil { + return fmt.Errorf("error saving actor state: %w", err) + } + + return nil +} diff --git a/backend/internal/usersignup/actor_test.go b/backend/internal/usersignup/actor_test.go new file mode 100644 index 00000000..c147a5bd --- /dev/null +++ b/backend/internal/usersignup/actor_test.go @@ -0,0 +1,173 @@ +package usersignup + +import ( + "testing" + "time" + + "github.com/italypaleale/francis/actor" + "github.com/italypaleale/francis/host/local" + "github.com/stretchr/testify/require" + + testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing" +) + +// newSignupTokenActorService starts a test actor host with the signup token actor registered and returns its service +func newSignupTokenActorService(t *testing.T) *actor.Service { + t.Helper() + + var svc *actor.Service + testutils.NewActorHostForTest(t, func(t *testing.T, h *local.Host) { + err := h.RegisterActor(SignupTokenActorType, NewSignupTokenActor) + require.NoError(t, err) + svc = h.Service() + }) + require.NotNil(t, svc) + + return svc +} + +func createSignupTokenForTest(t *testing.T, svc *actor.Service, token string, state SignupTokenState) { + t.Helper() + _, err := svc.Invoke(t.Context(), SignupTokenActorType, token, SignupTokenMethodCreate, state) + require.NoError(t, err) +} + +func consumeSignupTokenForTest(t *testing.T, svc *actor.Service, token string) signupTokenConsumeResponse { + t.Helper() + res, err := svc.Invoke(t.Context(), SignupTokenActorType, token, signupTokenMethodConsume, nil) + require.NoError(t, err) + + var out signupTokenConsumeResponse + err = res.Decode(&out) + require.NoError(t, err) + + return out +} + +// listSignupTokenIDsForTest returns the actor IDs (that is, the token values) of every stored signup token +func listSignupTokenIDsForTest(t *testing.T, svc *actor.Service) []string { + t.Helper() + res, err := svc.ListStates(t.Context(), SignupTokenActorType, nil) + require.NoError(t, err) + + ids := make([]string, len(res.States)) + for i, st := range res.States { + ids[i] = st.ActorID + } + + return ids +} + +func TestSignupTokenActorConsume(t *testing.T) { + svc := newSignupTokenActorService(t) + + createSignupTokenForTest(t, svc, "token-1", SignupTokenState{ + ID: "id-1", + ExpiresAt: time.Now().Add(time.Hour), + UsageLimit: 1, + UserGroupIDs: []string{"group-a", "group-b"}, + CreatedAt: time.Now(), + }) + + // First consume succeeds and returns the token's user groups + res := consumeSignupTokenForTest(t, svc, "token-1") + require.Equal(t, signupTokenConsumeOK, res.Status) + require.Equal(t, []string{"group-a", "group-b"}, res.UserGroupIDs) + + // Second consume fails: the usage limit (1) has been reached + res = consumeSignupTokenForTest(t, svc, "token-1") + require.Equal(t, signupTokenConsumeLimitReached, res.Status) +} + +func TestSignupTokenActorConsumeNotFound(t *testing.T) { + svc := newSignupTokenActorService(t) + + res := consumeSignupTokenForTest(t, svc, "does-not-exist") + require.Equal(t, signupTokenConsumeNotFound, res.Status) +} + +// TestSignupTokenActorCreateExpired verifies that a token that has already expired is never stored, since its state TTL would be in the past +func TestSignupTokenActorCreateExpired(t *testing.T) { + svc := newSignupTokenActorService(t) + + createSignupTokenForTest(t, svc, "token-expired", SignupTokenState{ + ID: "id-expired", + ExpiresAt: time.Now().Add(-time.Minute), + UsageLimit: 1, + CreatedAt: time.Now().Add(-time.Hour), + }) + + require.Empty(t, listSignupTokenIDsForTest(t, svc)) + require.Equal(t, signupTokenConsumeNotFound, consumeSignupTokenForTest(t, svc, "token-expired").Status) +} + +func TestSignupTokenActorRelease(t *testing.T) { + svc := newSignupTokenActorService(t) + + createSignupTokenForTest(t, svc, "token-2", SignupTokenState{ + ID: "id-2", + ExpiresAt: time.Now().Add(time.Hour), + UsageLimit: 2, + CreatedAt: time.Now(), + }) + + // Consume both uses + require.Equal(t, signupTokenConsumeOK, consumeSignupTokenForTest(t, svc, "token-2").Status) + require.Equal(t, signupTokenConsumeOK, consumeSignupTokenForTest(t, svc, "token-2").Status) + require.Equal(t, signupTokenConsumeLimitReached, consumeSignupTokenForTest(t, svc, "token-2").Status) + + // Release one use (compensation) + _, err := svc.Invoke(t.Context(), SignupTokenActorType, "token-2", signupTokenMethodRelease, nil) + require.NoError(t, err) + + // Consuming succeeds again now that a use was released + require.Equal(t, signupTokenConsumeOK, consumeSignupTokenForTest(t, svc, "token-2").Status) +} + +func TestSignupTokenActorDelete(t *testing.T) { + svc := newSignupTokenActorService(t) + + createSignupTokenForTest(t, svc, "token-3", SignupTokenState{ + ID: "id-3", + ExpiresAt: time.Now().Add(time.Hour), + UsageLimit: 1, + CreatedAt: time.Now(), + }) + require.Equal(t, []string{"token-3"}, listSignupTokenIDsForTest(t, svc)) + + _, err := svc.Invoke(t.Context(), SignupTokenActorType, "token-3", SignupTokenMethodDelete, nil) + require.NoError(t, err) + + require.Empty(t, listSignupTokenIDsForTest(t, svc)) + + // The token can no longer be consumed + require.Equal(t, signupTokenConsumeNotFound, consumeSignupTokenForTest(t, svc, "token-3").Status) + + // Deleting a token that no longer exists is a no-op + _, err = svc.Invoke(t.Context(), SignupTokenActorType, "token-3", SignupTokenMethodDelete, nil) + require.NoError(t, err) +} + +// TestSignupTokenActorMigrateDoesNotOverwrite verifies that the one-time migration never resets a token that was already migrated and used since +func TestSignupTokenActorMigrateDoesNotOverwrite(t *testing.T) { + svc := newSignupTokenActorService(t) + + state := SignupTokenState{ + ID: "id-4", + ExpiresAt: time.Now().Add(time.Hour), + UsageLimit: 2, + CreatedAt: time.Now(), + } + createSignupTokenForTest(t, svc, "token-4", state) + + // Use the token once + require.Equal(t, signupTokenConsumeOK, consumeSignupTokenForTest(t, svc, "token-4").Status) + + // Re-running the migration must not reset the usage count + _, err := svc.Invoke(t.Context(), SignupTokenActorType, "token-4", signupTokenMethodMigrate, state) + require.NoError(t, err) + + // Only one use is left, so a single consume succeeds and the next one doesn't + require.Equal(t, signupTokenConsumeOK, consumeSignupTokenForTest(t, svc, "token-4").Status) + require.Equal(t, signupTokenConsumeLimitReached, consumeSignupTokenForTest(t, svc, "token-4").Status) +} diff --git a/backend/internal/usersignup/cleanup.go b/backend/internal/usersignup/cleanup.go deleted file mode 100644 index bcbc0971..00000000 --- a/backend/internal/usersignup/cleanup.go +++ /dev/null @@ -1,19 +0,0 @@ -package usersignup - -import ( - "context" - "time" - - "gorm.io/gorm" - - datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" -) - -// CleanupExpiredSignupTokens deletes signup tokens that have expired -// It returns the number of rows removed -func CleanupExpiredSignupTokens(ctx context.Context, db *gorm.DB) (int64, error) { - st := db. - WithContext(ctx). - Delete(&SignupToken{}, "expires_at < ?", datatype.DateTime(time.Now())) - return st.RowsAffected, st.Error -} diff --git a/backend/internal/usersignup/handler.go b/backend/internal/usersignup/handler.go index c8d96ef2..ec1552c2 100644 --- a/backend/internal/usersignup/handler.go +++ b/backend/internal/usersignup/handler.go @@ -56,7 +56,8 @@ func (h *handler) signUpInitialAdmin(c *gin.Context) { } var input signUpDto - if err := dto.ShouldBindWithNormalizedJSON(c, &input); err != nil { + err = dto.ShouldBindWithNormalizedJSON(c, &input) + if err != nil { _ = c.Error(err) return } @@ -68,7 +69,8 @@ func (h *handler) signUpInitialAdmin(c *gin.Context) { } var userDto dto.UserDto - if err := dto.MapStruct(user, &userDto); err != nil { + err = dto.MapStruct(user, &userDto) + if err != nil { _ = c.Error(err) return } @@ -136,7 +138,8 @@ func (h *handler) listSignupTokens(c *gin.Context) { } var tokensDto []signupTokenDto - if err := dto.MapStructList(tokens, &tokensDto); err != nil { + err = dto.MapStructList(tokens, &tokensDto) + if err != nil { _ = c.Error(err) return } @@ -183,15 +186,13 @@ func (h *handler) signup(c *gin.Context) { } var input signUpDto - if err := dto.ShouldBindWithNormalizedJSON(c, &input); err != nil { + err = dto.ShouldBindWithNormalizedJSON(c, &input) + if err != nil { _ = c.Error(err) return } - ipAddress := c.ClientIP() - userAgent := c.GetHeader("User-Agent") - - user, accessToken, err := h.service.SignUp(c.Request.Context(), config, input, ipAddress, userAgent) + user, accessToken, err := h.service.SignUp(c.Request.Context(), config, input, c.ClientIP(), c.GetHeader("User-Agent")) if err != nil { _ = c.Error(err) return @@ -201,7 +202,8 @@ func (h *handler) signup(c *gin.Context) { cookie.AddAccessTokenCookie(c, maxAge, accessToken) var userDto dto.UserDto - if err := dto.MapStruct(user, &userDto); err != nil { + err = dto.MapStruct(user, &userDto) + if err != nil { _ = c.Error(err) return } diff --git a/backend/internal/usersignup/migration.go b/backend/internal/usersignup/migration.go new file mode 100644 index 00000000..7d2d2cbb --- /dev/null +++ b/backend/internal/usersignup/migration.go @@ -0,0 +1,105 @@ +package usersignup + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log/slog" + "time" + + "gorm.io/gorm" + + "github.com/pocket-id/pocket-id/backend/internal/model" +) + +// This file holds the one-time migration of the pre-actor signup tokens. +// The "actor tokens" migration freezes the signup_tokens table (and its user-group associations) into a JSON document stored in the "kv" table under the "signup_tokens_migrated" key. +// It's loaded here to create the per-token actors on first startup. + +// signupTokensMigratedKey is the kv key under which the pre-actor signup tokens were frozen. +const signupTokensMigratedKey = "signup_tokens_migrated" //nolint:gosec // G101 false positive: this is the name of a kv key, not a credential + +// migratedSignupToken is the JSON shape of a signup token frozen into the kv table by the migration. +// All timestamps are expressed as Unix seconds. +type migratedSignupToken struct { + ID string `json:"id"` + Token string `json:"token"` + ExpiresAt int64 `json:"expiresAt"` + UsageLimit int `json:"usageLimit"` + UsageCount int `json:"usageCount"` + UserGroupIDs []string `json:"userGroupIds"` + CreatedAt int64 `json:"createdAt"` +} + +// migrateSignupTokens creates an actor for every signup token frozen into the kv table by the migration. +// It requires the actor state store to be available, so it must run after the actor host is ready. +// It is idempotent: tokens that have already been migrated are left untouched, so a token that has been used since it was migrated is never reset. +func (s *Service) migrateSignupTokens(ctx context.Context) error { + migrated, err := loadMigratedSignupTokens(ctx, s.db) + if err != nil { + return err + } + if len(migrated) == 0 { + return nil + } + + var count int + for _, m := range migrated { + // Skip tokens that have already expired, since there would be nothing left to store + expiresAt := time.Unix(m.ExpiresAt, 0) + if !expiresAt.After(time.Now()) { + continue + } + + state := SignupTokenState{ + ID: m.ID, + ExpiresAt: expiresAt, + UsageLimit: m.UsageLimit, + UsageCount: m.UsageCount, + UserGroupIDs: m.UserGroupIDs, + CreatedAt: time.Unix(m.CreatedAt, 0), + } + + // The token's value is the actor's ID + // The "migrate" method only writes the state if the actor doesn't have one already + _, err = s.actorService.Invoke(ctx, SignupTokenActorType, m.Token, signupTokenMethodMigrate, state) + if err != nil { + return fmt.Errorf("error migrating signup token '%s': %w", m.ID, err) + } + count++ + } + + slog.InfoContext(ctx, "Migrated signup tokens to actors", slog.Int("count", count)) + + return nil +} + +// loadMigratedSignupTokens reads the signup tokens frozen into the kv table by the migration +// It returns nil if there's nothing to migrate +func loadMigratedSignupTokens(ctx context.Context, db *gorm.DB) ([]migratedSignupToken, error) { + row := model.KV{ + Key: signupTokensMigratedKey, + } + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + err := db.WithContext(ctx).First(&row).Error + switch { + case errors.Is(err, gorm.ErrRecordNotFound): + // There are no migrated signup tokens in the database, nothing to do + return nil, nil + case err != nil: + return nil, fmt.Errorf("failed to load migrated signup tokens from the database: %w", err) + case row.Value == nil || len(*row.Value) == 0: + // Also no migrated signup tokens, nothing to do + return nil, nil + } + + var migrated []migratedSignupToken + err = json.Unmarshal([]byte(*row.Value), &migrated) + if err != nil { + return nil, fmt.Errorf("error parsing migrated signup tokens: %w", err) + } + + return migrated, nil +} diff --git a/backend/internal/usersignup/migration_test.go b/backend/internal/usersignup/migration_test.go new file mode 100644 index 00000000..35515cfe --- /dev/null +++ b/backend/internal/usersignup/migration_test.go @@ -0,0 +1,207 @@ +package usersignup + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "gorm.io/gorm" + + "github.com/pocket-id/pocket-id/backend/internal/utils" + testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing" +) + +// versionBeforeMoveTokens is the migration version right before the "actor tokens" migration. +const versionBeforeMoveTokens = 20260722120000 + +// seedSignupTokensForMigration seeds two signup tokens (one with a user group, one without) into the pre-migration schema. +func seedSignupTokensForMigration(t *testing.T, db *gorm.DB, createdAt, expiresAt time.Time) { + t.Helper() + + // An unrelated, non-JSON kv entry, to ensure the freeze/restore queries don't choke on other kv keys + err := db.Exec( + `INSERT INTO kv ("key", "value") VALUES ('instance_id', ?)`, + "not-json-instance-id", + ).Error + require.NoError(t, err) + + // A user group referenced by one of the tokens + err = db.Exec( + `INSERT INTO user_groups (id, created_at, friendly_name, name) VALUES (?, ?, ?, ?)`, + "grp-1", createdAt.Unix(), "Group One", "group-one", + ).Error + require.NoError(t, err) + + // A token with a user group + err = db.Exec( + `INSERT INTO signup_tokens (id, created_at, token, expires_at, usage_limit, usage_count) VALUES (?, ?, ?, ?, ?, ?)`, + "tok-1", createdAt.Unix(), "TOKENWITHGROUP01", expiresAt.Unix(), 3, 1, + ).Error + require.NoError(t, err) + err = db.Exec( + `INSERT INTO signup_tokens_user_groups (signup_token_id, user_group_id) VALUES (?, ?)`, + "tok-1", "grp-1", + ).Error + require.NoError(t, err) + + // A token without user groups + err = db.Exec( + `INSERT INTO signup_tokens (id, created_at, token, expires_at, usage_limit, usage_count) VALUES (?, ?, ?, ?, ?, ?)`, + "tok-2", createdAt.Unix(), "TOKENNOGROUP0002", expiresAt.Unix(), 1, 0, + ).Error + require.NoError(t, err) +} + +func TestLoadMigratedSignupTokens(t *testing.T) { + createdAt := time.Now().Add(-time.Hour).Truncate(time.Second) + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + + db := testutils.NewDatabaseForTestWithMigrationSeed(t, versionBeforeMoveTokens, func(t *testing.T, db *gorm.DB) { + seedSignupTokensForMigration(t, db, createdAt, expiresAt) + }) + + // The migration must have dropped the signup_tokens tables + ok := db.Migrator().HasTable("signup_tokens") + require.False(t, ok, "signup_tokens table should have been dropped") + ok = db.Migrator().HasTable("signup_tokens_user_groups") + require.False(t, ok, "signup_tokens_user_groups table should have been dropped") + + tokens, err := loadMigratedSignupTokens(t.Context(), db) + require.NoError(t, err) + require.Len(t, tokens, 2) + + byID := make(map[string]migratedSignupToken, len(tokens)) + for _, tok := range tokens { + byID[tok.ID] = tok + } + + tok1 := byID["tok-1"] + require.Equal(t, "TOKENWITHGROUP01", tok1.Token) + require.Equal(t, 3, tok1.UsageLimit) + require.Equal(t, 1, tok1.UsageCount) + require.Equal(t, []string{"grp-1"}, tok1.UserGroupIDs) + require.Equal(t, expiresAt.Unix(), tok1.ExpiresAt) + require.Equal(t, createdAt.Unix(), tok1.CreatedAt) + + tok2 := byID["tok-2"] + require.Equal(t, "TOKENNOGROUP0002", tok2.Token) + require.Equal(t, 1, tok2.UsageLimit) + require.Equal(t, 0, tok2.UsageCount) + require.Empty(t, tok2.UserGroupIDs) +} + +// TestMigrateSignupTokens verifies that the frozen signup tokens are turned into per-token actors, and that already-expired ones are skipped +func TestMigrateSignupTokens(t *testing.T) { + createdAt := time.Now().Add(-time.Hour).Truncate(time.Second) + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + + db := testutils.NewDatabaseForTestWithMigrationSeed(t, versionBeforeMoveTokens, func(t *testing.T, db *gorm.DB) { + seedSignupTokensForMigration(t, db, createdAt, expiresAt) + + // A token that has already expired: it must not be migrated + err := db.Exec( + `INSERT INTO signup_tokens (id, created_at, token, expires_at, usage_limit, usage_count) VALUES (?, ?, ?, ?, ?, ?)`, + "tok-expired", createdAt.Unix(), "EXPIREDTOKEN0003", time.Now().Add(-time.Hour).Unix(), 1, 0, + ).Error + require.NoError(t, err) + }) + + svc := newSignupServiceForTest(t, db, fakeUserCreator{}) + + err := svc.migrateSignupTokens(t.Context()) + require.NoError(t, err) + + entries, err := svc.listSignupTokenStates(t.Context()) + require.NoError(t, err) + require.Len(t, entries, 2) + + byToken := make(map[string]SignupTokenState, len(entries)) + for _, e := range entries { + byToken[e.Token] = e.State + } + + tok1 := byToken["TOKENWITHGROUP01"] + require.Equal(t, "tok-1", tok1.ID) + require.Equal(t, 3, tok1.UsageLimit) + require.Equal(t, 1, tok1.UsageCount) + require.Equal(t, []string{"grp-1"}, tok1.UserGroupIDs) + require.Equal(t, expiresAt.Unix(), tok1.ExpiresAt.Unix()) + require.Equal(t, createdAt.Unix(), tok1.CreatedAt.Unix()) + + tok2 := byToken["TOKENNOGROUP0002"] + require.Equal(t, "tok-2", tok2.ID) + require.Empty(t, tok2.UserGroupIDs) + + // The expired token must not have been migrated + require.NotContains(t, byToken, "EXPIREDTOKEN0003") + + // The migration is idempotent: re-running it doesn't reset a token that has been used since + require.Equal(t, signupTokenConsumeOK, consumeSignupTokenForTest(t, svc.actorService, "TOKENNOGROUP0002").Status) + err = svc.migrateSignupTokens(t.Context()) + require.NoError(t, err) + require.Equal(t, signupTokenConsumeLimitReached, consumeSignupTokenForTest(t, svc.actorService, "TOKENNOGROUP0002").Status) +} + +// TestLoadMigratedSignupTokensEmpty verifies that when there were no signup tokens, nothing is frozen and nothing is loaded. +func TestLoadMigratedSignupTokensEmpty(t *testing.T) { + db := testutils.NewDatabaseForTest(t) + + tokens, err := loadMigratedSignupTokens(t.Context(), db) + require.NoError(t, err) + require.Empty(t, tokens) +} + +// TestMoveTokensToActorStateDown verifies that rolling the migration back recreates the signup token tables and restores their contents from the frozen kv document. +func TestMoveTokensToActorStateDown(t *testing.T) { + createdAt := time.Now().Add(-time.Hour).Truncate(time.Second) + expiresAt := time.Now().Add(24 * time.Hour).Truncate(time.Second) + + db := testutils.NewDatabaseForTestWithMigrationSeed(t, versionBeforeMoveTokens, func(t *testing.T, db *gorm.DB) { + seedSignupTokensForMigration(t, db, createdAt, expiresAt) + }) + + // The tables were frozen and dropped by the up migration + ok := db.Migrator().HasTable("signup_tokens") + require.False(t, ok) + + // Roll the migration back + sqlDB, err := db.DB() + require.NoError(t, err) + m, cleanup, err := utils.GetEmbeddedMigrateInstance(t.Context(), sqlDB) + require.NoError(t, err) + defer cleanup() + + err = m.Migrate(versionBeforeMoveTokens) + require.NoError(t, err) + + // The tables must have been recreated and repopulated from the frozen document + ok = db.Migrator().HasTable("signup_tokens") + require.True(t, ok) + ok = db.Migrator().HasTable("signup_tokens_user_groups") + require.True(t, ok) + + type row struct { + ID string + Token string + UsageLimit int + UsageCount int + } + var rows []row + err = db.Raw(`SELECT id, token, usage_limit, usage_count FROM signup_tokens ORDER BY id`).Scan(&rows).Error + require.NoError(t, err) + require.Equal(t, []row{ + {ID: "tok-1", Token: "TOKENWITHGROUP01", UsageLimit: 3, UsageCount: 1}, + {ID: "tok-2", Token: "TOKENNOGROUP0002", UsageLimit: 1, UsageCount: 0}, + }, rows) + + var groupID string + err = db.Raw(`SELECT user_group_id FROM signup_tokens_user_groups WHERE signup_token_id = ?`, "tok-1").Scan(&groupID).Error + require.NoError(t, err) + require.Equal(t, "grp-1", groupID) + + // The frozen document must have been removed from the kv table + var kvCount int64 + err = db.Raw(`SELECT count(*) FROM kv WHERE "key" = ?`, signupTokensMigratedKey).Scan(&kvCount).Error + require.NoError(t, err) + require.Zero(t, kvCount) +} diff --git a/backend/internal/usersignup/models.go b/backend/internal/usersignup/models.go index ddbf5b3e..7c3b31cb 100644 --- a/backend/internal/usersignup/models.go +++ b/backend/internal/usersignup/models.go @@ -1,8 +1,6 @@ package usersignup import ( - "time" - "github.com/pocket-id/pocket-id/backend/internal/model" datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" ) @@ -15,17 +13,5 @@ type SignupToken struct { ExpiresAt datatype.DateTime `json:"expiresAt" sortable:"true"` UsageLimit int `json:"usageLimit" sortable:"true"` UsageCount int `json:"usageCount" sortable:"true"` - UserGroups []model.UserGroup `gorm:"many2many:signup_tokens_user_groups;"` -} - -func (st *SignupToken) IsExpired() bool { - return time.Time(st.ExpiresAt).Before(time.Now()) -} - -func (st *SignupToken) IsUsageLimitReached() bool { - return st.UsageCount >= st.UsageLimit -} - -func (st *SignupToken) IsValid() bool { - return !st.IsExpired() && !st.IsUsageLimitReached() + UserGroups []model.UserGroup `json:"userGroups"` } diff --git a/backend/internal/usersignup/module.go b/backend/internal/usersignup/module.go index 8a7acacf..7698ba6d 100644 --- a/backend/internal/usersignup/module.go +++ b/backend/internal/usersignup/module.go @@ -2,9 +2,11 @@ package usersignup import ( "context" + "fmt" "time" "github.com/gin-gonic/gin" + "github.com/italypaleale/francis/host/local" "gorm.io/gorm" "github.com/pocket-id/pocket-id/backend/internal/appconfig" @@ -30,7 +32,8 @@ type AppConfigResolver interface { } type Dependencies struct { - DB *gorm.DB + DB *gorm.DB + Actors *local.Host Signer TokenService AuditLog AuditLogger @@ -43,12 +46,32 @@ type Module struct { handler *handler } -func New(deps Dependencies) *Module { - service := newService(deps) +func New(deps Dependencies) (*Module, error) { + // Register the actor that manages a signup token + // Each token is its own actor, whose actor ID is the token's value + err := deps.Actors.RegisterActor(SignupTokenActorType, NewSignupTokenActor) + if err != nil { + return nil, fmt.Errorf("error registering the %s actor: %w", SignupTokenActorType, err) + } + + service := newService(deps, deps.Actors.Service()) return &Module{ service: service, handler: newHandler(service, deps.AppConfig), + }, nil +} + +// RunSignupTokenMigration performs the one-time migration of the pre-actor signup tokens, then blocks until the context is canceled. +// It's meant to be started as a background service gated on the actor host being ready, since the migration needs the actor state store. +// Note that it must not return before the context is canceled, as the service runner stops the application as soon as any of its services returns. +func (m *Module) RunSignupTokenMigration(ctx context.Context) error { + err := m.service.migrateSignupTokens(ctx) + if err != nil { + return fmt.Errorf("failed to migrate signup tokens: %w", err) } + + <-ctx.Done() + return ctx.Err() } // RegisterRoutes mounts the signup and signup-token management endpoints diff --git a/backend/internal/usersignup/service.go b/backend/internal/usersignup/service.go index 26b23157..0a26f6b3 100644 --- a/backend/internal/usersignup/service.go +++ b/backend/internal/usersignup/service.go @@ -2,12 +2,15 @@ package usersignup import ( "context" - "errors" + "fmt" + "log/slog" + "sort" "strings" "time" + "github.com/google/uuid" + "github.com/italypaleale/francis/actor" "gorm.io/gorm" - "gorm.io/gorm/clause" "github.com/pocket-id/pocket-id/backend/internal/appconfig" "github.com/pocket-id/pocket-id/backend/internal/common" @@ -22,57 +25,49 @@ import ( const authenticationMethodOneTimePassword = "otp" type Service struct { - db *gorm.DB - userCreator UserCreator - signer TokenService - auditLog AuditLogger + db *gorm.DB + actorService *actor.Service + userCreator UserCreator + signer TokenService + auditLog AuditLogger } -func newService(deps Dependencies) *Service { +func newService(deps Dependencies, actorService *actor.Service) *Service { return &Service{ - db: deps.DB, - userCreator: deps.UserCreator, - signer: deps.Signer, - auditLog: deps.AuditLog, + db: deps.DB, + actorService: actorService, + userCreator: deps.UserCreator, + signer: deps.Signer, + auditLog: deps.AuditLog, } } func (s *Service) SignUp(ctx context.Context, config *appconfig.AppConfigModel, signupData signUpDto, ipAddress, userAgent string) (model.User, string, error) { - tx := s.db.Begin() - defer func() { - tx.Rollback() - }() - tokenProvided := signupData.Token != "" if config.AllowUserSignups.String() != "open" && !tokenProvided { return model.User{}, "", &common.OpenSignupDisabledError{} } - var signupToken SignupToken var userGroupIDs []string if tokenProvided { - err := tx. - WithContext(ctx). - Preload("UserGroups"). - Where("token = ?", signupData.Token). - Clauses(clause.Locking{Strength: "UPDATE"}). - First(&signupToken). - Error + // Consume the signup token by invoking its actor: this atomically validates it and increments its usage count + // Note: must invoke outside of a DB transaction, since invoking an actor while a transaction is open would deadlock on SQLite + res, err := s.actorService.Invoke(ctx, SignupTokenActorType, signupData.Token, signupTokenMethodConsume, nil) if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return model.User{}, "", &common.TokenInvalidOrExpiredError{} - } - return model.User{}, "", err + return model.User{}, "", fmt.Errorf("error invoking signup token actor: %w", err) } - if !signupToken.IsValid() { + var consumeRes signupTokenConsumeResponse + err = res.Decode(&consumeRes) + if err != nil { + return model.User{}, "", fmt.Errorf("error decoding signup token actor response: %w", err) + } + + if consumeRes.Status != signupTokenConsumeOK { return model.User{}, "", &common.TokenInvalidOrExpiredError{} } - - for _, group := range signupToken.UserGroups { - userGroupIDs = append(userGroupIDs, group.ID) - } + userGroupIDs = consumeRes.UserGroupIDs } userToCreate := dto.UserCreateDto{ @@ -85,6 +80,27 @@ func (s *Service) SignUp(ctx context.Context, config *appconfig.AppConfigModel, EmailVerified: config.EmailsVerified.IsTrue(), } + // The token has now been consumed + // From this point on, if we hit an error we compensate by releasing the token (best-effort) + user, accessToken, err := s.createSignedUpUser(ctx, config, userToCreate, signupData.Token, tokenProvided, ipAddress, userAgent) + if err != nil { + if tokenProvided { + s.releaseSignupToken(ctx, signupData.Token) + } + return model.User{}, "", err + } + + return user, accessToken, nil +} + +// createSignedUpUser creates the user and issues an access token within a single transaction. +// It performs no actor calls, so it's safe to keep the transaction open for its whole duration. +func (s *Service) createSignedUpUser(ctx context.Context, config *appconfig.AppConfigModel, userToCreate dto.UserCreateDto, token string, tokenProvided bool, ipAddress, userAgent string) (model.User, string, error) { + tx := s.db.Begin() + defer func() { + tx.Rollback() + }() + user, err := s.userCreator.CreateUserInternal(ctx, config, userToCreate, false, tx) if err != nil { return model.User{}, "", err @@ -97,15 +113,8 @@ func (s *Service) SignUp(ctx context.Context, config *appconfig.AppConfigModel, if tokenProvided { s.auditLog.Create(ctx, model.AuditLogEventAccountCreated, ipAddress, userAgent, user.ID, model.AuditLogData{ - "signupToken": signupToken.Token, + "signupToken": token, }, tx) - - signupToken.UsageCount++ - - err = tx.WithContext(ctx).Save(&signupToken).Error - if err != nil { - return model.User{}, "", err - } } else { s.auditLog.Create(ctx, model.AuditLogEventAccountCreated, ipAddress, userAgent, user.ID, model.AuditLogData{ "method": "open_signup", @@ -120,6 +129,19 @@ func (s *Service) SignUp(ctx context.Context, config *appconfig.AppConfigModel, return user, accessToken, nil } +// releaseSignupToken reverts the usage count increment performed while consuming a token, used to compensate when the signup could not be completed. +// It's a best-effort compensation: if it fails (or the process crashes before it runs) we accept that a token use was consumed unnecessarily +func (s *Service) releaseSignupToken(parentCtx context.Context, token string) { + // Use a context that is not canceled when the original request ends + ctx, cancel := context.WithTimeout(context.WithoutCancel(parentCtx), 10*time.Second) + defer cancel() + + _, err := s.actorService.Invoke(ctx, SignupTokenActorType, token, signupTokenMethodRelease, nil) + if err != nil { + slog.ErrorContext(ctx, "Failed to release signup token after a failed signup", slog.Any("error", err)) + } +} + func (s *Service) SignUpInitialAdmin(ctx context.Context, config *appconfig.AppConfigModel, signUpData signUpDto) (model.User, string, error) { tx := s.db.Begin() defer func() { @@ -177,55 +199,273 @@ func (s *Service) isInitialAdminSetupCompleted(ctx context.Context, db *gorm.DB) } func (s *Service) ListSignupTokens(ctx context.Context, listRequestOptions utils.ListRequestOptions) ([]SignupToken, utils.PaginationResponse, error) { - var tokens []SignupToken - query := s.db.WithContext(ctx).Preload("UserGroups").Model(&SignupToken{}) + // Each signup token is its own actor, so we enumerate the stored states (expired ones are filtered out by the state store), then sort and paginate in memory + entries, err := s.listSignupTokenStates(ctx) + if err != nil { + return nil, utils.PaginationResponse{}, err + } - pagination, err := utils.PaginateFilterAndSort(listRequestOptions, query, &tokens) - return tokens, pagination, err + // Resolve the referenced user groups so they can be included in the response + groupsByID, err := s.loadUserGroupsByID(ctx, entries) + if err != nil { + return nil, utils.PaginationResponse{}, err + } + + tokens := make([]SignupToken, len(entries)) + for i, e := range entries { + tokens[i] = signupTokenModelFromState(e.Token, e.State, resolveUserGroups(e.State.UserGroupIDs, groupsByID)) + } + + return paginateSignupTokens(tokens, listRequestOptions) } func (s *Service) DeleteSignupToken(ctx context.Context, tokenID string) error { - return s.db.WithContext(ctx).Delete(&SignupToken{}, "id = ?", tokenID).Error + // Tokens are addressed by their value (the actor ID), while the API deletes them by ID, so we look up the matching token first + entries, err := s.listSignupTokenStates(ctx) + if err != nil { + return err + } + + for _, e := range entries { + if e.State.ID != tokenID { + continue + } + + _, err = s.actorService.Invoke(ctx, SignupTokenActorType, e.Token, SignupTokenMethodDelete, nil) + if err != nil { + return fmt.Errorf("error deleting signup token via actor: %w", err) + } + return nil + } + + // The token doesn't exist (or has expired): deleting it already reaches the desired end state + return nil +} + +// signupTokenEntry pairs a signup token's value (which is its actor ID) with its stored state +type signupTokenEntry struct { + Token string + State SignupTokenState +} + +// listSignupTokenStates returns every signup token currently stored in the actor state store. +// Expired tokens are not returned, since the state store filters out states whose TTL has passed. +func (s *Service) listSignupTokenStates(ctx context.Context) ([]signupTokenEntry, error) { + var ( + entries []signupTokenEntry + after string + ) + for { + res, err := s.actorService.ListStates(ctx, SignupTokenActorType, &actor.ListStatesOpts{ + IncludeData: true, + After: after, + }) + if err != nil { + return nil, fmt.Errorf("error listing signup token states: %w", err) + } + + for _, st := range res.States { + if st.Data == nil { + continue + } + + var state SignupTokenState + err = st.Data.Decode(&state) + if err != nil { + return nil, fmt.Errorf("error decoding state of signup token actor '%s': %w", st.ActorID, err) + } + + entries = append(entries, signupTokenEntry{ + Token: st.ActorID, + State: state, + }) + } + + // An empty cursor means we've just read the last page + after = res.AfterID() + if after == "" { + break + } + } + + return entries, nil } func (s *Service) CreateSignupToken(ctx context.Context, ttl time.Duration, usageLimit int, userGroupIDs []string) (SignupToken, error) { - signupToken, err := newSignupToken(ttl, usageLimit) - if err != nil { - return SignupToken{}, err - } - + // Load the referenced user groups to validate them and to include them in the response var userGroups []model.UserGroup - err = s.db.WithContext(ctx). - Where("id IN ?", userGroupIDs). - Find(&userGroups). - Error - if err != nil { - return SignupToken{}, err - } - signupToken.UserGroups = userGroups - - err = s.db.WithContext(ctx).Create(signupToken).Error - if err != nil { - return SignupToken{}, err + if len(userGroupIDs) > 0 { + err := s.db.WithContext(ctx). + Where("id IN ?", userGroupIDs). + Find(&userGroups). + Error + if err != nil { + return SignupToken{}, err + } } - return *signupToken, nil -} + validGroupIDs := make([]string, len(userGroups)) + for i, g := range userGroups { + validGroupIDs[i] = g.ID + } -func newSignupToken(ttl time.Duration, usageLimit int) (*SignupToken, error) { // Generate a random token randomString, err := utils.GenerateRandomAlphanumericString(16) + if err != nil { + return SignupToken{}, err + } + + now := time.Now().Round(time.Second) + state := SignupTokenState{ + ID: uuid.NewString(), + ExpiresAt: now.Add(ttl), + UsageLimit: usageLimit, + UsageCount: 0, + UserGroupIDs: validGroupIDs, + CreatedAt: now, + } + + // The token's value is the actor's ID + _, err = s.actorService.Invoke(ctx, SignupTokenActorType, randomString, SignupTokenMethodCreate, state) + if err != nil { + return SignupToken{}, fmt.Errorf("error creating signup token via actor: %w", err) + } + + return signupTokenModelFromState(randomString, state, userGroups), nil +} + +// loadUserGroupsByID loads every user group referenced by the given tokens, keyed by ID. +func (s *Service) loadUserGroupsByID(ctx context.Context, entries []signupTokenEntry) (map[string]model.UserGroup, error) { + idSet := make(map[string]struct{}) + for _, e := range entries { + for _, id := range e.State.UserGroupIDs { + idSet[id] = struct{}{} + } + } + if len(idSet) == 0 { + return map[string]model.UserGroup{}, nil + } + + ids := make([]string, 0, len(idSet)) + for id := range idSet { + ids = append(ids, id) + } + + var groups []model.UserGroup + err := s.db.WithContext(ctx). + Where("id IN ?", ids). + Find(&groups). + Error if err != nil { return nil, err } - now := time.Now().Round(time.Second) - token := &SignupToken{ - Token: randomString, - ExpiresAt: datatype.DateTime(now.Add(ttl)), - UsageLimit: usageLimit, - UsageCount: 0, + byID := make(map[string]model.UserGroup, len(groups)) + for _, g := range groups { + byID[g.ID] = g } - return token, nil + return byID, nil +} + +// resolveUserGroups maps the given group IDs to the corresponding UserGroup objects, preserving order and skipping any that no longer exist. +func resolveUserGroups(ids []string, byID map[string]model.UserGroup) []model.UserGroup { + if len(ids) == 0 { + return nil + } + + groups := make([]model.UserGroup, 0, len(ids)) + for _, id := range ids { + g, ok := byID[id] + if ok { + groups = append(groups, g) + } + } + + return groups +} + +// signupTokenModelFromState builds the API/model representation of a signup token from its actor ID (the token's value) and stored state. +func signupTokenModelFromState(token string, state SignupTokenState, groups []model.UserGroup) SignupToken { + return SignupToken{ + Base: model.Base{ + ID: state.ID, + CreatedAt: datatype.DateTime(state.CreatedAt), + }, + Token: token, + ExpiresAt: datatype.DateTime(state.ExpiresAt), + UsageLimit: state.UsageLimit, + UsageCount: state.UsageCount, + UserGroups: groups, + } +} + +// paginateSignupTokens sorts and paginates the in-memory list of signup tokens, mirroring the behavior of the DB-backed pagination utility. +func paginateSignupTokens(tokens []SignupToken, params utils.ListRequestOptions) ([]SignupToken, utils.PaginationResponse, error) { + sortSignupTokens(tokens, params.Sort.Column, params.Sort.Direction) + + page := max(params.Pagination.Page, 1) + pageSize := params.Pagination.Limit + switch { + case pageSize < 1: + pageSize = 20 + case pageSize > 100: + pageSize = 100 + } + + totalItems := int64(len(tokens)) + totalPages := (totalItems + int64(pageSize) - 1) / int64(pageSize) + if totalItems == 0 { + totalPages = 1 + } + if int64(page) > totalPages { + page = int(totalPages) + } + + start := min((page-1)*pageSize, len(tokens)) + end := min(start+pageSize, len(tokens)) + + return tokens[start:end], utils.PaginationResponse{ + TotalPages: totalPages, + TotalItems: totalItems, + CurrentPage: page, + ItemsPerPage: pageSize, + }, nil +} + +// sortSignupTokens sorts the tokens by the given column and direction. +// It defaults to sorting by creation date ascending, matching the DB-backed listing. +func sortSignupTokens(tokens []SignupToken, column, direction string) { + desc := utils.NormalizeSortDirection(direction) == "desc" + + less := func(i, j int) bool { + caI := time.Time(tokens[i].CreatedAt) + caJ := time.Time(tokens[j].CreatedAt) + return caI.Before(caJ) + } + switch column { + case "expiresAt": + less = func(i, j int) bool { + eaI := time.Time(tokens[i].ExpiresAt) + eaJ := time.Time(tokens[j].ExpiresAt) + return eaI.Before(eaJ) + } + case "usageLimit": + less = func(i, j int) bool { return tokens[i].UsageLimit < tokens[j].UsageLimit } + case "usageCount": + less = func(i, j int) bool { + return tokens[i].UsageCount < tokens[j].UsageCount + } + case "createdAt", "": + // Use the default comparator (creation date) + default: + // Unknown or non-sortable column: keep the default (creation date) ordering + } + + sort.SliceStable(tokens, func(i, j int) bool { + if desc { + return less(j, i) + } + return less(i, j) + }) } diff --git a/backend/internal/usersignup/service_test.go b/backend/internal/usersignup/service_test.go new file mode 100644 index 00000000..bc3a424b --- /dev/null +++ b/backend/internal/usersignup/service_test.go @@ -0,0 +1,127 @@ +package usersignup + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" + "gorm.io/gorm" + + "github.com/pocket-id/pocket-id/backend/internal/appconfig" + "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" + "github.com/pocket-id/pocket-id/backend/internal/utils" + testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing" +) + +type fakeUserCreator struct { + err error + user model.User +} + +func (f fakeUserCreator) CreateUserInternal(_ context.Context, _ *appconfig.AppConfigModel, _ dto.UserCreateDto, _ bool, _ *gorm.DB) (model.User, error) { + if f.err != nil { + return model.User{}, f.err + } + return f.user, nil +} + +type fakeSigner struct{} + +func (fakeSigner) GenerateAccessToken(_ model.User, _ string, _ time.Duration) (string, error) { + return "access-token", nil +} + +type fakeAuditLogger struct{} + +func (fakeAuditLogger) Create(_ context.Context, _ model.AuditLogEvent, _, _, _ string, _ model.AuditLogData, _ *gorm.DB) (model.AuditLog, bool) { + return model.AuditLog{}, true +} + +func newSignupServiceForTest(t *testing.T, db *gorm.DB, userCreator UserCreator) *Service { + t.Helper() + actorService := newSignupTokenActorService(t) + return newService(Dependencies{ + DB: db, + UserCreator: userCreator, + Signer: fakeSigner{}, + AuditLog: fakeAuditLogger{}, + }, actorService) +} + +func signupTokenUsageCount(t *testing.T, svc *Service, tokenID string) int { + t.Helper() + tokens, _, err := svc.ListSignupTokens(t.Context(), listAllOptions()) + require.NoError(t, err) + for _, tok := range tokens { + if tok.ID == tokenID { + return tok.UsageCount + } + } + t.Fatalf("signup token %q not found", tokenID) + return 0 +} + +func TestSignUpConsumesTokenOnSuccess(t *testing.T) { + db := testutils.NewDatabaseForTest(t) + svc := newSignupServiceForTest(t, db, fakeUserCreator{user: model.User{Base: model.Base{ID: "new-user"}}}) + + token, err := svc.CreateSignupToken(t.Context(), time.Hour, 2, nil) + require.NoError(t, err) + + config := appconfig.NewTestConfig(nil) + user, accessToken, err := svc.SignUp(t.Context(), config, signUpDto{ + Username: "newuser", + Token: token.Token, + }, "1.2.3.4", "test-agent") + require.NoError(t, err) + require.Equal(t, "new-user", user.ID) + require.Equal(t, "access-token", accessToken) + + // The token's usage count must have been incremented and not rolled back + require.Equal(t, 1, signupTokenUsageCount(t, svc, token.ID)) +} + +func TestSignUpCompensatesTokenOnFailure(t *testing.T) { + db := testutils.NewDatabaseForTest(t) + boom := errors.New("could not create user") + svc := newSignupServiceForTest(t, db, fakeUserCreator{err: boom}) + + token, err := svc.CreateSignupToken(t.Context(), time.Hour, 2, nil) + require.NoError(t, err) + + config := appconfig.NewTestConfig(nil) + _, _, err = svc.SignUp(t.Context(), config, signUpDto{ + Username: "newuser", + Token: token.Token, + }, "1.2.3.4", "test-agent") + require.ErrorIs(t, err, boom) + + // The usage count increment must have been compensated (reverted back to 0) + require.Equal(t, 0, signupTokenUsageCount(t, svc, token.ID)) +} + +func TestSignUpRejectsInvalidToken(t *testing.T) { + db := testutils.NewDatabaseForTest(t) + svc := newSignupServiceForTest(t, db, fakeUserCreator{user: model.User{Base: model.Base{ID: "new-user"}}}) + + config := appconfig.NewTestConfig(nil) + _, _, err := svc.SignUp(t.Context(), config, signUpDto{ + Username: "newuser", + Token: "not-a-real-token", + }, "1.2.3.4", "test-agent") + + var invalidErr *common.TokenInvalidOrExpiredError + require.ErrorAs(t, err, &invalidErr) +} + +// listAllOptions returns list options that return every token on a single page. +func listAllOptions() utils.ListRequestOptions { + var opts utils.ListRequestOptions + opts.Pagination.Page = 1 + opts.Pagination.Limit = 100 + return opts +} diff --git a/backend/resources/migrations/postgres/20260723000000_actor_tokens.down.sql b/backend/resources/migrations/postgres/20260723000000_actor_tokens.down.sql new file mode 100644 index 00000000..516579fb --- /dev/null +++ b/backend/resources/migrations/postgres/20260723000000_actor_tokens.down.sql @@ -0,0 +1,56 @@ +-- Recreate the one_time_access_tokens table with the schema it had before it was dropped. +CREATE TABLE one_time_access_tokens +( + id UUID NOT NULL PRIMARY KEY, + created_at TIMESTAMPTZ, + token VARCHAR(255) NOT NULL UNIQUE, + expires_at TIMESTAMPTZ NOT NULL, + user_id UUID NOT NULL REFERENCES users ON DELETE CASCADE, + device_token VARCHAR(16) +); +CREATE INDEX IF NOT EXISTS idx_one_time_access_tokens_expires_at ON one_time_access_tokens (expires_at); + +-- Recreate the signup token tables with the schema they had before they were frozen. +CREATE TABLE signup_tokens ( + id UUID NOT NULL PRIMARY KEY, + created_at TIMESTAMPTZ NOT NULL, + token VARCHAR(255) NOT NULL UNIQUE, + expires_at TIMESTAMPTZ NOT NULL, + usage_limit INTEGER NOT NULL DEFAULT 1, + usage_count INTEGER NOT NULL DEFAULT 0 +); +CREATE INDEX idx_signup_tokens_token ON signup_tokens(token); +CREATE INDEX idx_signup_tokens_expires_at ON signup_tokens(expires_at); + +CREATE TABLE signup_tokens_user_groups +( + signup_token_id UUID NOT NULL, + user_group_id UUID NOT NULL, + PRIMARY KEY (signup_token_id, user_group_id), + FOREIGN KEY (signup_token_id) REFERENCES signup_tokens (id) ON DELETE CASCADE, + FOREIGN KEY (user_group_id) REFERENCES user_groups (id) ON DELETE CASCADE +); + +-- Restore the signup tokens from the frozen JSON document stored in the "kv" table. +-- json_array_elements expands the JSON array into one row per token object. +INSERT INTO signup_tokens (id, created_at, token, expires_at, usage_limit, usage_count) +SELECT + (e ->> 'id')::uuid, + to_timestamp((e ->> 'createdAt')::bigint), + e ->> 'token', + to_timestamp((e ->> 'expiresAt')::bigint), + (e ->> 'usageLimit')::int, + (e ->> 'usageCount')::int +FROM kv, json_array_elements(kv."value"::json) AS e +WHERE kv."key" = 'signup_tokens_migrated'; + +-- Restore the token/user-group associations, expanding each token's nested userGroupIds array. +INSERT INTO signup_tokens_user_groups (signup_token_id, user_group_id) +SELECT + (e ->> 'id')::uuid, + g.value::uuid +FROM kv, json_array_elements(kv."value"::json) AS e, json_array_elements_text(e -> 'userGroupIds') AS g +WHERE kv."key" = 'signup_tokens_migrated'; + +-- Remove the frozen signup tokens from the "kv" table. +DELETE FROM kv WHERE "key" = 'signup_tokens_migrated'; diff --git a/backend/resources/migrations/postgres/20260723000000_actor_tokens.up.sql b/backend/resources/migrations/postgres/20260723000000_actor_tokens.up.sql new file mode 100644 index 00000000..bb5cd847 --- /dev/null +++ b/backend/resources/migrations/postgres/20260723000000_actor_tokens.up.sql @@ -0,0 +1,28 @@ +-- One-time access tokens are now stored in the actor state store, so the table is no longer needed. +DROP TABLE IF EXISTS one_time_access_tokens; + +-- Freeze the signup tokens. +-- Encode every signup token (with its user group IDs) as a single JSON array and store it in the "kv" table under the "signup_tokens_migrated" key, so the singleton signup token actor can seed its state from it on first startup. +-- The "HAVING count(*) > 0" clause ensures nothing is written to the "kv" table when there are no signup tokens. +-- Timestamps are stored as Unix seconds so the frozen format is identical across databases. +INSERT INTO kv ("key", "value") +SELECT 'signup_tokens_migrated', json_agg( + json_build_object( + 'id', st.id, + 'token', st.token, + 'expiresAt', extract(epoch FROM st.expires_at)::bigint, + 'usageLimit', st.usage_limit, + 'usageCount', st.usage_count, + 'createdAt', extract(epoch FROM st.created_at)::bigint, + 'userGroupIds', COALESCE( + (SELECT json_agg(stug.user_group_id) FROM signup_tokens_user_groups stug WHERE stug.signup_token_id = st.id), + '[]'::json + ) + ) +)::text +FROM signup_tokens st +HAVING count(*) > 0; + +-- Drop the now-frozen signup token tables. +DROP TABLE signup_tokens_user_groups; +DROP TABLE signup_tokens; diff --git a/backend/resources/migrations/sqlite/20260723000000_actor_tokens.down.sql b/backend/resources/migrations/sqlite/20260723000000_actor_tokens.down.sql new file mode 100644 index 00000000..b7627995 --- /dev/null +++ b/backend/resources/migrations/sqlite/20260723000000_actor_tokens.down.sql @@ -0,0 +1,62 @@ +PRAGMA foreign_keys=OFF; +BEGIN; + +-- Recreate the one_time_access_tokens table with the schema it had before it was dropped. +CREATE TABLE one_time_access_tokens +( + id TEXT PRIMARY KEY, + created_at DATETIME NOT NULL, + token TEXT NOT NULL UNIQUE, + expires_at DATETIME NOT NULL, + user_id TEXT NOT NULL REFERENCES users ON DELETE CASCADE, + device_token TEXT +); +CREATE INDEX IF NOT EXISTS idx_one_time_access_tokens_expires_at ON one_time_access_tokens (expires_at); + +-- Recreate the signup token tables with the schema they had before they were frozen. +CREATE TABLE signup_tokens ( + id TEXT NOT NULL PRIMARY KEY, + created_at DATETIME NOT NULL, + token TEXT NOT NULL UNIQUE, + expires_at DATETIME NOT NULL, + usage_limit INTEGER NOT NULL DEFAULT 1, + usage_count INTEGER NOT NULL DEFAULT 0 +); +CREATE INDEX idx_signup_tokens_token ON signup_tokens(token); +CREATE INDEX idx_signup_tokens_expires_at ON signup_tokens(expires_at); + +CREATE TABLE signup_tokens_user_groups +( + signup_token_id TEXT NOT NULL, + user_group_id TEXT NOT NULL, + PRIMARY KEY (signup_token_id, user_group_id), + FOREIGN KEY (signup_token_id) REFERENCES signup_tokens (id) ON DELETE CASCADE, + FOREIGN KEY (user_group_id) REFERENCES user_groups (id) ON DELETE CASCADE +); + +-- Restore the signup tokens from the frozen JSON document stored in the "kv" table. +-- json_each expands the JSON array into one row per token object. +INSERT INTO signup_tokens (id, created_at, token, expires_at, usage_limit, usage_count) +SELECT + json_extract(e.value, '$.id'), + json_extract(e.value, '$.createdAt'), + json_extract(e.value, '$.token'), + json_extract(e.value, '$.expiresAt'), + json_extract(e.value, '$.usageLimit'), + json_extract(e.value, '$.usageCount') +FROM kv, json_each(kv."value") AS e +WHERE kv."key" = 'signup_tokens_migrated'; + +-- Restore the token/user-group associations, expanding each token's nested userGroupIds array. +INSERT INTO signup_tokens_user_groups (signup_token_id, user_group_id) +SELECT + json_extract(e.value, '$.id'), + g.value +FROM kv, json_each(kv."value") AS e, json_each(json_extract(e.value, '$.userGroupIds')) AS g +WHERE kv."key" = 'signup_tokens_migrated'; + +-- Remove the frozen signup tokens from the "kv" table. +DELETE FROM kv WHERE "key" = 'signup_tokens_migrated'; + +COMMIT; +PRAGMA foreign_keys=ON; diff --git a/backend/resources/migrations/sqlite/20260723000000_actor_tokens.up.sql b/backend/resources/migrations/sqlite/20260723000000_actor_tokens.up.sql new file mode 100644 index 00000000..ddaff1cf --- /dev/null +++ b/backend/resources/migrations/sqlite/20260723000000_actor_tokens.up.sql @@ -0,0 +1,35 @@ +PRAGMA foreign_keys=OFF; +BEGIN; + +-- One-time access tokens are now stored in the actor state store, so the table is no longer needed. +DROP TABLE IF EXISTS one_time_access_tokens; + +-- Freeze the signup tokens. +-- Encode every signup token (with its user group IDs) as a single JSON array and store it in the "kv" table under the "signup_tokens_migrated" key, so the singleton signup token actor can seed its state from it on first startup. +-- The "HAVING count(*) > 0" clause ensures nothing is written to the "kv" table when there are no signup tokens. +-- Timestamps are stored as Unix seconds, matching how DateTime values are persisted on SQLite. +INSERT INTO kv ("key", "value") +SELECT 'signup_tokens_migrated', json_group_array( + json_object( + 'id', st.id, + 'token', st.token, + 'expiresAt', st.expires_at, + 'usageLimit', st.usage_limit, + 'usageCount', st.usage_count, + 'createdAt', st.created_at, + 'userGroupIds', json(( + SELECT COALESCE(json_group_array(stug.user_group_id), json_array()) + FROM signup_tokens_user_groups stug + WHERE stug.signup_token_id = st.id + )) + ) +) +FROM signup_tokens st +HAVING count(*) > 0; + +-- Drop the now-frozen signup token tables. +DROP TABLE signup_tokens_user_groups; +DROP TABLE signup_tokens; + +COMMIT; +PRAGMA foreign_keys=ON; diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 00000000..0e4bc1f7 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,5429 @@ +{ + "name": "pocket-id-frontend", + "version": "2.11.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pocket-id-frontend", + "version": "2.11.0", + "hasInstallScript": true, + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/exporter-trace-otlp-http": "^0.220.0", + "@opentelemetry/resources": "^2.8.0", + "@opentelemetry/sdk-trace-web": "^2.8.0", + "@opentelemetry/semantic-conventions": "^1.41.1", + "@simplewebauthn/browser": "^13.3.0", + "@tailwindcss/vite": "^4.3.0", + "axios": "^1.16.1", + "clsx": "^2.1.1", + "date-fns": "^4.2.1", + "qrcode": "^1.5.4", + "runed": "^0.37.1", + "sveltekit-superforms": "^2.30.1", + "tailwind-merge": "^3.6.0", + "zod": "^4.4.3" + }, + "devDependencies": { + "@inlang/paraglide-js": "^2.18.0", + "@inlang/plugin-m-function-matcher": "^2.2.6", + "@inlang/plugin-message-format": "^4.4.0", + "@internationalized/date": "^3.12.1", + "@lucide/svelte": "^1.16.0", + "@sveltejs/adapter-static": "^3.0.10", + "@sveltejs/kit": "^2.60.1", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@types/node": "^25.9.0", + "@types/qrcode": "^1.5.6", + "bits-ui": "^2.18.1", + "eslint": "^10.4.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.17.1", + "formsnap": "^2.0.1", + "globals": "^17.6.0", + "mode-watcher": "^1.1.0", + "prettier": "^3.8.3", + "prettier-plugin-svelte": "^3.5.2", + "prettier-plugin-tailwindcss": "^0.8.0", + "shadcn-svelte": "^1.3.0", + "svelte": "^5.55.8", + "svelte-check": "^4.4.8", + "svelte-sonner": "^1.1.1", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.3.0", + "tslib": "^2.8.1", + "tw-animate-css": "^1.4.0", + "typescript": "^6.0.3", + "typescript-eslint": "^8.59.4", + "vite": "^8.0.16", + "vite-plugin-compression": "^0.5.1" + } + }, + "node_modules/@ark/schema": { + "version": "0.56.2", + "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.56.2.tgz", + "integrity": "sha512-Qx4D2JFbBWpntiHZaTv7bGG4H/M2rigiknezKg/WVyDSaLdE4YCcWAOoFB7pjjDqHbbV2OqRfntm1nnXvwMexg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@ark/util": "0.56.2" + } + }, + "node_modules/@ark/util": { + "version": "0.56.2", + "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.56.2.tgz", + "integrity": "sha512-9kU2sUE38FZEGG7l3hamYMBieLYEJh2L1mrYD2eXpT+78EnQSV1bhjxJhnxGBMSTbtwpBSDNSK+K60WvaI/DTQ==", + "license": "MIT", + "optional": true + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@exodus/schemasafe": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", + "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", + "license": "MIT", + "optional": true + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inlang/paraglide-js": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/@inlang/paraglide-js/-/paraglide-js-2.22.0.tgz", + "integrity": "sha512-GSzG7KEKcYAhwuPNJczIPB+DzyndYxr4lsXAkkB7xh00jTrt80NF2KfgjEAkxuJvWcnscqXf7y7d1Q0SWtSu7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inlang/recommend-sherlock": "^0.2.1", + "@inlang/sdk": "^2.10.0", + "commander": "11.1.0", + "consola": "3.4.0", + "json5": "2.2.3", + "unplugin": "^2.1.2", + "urlpattern-polyfill": "^10.0.0" + }, + "bin": { + "paraglide-js": "bin/run.js" + }, + "peerDependencies": { + "typescript": ">=5.6", + "vite": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@inlang/plugin-m-function-matcher": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@inlang/plugin-m-function-matcher/-/plugin-m-function-matcher-2.2.9.tgz", + "integrity": "sha512-FqrEw6p5UKn0fVLedILx5vjEAPAl01Uuv1xlF6pQ9XMxpDsTUSOAPrjWuus1jiPBnYEdbK8cP43Ni0e3IPz9aQ==", + "dev": true, + "dependencies": { + "@inlang/sdk": "2.10.2" + } + }, + "node_modules/@inlang/plugin-message-format": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@inlang/plugin-message-format/-/plugin-message-format-4.4.0.tgz", + "integrity": "sha512-n4aXt6XVg5kxhKoLAhi9nMgZtCA9iS0QOaXte56VqxWHcfj9O4c4gOkyVQZH7H9D8h7OZufCrO1sZGYOypPwEA==", + "dev": true, + "dependencies": { + "flat": "^6.0.1" + } + }, + "node_modules/@inlang/recommend-sherlock": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@inlang/recommend-sherlock/-/recommend-sherlock-0.2.1.tgz", + "integrity": "sha512-ckv8HvHy/iTqaVAEKrr+gnl+p3XFNwe5D2+6w6wJk2ORV2XkcRkKOJ/XsTUJbPSiyi4PI+p+T3bqbmNx/rDUlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "comment-json": "^4.2.3" + } + }, + "node_modules/@inlang/sdk": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@inlang/sdk/-/sdk-2.10.2.tgz", + "integrity": "sha512-O1ki72SNK6LPagaGrvlioBb1mWKvump7cO7P85hfGZjdFTmDdn3icI0A6MvaBsB3P9KQHAjzyubnN1OslGufTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lix-js/sdk": "0.4.10", + "@sinclair/typebox": "^0.31.17", + "kysely": "^0.28.12", + "sqlite-wasm-kysely": "0.3.0", + "uuid": "^14.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@internationalized/date": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.2.tgz", + "integrity": "sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lix-js/sdk": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/@lix-js/sdk/-/sdk-0.4.10.tgz", + "integrity": "sha512-0dMInAJK/67guTG5rRZaCEhvzC5cCXENOjaePA5AqMXrCE97kaY7SRor9e2vnoGsFIiGqXKlT0MCIoZj36G0gg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@lix-js/server-protocol-schema": "0.1.1", + "dedent": "1.5.1", + "human-id": "^4.1.1", + "js-sha256": "^0.11.0", + "kysely": "^0.28.12", + "sqlite-wasm-kysely": "0.3.0", + "uuid": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@lix-js/server-protocol-schema": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@lix-js/server-protocol-schema/-/server-protocol-schema-0.1.1.tgz", + "integrity": "sha512-jBeALB6prAbtr5q4vTuxnRZZv1M2rKe8iNqRQhFJ4Tv7150unEa0vKyz0hs8Gl3fUGsWaNJBh3J8++fpbrpRBQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@lucide/svelte": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.25.0.tgz", + "integrity": "sha512-v9m+dD68jxVnqkU3K59mG/RSRFlPGzmKCGSyMfnXcaGv9jODDQMyQkcp1CGvk3Y/cUj9v7f8rw1n//K0B53xGQ==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.220.0.tgz", + "integrity": "sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.9.0.tgz", + "integrity": "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.220.0.tgz", + "integrity": "sha512-/+ExB3lRkf+erv4PnoywyL7RHKITidxtUpUTS55k7OQ0dB42S7gEF1gry7swb9MSm1hYLUhJg4QQh9W8SpwwqA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@opentelemetry/otlp-exporter-base": "0.220.0", + "@opentelemetry/otlp-transformer": "0.220.0", + "@opentelemetry/resources": "2.9.0", + "@opentelemetry/sdk-trace": "2.9.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.220.0.tgz", + "integrity": "sha512-CXYo8UD5Mn9YbgebO2EL4wejtA+gxLmLiu6HCk2KH2BR7XhFN6/6p1UlCb23DYCjeYkndevLHuejCCN1yx4+OQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@opentelemetry/otlp-transformer": "0.220.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.220.0.tgz", + "integrity": "sha512-lXGrv7KXZ0gNH9SVNUaa6vv6phVYGvJxfXAlMbzbakiXru75f5MZl8Z7oqiMMQD77riVHJCFlQvbZs/VVN2/4A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.220.0", + "@opentelemetry/core": "2.9.0", + "@opentelemetry/resources": "2.9.0", + "@opentelemetry/sdk-logs": "0.220.0", + "@opentelemetry/sdk-metrics": "2.9.0", + "@opentelemetry/sdk-trace": "2.9.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.9.0.tgz", + "integrity": "sha512-jyA5MBLQ+Dkl3+JsZkUoUvL7yHvU64kLsvpXKarWm6347Sl1t1bXFTFykUePNpT5WH5pm9a2Qtt03iIYQhZ1Fg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.220.0.tgz", + "integrity": "sha512-WywcTkQtv2iNmt+6y5Kcd4rzvx9bLVsBa2Nwcmg01IUaBTkTow3W4d9KE5vNBpEDtb9tp21WcRBY/lANRrApYA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.220.0", + "@opentelemetry/core": "2.9.0", + "@opentelemetry/resources": "2.9.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.9.0.tgz", + "integrity": "sha512-Xx8RGS4H5XEBl01WuCreMIpiah9cCXMbSkeuIePPdD2cUpq/vUzYmj8E/MK1OsbOc93FuAD4jfn2WOacKwLn7Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@opentelemetry/resources": "2.9.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.9.0.tgz", + "integrity": "sha512-sGA19HvtrrSKYsseHphluH6j3p6Xa3fqc7c7y8f/7mYWejc1lyDFcpSdD1kYa50HCLUeEo4zA5bW0pniaPszuw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@opentelemetry/resources": "2.9.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.9.0.tgz", + "integrity": "sha512-cp9zmTl62R8PJrpvFcmc8N2JQU/xfa0S+61q511Nji+QxCfZ8Ifvg7H27G8cANe4crg4RTrWsVvanHiXjSp6ag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@opentelemetry/resources": "2.9.0", + "@opentelemetry/sdk-trace": "2.9.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-web": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-web/-/sdk-trace-web-2.9.0.tgz", + "integrity": "sha512-LS4XlzOK3e6YYdt84m15AmRR04121rKipmifi4XFZToH1h75f7F2bzHLbB1NMgIEYJ0jSKYm4VsK5mws/5kfTQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@opentelemetry/sdk-trace-base": "2.9.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "license": "MIT" + }, + "node_modules/@poppinss/macroable": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@poppinss/macroable/-/macroable-1.1.2.tgz", + "integrity": "sha512-FAVBRzzWhYP5mA3lCwLH1A0fKBqq5anyjGet90Z81aRK5c/+LTGUE1zJhZrErjaenBSOOI9BVUs3WVmotneFQA==", + "license": "MIT", + "optional": true + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT" + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@simplewebauthn/browser": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.3.0.tgz", + "integrity": "sha512-BE/UWv6FOToAdVk0EokzkqQQDOWtNydYlY6+OrmiZ5SCNmb41VehttboTetUM3T/fr6EAFYVXjz4My2wg230rQ==", + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.31.30", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.31.30.tgz", + "integrity": "sha512-MGsM7bVmHg3sUKCphlu3SGQ+T+5JTbygZWYArfKQCW5anJeACHeqLhcnf+R7XlCWZ+QR3C8JTBx3kCKJTN69ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sqlite.org/sqlite-wasm": { + "version": "3.48.0-build4", + "resolved": "https://registry.npmjs.org/@sqlite.org/sqlite-wasm/-/sqlite-wasm-3.48.0-build4.tgz", + "integrity": "sha512-hI6twvUkzOmyGZhQMza1gpfqErZxXRw6JEsiVjUbo7tFanVD+8Oil0Ih3l2nGzHdxPI41zFmfUQG7GHqhciKZQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "sqlite-wasm": "bin/index.js" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.11.tgz", + "integrity": "sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-static": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", + "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.70.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.70.1.tgz", + "integrity": "sha512-nY9SPHGOZro3doud9vZXDBwl9tCZIouuJztjgSHs6PAIrv9M/z5O7eOhPV5xU7CgVHA976Jwu3BA1hIFvXztkA==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.9", + "@types/cookie": "^0.6.0", + "acorn": "^8.16.0", + "cookie": "^0.6.0", + "devalue": "^5.8.1", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "set-cookie-parser": "^3.0.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3 || ^6.0.0", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sveltejs/load-config": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@sveltejs/load-config/-/load-config-0.2.0.tgz", + "integrity": "sha512-1LgZ/qUqSoq+QorD83lk2hka79Px0wXNW2q5V1nZlxGhQgw1jrsIbVz5YiCeucVLo4XvFLjXukUaQjIiqowkcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.2.0.tgz", + "integrity": "sha512-1SpkuMSRLfugrVX+IrKfE1RUegzo8AQzKQ6qQPfVzbcWi5IhuTPaKb5ZrLpucleFznkc4/RTeSPoRnGWFxX+EQ==", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.3.1", + "magic-string": "^0.30.21", + "obug": "^2.1.0", + "vitefu": "^1.1.2" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.46.4", + "vite": "^8.0.0-beta.7 || ^8.0.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz", + "integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT", + "optional": true + }, + "node_modules/@typeschema/class-validator": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@typeschema/class-validator/-/class-validator-0.3.0.tgz", + "integrity": "sha512-OJSFeZDIQ8EK1HTljKLT5CItM2wsbgczLN8tMEfz3I1Lmhc5TBfkZ0eikFzUC16tI3d1Nag7um6TfCgp2I2Bww==", + "license": "MIT", + "optional": true, + "dependencies": { + "@typeschema/core": "0.14.0" + }, + "peerDependencies": { + "class-validator": "^0.14.1" + }, + "peerDependenciesMeta": { + "class-validator": { + "optional": true + } + } + }, + "node_modules/@typeschema/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@typeschema/core/-/core-0.14.0.tgz", + "integrity": "sha512-Ia6PtZHcL3KqsAWXjMi5xIyZ7XMH4aSnOQes8mfMLx+wGFGtGRNlwe6Y7cYvX+WfNK67OL0/HSe9t8QDygV0/w==", + "license": "MIT", + "optional": true, + "peerDependencies": { + "@types/json-schema": "^7.0.15" + }, + "peerDependenciesMeta": { + "@types/json-schema": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.65.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@valibot/to-json-schema": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@valibot/to-json-schema/-/to-json-schema-1.7.1.tgz", + "integrity": "sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A==", + "license": "MIT", + "optional": true, + "peerDependencies": { + "valibot": "^1.4.0" + } + }, + "node_modules/@vinejs/compiler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@vinejs/compiler/-/compiler-3.0.0.tgz", + "integrity": "sha512-v9Lsv59nR56+bmy2p0+czjZxsLHwaibJ+SV5iK9JJfehlJMa501jUJQqqz4X/OqKXrxtE3uTQmSqjUqzF3B2mw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@vinejs/vine": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@vinejs/vine/-/vine-3.0.1.tgz", + "integrity": "sha512-ZtvYkYpZOYdvbws3uaOAvTFuvFXoQGAtmzeiXu+XSMGxi5GVsODpoI9Xu9TplEMuD/5fmAtBbKb9cQHkWkLXDQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@poppinss/macroable": "^1.0.4", + "@types/validator": "^13.12.2", + "@vinejs/compiler": "^3.0.0", + "camelcase": "^8.0.0", + "dayjs": "^1.11.13", + "dlv": "^1.1.3", + "normalize-url": "^8.0.1", + "validator": "^13.12.0" + }, + "engines": { + "node": ">=18.16.0" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arkregex": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/arkregex/-/arkregex-0.0.8.tgz", + "integrity": "sha512-PJcx6G1kQTgLKPUbeYlYecDRaKq15AMSGVajlKFYWlPeJRQL+j3dKE6tyMs40HZ99djS1l9Vhl3ezAHy9JBIqQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@ark/util": "0.56.2" + } + }, + "node_modules/arktype": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.2.3.tgz", + "integrity": "sha512-7W+0RLTUNJiBFIIZXwOQxSR8Z273IAd6IvqBeG9+gHnQKFsIx2C0iOtGTmMrPnlX4qLXyc5+ll7A0BIj9WrbTg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@ark/schema": "0.56.2", + "@ark/util": "0.56.2", + "arkregex": "0.0.8" + } + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bits-ui": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.18.1.tgz", + "integrity": "sha512-KkemzKFH4T3gt3H+P86JcnAWExjByv/6vlwjm/BoCwTPHu03yiCdxbghdJLvFReQTe0acCAiRcKfmixxD6XvlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.1", + "@floating-ui/dom": "^1.7.1", + "esm-env": "^1.1.2", + "runed": "^0.35.1", + "svelte-toolbelt": "^0.10.6", + "tabbable": "^6.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "@internationalized/date": "^3.8.1", + "svelte": "^5.33.0" + } + }, + "node_modules/bits-ui/node_modules/runed": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.35.1.tgz", + "integrity": "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "esm-env": "^1.0.0", + "lz-string": "^1.5.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.21.0", + "svelte": "^5.7.0" + }, + "peerDependenciesMeta": { + "@sveltejs/kit": { + "optional": true + } + } + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/class-validator": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.4.tgz", + "integrity": "sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.22" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/comment-json": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.6.2.tgz", + "integrity": "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/consola": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz", + "integrity": "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/date-fns": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", + "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT", + "optional": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.2.tgz", + "integrity": "sha512-DObPPAfdtFbXjxLqK8s2Xk9ZuWz5+ZoFEhC7J76es4GU/rEiXwHTmbImoCdyoCOcBH1UF3+Cz6Z2sYD4hyl5TA==", + "license": "MIT" + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT", + "optional": true + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/effect": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.22.0.tgz", + "integrity": "sha512-jhYFe0zTlIRqYFrKTS+6luhmS/Tm0f+JLo0K9KUxvtFab1SUGEszQi2ehOP6QzAZvy831lDmTwwzvVDZSPNz3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz", + "integrity": "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz", + "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.22.0.tgz", + "integrity": "sha512-O3qn0NePTWta+1o25dIThqeEP/hEQ3VxDK2LVO8SQ5wG9umLMvulK+m1yQ4JGOb2Pkl8IB0G1lpRV/HXDXSLTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.6.1", + "@jridgewell/sourcemap-codec": "^1.5.0", + "esutils": "^2.0.3", + "globals": "^16.0.0", + "known-css-properties": "^0.37.0", + "postcss": "^8.4.49", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^7.0.0", + "semver": "^7.6.3", + "svelte-eslint-parser": "^1.7.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^8.57.1 || ^9.0.0 || ^10.0.0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-svelte/node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.3.0.tgz", + "integrity": "sha512-GQ/7RN8uOtEfNpzZzBMTzW9JBcX42oaSVtPzdF+6cEL8pqIL094iUpr9jzYGn4O4P/1S60dJ6izyT8F4LYARng==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-6.0.1.tgz", + "integrity": "sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formsnap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/formsnap/-/formsnap-2.0.1.tgz", + "integrity": "sha512-iJSe4YKd/W6WhLwKDVJU9FQeaJRpEFuolhju7ZXlRpUVyDdqFdMP8AUBICgnVvQPyP41IPAlBa/v0Eo35iE6wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "svelte-toolbelt": "^0.5.0" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "sveltekit-superforms": "^2.19.0" + } + }, + "node_modules/formsnap/node_modules/svelte-toolbelt": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.5.0.tgz", + "integrity": "sha512-t3tenZcnfQoIeRuQf/jBU7bvTeT3TGkcEE+1EUr5orp0lR7NEpprflpuie3x9Dn0W9nOKqs3HwKGJeeN5Ok1sQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0-next.126" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-id": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.2.0.tgz", + "integrity": "sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==", + "dev": true, + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/joi": { + "version": "17.13.4", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.4.tgz", + "integrity": "sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-sha256": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.1.tgz", + "integrity": "sha512-o6WSo/LUvY2uC4j7mO50a2ms7E/EAdbP0swigLV+nzHKTTaYnaLIWJ02VdXrsJX0vGedDESQnLsOekr94ryfjg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/kysely": { + "version": "0.28.17", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.17.tgz", + "integrity": "sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.13.9", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.9.tgz", + "integrity": "sha512-VNS5vWMM7r0P66BYv+TQJATxExEgLxN+34hfHDVhDkUsGAE4cRg0shCNSLTXNKm7nIUscC7AfB51TjxEeF7msQ==", + "license": "MIT", + "optional": true + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-weak": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/memoize-weak/-/memoize-weak-1.0.2.tgz", + "integrity": "sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ==", + "license": "ISC" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mode-watcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-1.1.0.tgz", + "integrity": "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "runed": "^0.25.0", + "svelte-toolbelt": "^0.7.1" + }, + "peerDependencies": { + "svelte": "^5.27.0" + } + }, + "node_modules/mode-watcher/node_modules/runed": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.25.0.tgz", + "integrity": "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/mode-watcher/node_modules/svelte-toolbelt": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.1.tgz", + "integrity": "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.23.2", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/mode-watcher/node_modules/svelte-toolbelt/node_modules/runed": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.23.4.tgz", + "integrity": "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss": { + "version": "8.5.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.20.tgz", + "integrity": "sha512-lW616l85ucIQL+FocMmL7pQFPqBmwejrCMg+iPxyImlrANNJG9NHq/RkyCZopDhd8C3LA03PHRJDjkbGu8vvug==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.2.tgz", + "integrity": "sha512-ItFouLvzSFE3ulNl4DKoWM3BGcbDCNVpIyy/Y3F2gC3aNiGLxtFUdffVqO5Z5hhYG+DFT5KULWaxmeFFpdbvaQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.8.1.tgz", + "integrity": "sha512-iaFMYqDsE4ffdDkn5qup0j5f2aCEBFZrdrZnvu9QKTlWx/iGPeQ4HHu7b7fCPMxeo9nwQBiOAh2nSypdFYWJkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT", + "optional": true + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/runed": { + "version": "0.37.1", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.37.1.tgz", + "integrity": "sha512-MeFY73xBW8IueWBm012nNFIGy19WUGPLtknavyUPMpnyt350M47PhGSGrGoSLbidwn+Zlt/O0cp8/OZE3LASWA==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "esm-env": "^1.0.0", + "lz-string": "^1.5.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.21.0", + "svelte": "^5.7.0", + "zod": "^4.1.0" + }, + "peerDependenciesMeta": { + "@sveltejs/kit": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/set-cookie-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz", + "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==", + "license": "MIT" + }, + "node_modules/shadcn-svelte": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/shadcn-svelte/-/shadcn-svelte-1.4.2.tgz", + "integrity": "sha512-j7oDhXRmFuZ8bAhvF7Y65moMi9qM0iA68j5wEDNBiH0be9NWOEg1qo7myDeIf+h2oUSLBYHpVmeJIKYBmNT4Kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.0", + "node-fetch-native": "^1.6.4", + "postcss": "^8.5.10", + "tailwind-merge": "^3.0.0" + }, + "bin": { + "shadcn-svelte": "dist/index.mjs" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/shadcn-svelte/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sqlite-wasm-kysely": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/sqlite-wasm-kysely/-/sqlite-wasm-kysely-0.3.0.tgz", + "integrity": "sha512-TzjBNv7KwRw6E3pdKdlRyZiTmUIE0UttT/Sl56MVwVARl/u5gp978KepazCJZewFUnlWHz9i3NQd4kOtP/Afdg==", + "dev": true, + "dependencies": { + "@sqlite.org/sqlite-wasm": "^3.48.0-build2" + }, + "peerDependencies": { + "kysely": "*" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/superstruct": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svelte": { + "version": "5.56.7", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.7.tgz", + "integrity": "sha512-5qERUZX80oQj6XrDMUmD2Uhd/cIpCPDWWKBK3ZHmyRUC9apPyamWM8xMo31mbWsIQxwG2hVoSnOJ/EcnhVkkzQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.10", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.8.1", + "esm-env": "^1.2.1", + "esrap": "^2.2.12", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.3.tgz", + "integrity": "sha512-DHdTCGX62R0fCxBEaT+USdASAnoaRBaaNczkRJl0K7o3WyoCeVUbVxo6fKqpOll/B+WMWCsiFK0eFrJSNBKZIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "@sveltejs/load-config": "^0.2.0", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.8.0.tgz", + "integrity": "sha512-mikR1qwIVy3t5WthUoAXkMwxkXvabZP9FJgdx35Ei7EbGWmctva1Pih16Koeor/bdNNq8NXHlwKGS6NkYTawLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.0", + "postcss": "^8.4.49", + "postcss-scss": "^4.0.9", + "postcss-selector-parser": "^7.0.0", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0", + "pnpm": "10.34.1" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-eslint-parser/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte-eslint-parser/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte-sonner": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.1.1.tgz", + "integrity": "sha512-5cd3p7wa4cq0NsqslMwdlPb7x1JglEZ/GKrLePWNr5bCxR1nagAVrY01FRFrXfUGs41miLt3C327+8XJo5BzZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "runed": "^0.28.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-sonner/node_modules/runed": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.28.0.tgz", + "integrity": "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/svelte-toolbelt": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.10.6.tgz", + "integrity": "sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.35.1", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.30.2" + } + }, + "node_modules/svelte-toolbelt/node_modules/runed": { + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.35.1.tgz", + "integrity": "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "esm-env": "^1.0.0", + "lz-string": "^1.5.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.21.0", + "svelte": "^5.7.0" + }, + "peerDependenciesMeta": { + "@sveltejs/kit": { + "optional": true + } + } + }, + "node_modules/sveltekit-superforms": { + "version": "2.30.2", + "resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.30.2.tgz", + "integrity": "sha512-6sR70ZfjFMAfdNure/Bu26o1rY32+WGxebko1L8jUFS+qZw5fxIHBPkUaTpNBkOIlJZ/UJwoNCYCm5xK2Z7Kqw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ciscoheat" + }, + { + "type": "ko-fi", + "url": "https://ko-fi.com/ciscoheat" + }, + { + "type": "paypal", + "url": "https://www.paypal.com/donate/?hosted_button_id=NY7F5ALHHSVQS" + } + ], + "license": "MIT", + "dependencies": { + "devalue": "^5.8.1", + "memoize-weak": "^1.0.2", + "ts-deepmerge": "^8.0.0" + }, + "optionalDependencies": { + "@exodus/schemasafe": "^1.3.0", + "@standard-schema/spec": "^1.1.0", + "@typeschema/class-validator": "^0.3.0", + "@valibot/to-json-schema": "^1.7.1", + "@vinejs/vine": "^3.0.1", + "arktype": "^2.2.2", + "class-validator": "^0.14.4", + "effect": "^3.21.4", + "joi": "^17.13.4", + "json-schema-to-ts": "^3.1.1", + "superstruct": "^2.0.2", + "typebox": "^1.3.3", + "valibot": "^1.4.2", + "yup": "^1.7.1", + "zod": "^4.4.3", + "zod-v3-to-json-schema": "^4.0.0" + }, + "peerDependencies": { + "@exodus/schemasafe": "^1.3.0", + "@sveltejs/kit": "1.x || 2.x", + "@typeschema/class-validator": "^0.3.0", + "@vinejs/vine": "^1.8.0 || ^2.0.0 || ^3.0.0", + "arktype": ">=2.0.0-rc.23", + "class-validator": "^0.14.1", + "effect": "^3.21.0", + "joi": "^17.13.1", + "superstruct": "^2.0.2", + "svelte": "3.x || 4.x || >=5.0.0-next.51", + "typebox": "^1.0.36", + "valibot": "^1.2.0", + "yup": "^1.4.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@exodus/schemasafe": { + "optional": true + }, + "@typeschema/class-validator": { + "optional": true + }, + "@vinejs/vine": { + "optional": true + }, + "arktype": { + "optional": true + }, + "class-validator": { + "optional": true + }, + "effect": { + "optional": true + }, + "joi": { + "optional": true + }, + "superstruct": { + "optional": true + }, + "typebox": { + "optional": true + }, + "valibot": { + "optional": true + }, + "yup": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-variants": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.2.2.tgz", + "integrity": "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwind-merge": ">=3.0.0", + "tailwindcss": "*" + }, + "peerDependenciesMeta": { + "tailwind-merge": { + "optional": true + } + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT", + "optional": true + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT", + "optional": true + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT", + "optional": true + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-deepmerge": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-8.0.0.tgz", + "integrity": "sha512-133O+10nJmVI8w5xeVZPEv5PIrv7iaUae07wv1aH8XJH95Ur6YIhWAPhPyP1YPlbPS9fCVcNIZTu7m8urRVF0A==", + "license": "ISC", + "engines": { + "node": ">=14.13.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "devOptional": true, + "license": "0BSD" + }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typebox": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.6.tgz", + "integrity": "sha512-Sc8RA0NCMEFmApHNU9ZMzqcpQj46She44J8ffpLM/bdhLNUZKq7DJumcLcsFx1gRmDfQPgCgOmFFJ7rcnfWNyA==", + "license": "MIT", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/valibot": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.2.tgz", + "integrity": "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==", + "license": "MIT", + "optional": true, + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-compression": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz", + "integrity": "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "debug": "^4.3.3", + "fs-extra": "^10.0.0" + }, + "peerDependencies": { + "vite": ">=2.0.0" + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs-parser/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yup": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.1.tgz", + "integrity": "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "license": "MIT" + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-v3-to-json-schema": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/zod-v3-to-json-schema/-/zod-v3-to-json-schema-4.0.0.tgz", + "integrity": "sha512-KixLrhX/uPmRFnDgsZrzrk4x5SSJA+PmaE5adbfID9+3KPJcdxqRobaHU397EfWBqfQircrjKqvEqZ/mW5QH6w==", + "license": "ISC", + "optional": true, + "peerDependencies": { + "zod": "^3.25 || ^4.0.14" + } + } + } +} diff --git a/tests/resources/export/database.json b/tests/resources/export/database.json index 23fdbba9..1cfde9a1 100644 --- a/tests/resources/export/database.json +++ b/tests/resources/export/database.json @@ -1,6 +1,6 @@ { "provider": "sqlite", - "version": 20260722120000, + "version": 20260723000000, "tableOrder": [ "users", "user_groups", @@ -250,32 +250,6 @@ "user_group_id": "c7ae7c01-28a3-4f3c-9572-1ee734ea8368" } ], - "one_time_access_tokens": [ - { - "created_at": "2025-11-25T12:39:02Z", - "expires_at": "2025-11-25T13:39:02Z", - "id": "bf877753-4ea4-4c9c-bbbd-e198bb201cb8", - "token": "HPe6k6uiDRRVuAQV", - "device_token": null, - "user_id": "f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e" - }, - { - "created_at": "2025-11-25T12:39:02Z", - "expires_at": "2025-11-25T12:39:01Z", - "id": "d3afae24-fe2d-4a98-abec-cf0b8525096a", - "token": "YCGDtftvsvYWiXd0", - "device_token": null, - "user_id": "f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e" - }, - { - "created_at": "2025-11-25T12:39:02Z", - "expires_at": "2025-11-25T13:39:02Z", - "id": "defd5164-9d9b-4228-bbce-708e33f49360", - "token": "one-time-token", - "device_token": null, - "user_id": "f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e" - } - ], "oauth2_jtis": [ { "id": "bd0c8bf2-66ec-487a-9dd5-7d9d78d73543", @@ -322,46 +296,6 @@ "user_id": "f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e" } ], - "signup_tokens": [ - { - "created_at": "2025-11-25T12:39:02Z", - "expires_at": "2025-11-26T12:39:02Z", - "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", - "token": "VALID1234567890A", - "usage_count": 0, - "usage_limit": 1 - }, - { - "created_at": "2025-11-25T12:39:02Z", - "expires_at": "2025-12-02T12:39:02Z", - "id": "dc3c9c96-714e-48eb-926e-2d7c7858e6cf", - "token": "PARTIAL567890ABC", - "usage_count": 2, - "usage_limit": 5 - }, - { - "created_at": "2025-11-25T12:39:02Z", - "expires_at": "2025-11-24T12:39:02Z", - "id": "44de1863-ffa5-4db1-9507-4887cd7a1e3f", - "token": "EXPIRED34567890B", - "usage_count": 1, - "usage_limit": 3 - }, - { - "created_at": "2025-11-25T12:39:02Z", - "expires_at": "2025-11-26T12:39:02Z", - "id": "f1b1678b-7720-4d8b-8f91-1dbff1e2d02b", - "token": "FULLYUSED567890C", - "usage_count": 1, - "usage_limit": 1 - } - ], - "signup_tokens_user_groups": [ - { - "signup_token_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", - "user_group_id": "c7ae7c01-28a3-4f3c-9572-1ee734ea8368" - } - ], "user_authorized_oidc_clients": [ { "client_id": "3654a746-35d4-4321-ac61-0bdcff2b4055", From 9f559788a44cfea790de360da0a74d4bdf8ca5d5 Mon Sep 17 00:00:00 2001 From: Markus Schanz <3457747+schnz@users.noreply.github.com> Date: Sun, 26 Jul 2026 16:27:42 +0200 Subject: [PATCH 12/13] feat: add support for declaritive client secret configuration (#1619) --- .../internal/controller/oidc_controller.go | 14 +++++++++-- backend/internal/dto/oidc_dto.go | 4 +++ backend/internal/service/oidc_service.go | 11 +++++--- backend/internal/service/oidc_service_test.go | 25 +++++++++++++++++++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/backend/internal/controller/oidc_controller.go b/backend/internal/controller/oidc_controller.go index 0033e918..6285975f 100644 --- a/backend/internal/controller/oidc_controller.go +++ b/backend/internal/controller/oidc_controller.go @@ -1,6 +1,8 @@ package controller import ( + "errors" + "io" "net/http" "strconv" "strings" @@ -234,14 +236,22 @@ func (oc *OidcController) updateClientHandler(c *gin.Context) { // createClientSecretHandler godoc // @Summary Create client secret -// @Description Generate a new secret for an OIDC client +// @Description Set or generate a new secret for an OIDC client // @Tags OIDC +// @Accept json // @Produce json // @Param id path string true "Client ID" +// @Param payload body dto.OidcClientSecretDto false "Client secret" // @Success 200 {object} object "{ \"secret\": \"string\" }" // @Router /api/oidc/clients/{id}/secret [post] func (oc *OidcController) createClientSecretHandler(c *gin.Context) { - secret, err := oc.oidcService.CreateClientSecret(c.Request.Context(), c.Param("id")) + var input dto.OidcClientSecretDto + if err := c.ShouldBindJSON(&input); err != nil && !errors.Is(err, io.EOF) { + _ = c.Error(err) + return + } + + secret, err := oc.oidcService.CreateClientSecret(c.Request.Context(), c.Param("id"), 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 d87716b0..507364c9 100644 --- a/backend/internal/dto/oidc_dto.go +++ b/backend/internal/dto/oidc_dto.go @@ -59,6 +59,10 @@ type OidcClientCreateDto struct { ID string `json:"id" binding:"omitempty,client_id,min=2,max=128"` } +type OidcClientSecretDto struct { + Secret string `json:"secret" binding:"omitempty,min=16,printascii"` +} + type OidcClientCredentialsDto struct { FederatedIdentities []OidcClientFederatedIdentityDto `json:"federatedIdentities,omitempty"` } diff --git a/backend/internal/service/oidc_service.go b/backend/internal/service/oidc_service.go index 805e6076..c9cdef80 100644 --- a/backend/internal/service/oidc_service.go +++ b/backend/internal/service/oidc_service.go @@ -266,7 +266,7 @@ func (s *OidcService) DeleteClient(ctx context.Context, clientID string) error { return nil } -func (s *OidcService) CreateClientSecret(ctx context.Context, clientID string) (string, error) { +func (s *OidcService) CreateClientSecret(ctx context.Context, clientID string, input dto.OidcClientSecretDto) (string, error) { tx := s.db.Begin() defer func() { tx.Rollback() @@ -281,9 +281,12 @@ func (s *OidcService) CreateClientSecret(ctx context.Context, clientID string) ( return "", err } - clientSecret, err := utils.GenerateRandomAlphanumericString(32) - if err != nil { - return "", err + clientSecret := input.Secret + if clientSecret == "" { + clientSecret, err = utils.GenerateRandomAlphanumericString(32) + if err != nil { + return "", err + } } hashedSecret, err := bcrypt.GenerateFromPassword([]byte(clientSecret), bcrypt.DefaultCost) diff --git a/backend/internal/service/oidc_service_test.go b/backend/internal/service/oidc_service_test.go index 0f233c8a..5806970e 100644 --- a/backend/internal/service/oidc_service_test.go +++ b/backend/internal/service/oidc_service_test.go @@ -7,6 +7,8 @@ import ( "strings" "testing" + "golang.org/x/crypto/bcrypt" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -499,6 +501,29 @@ func TestOidcService_CreateClient_withoutDescription(t *testing.T) { assert.Empty(t, fetched.Description) } +func TestOidcService_CreateClientSecret_withCustomSecret(t *testing.T) { + db := testutils.NewDatabaseForTest(t) + + s, err := NewOidcService(db, nil, nil, nil, nil, nil) + require.NoError(t, err) + + client := model.OidcClient{Name: "Test Client"} + err = db.Create(&client).Error + require.NoError(t, err) + + customSecret := "custom-client-secret-with-a-minimum-length" + input := dto.OidcClientSecretDto{Secret: customSecret} + + secret, err := s.CreateClientSecret(t.Context(), client.ID, input) + require.NoError(t, err) + assert.Equal(t, customSecret, secret) + + var fetched model.OidcClient + err = db.First(&fetched, "id = ?", client.ID).Error + require.NoError(t, err) + require.NoError(t, bcrypt.CompareHashAndPassword([]byte(fetched.Secret), []byte(customSecret))) +} + func TestOidcService_UpdateClient_description(t *testing.T) { db := testutils.NewDatabaseForTest(t) From 4743d5967a6b7e53d6d8c670c62f0bdea3abd4af Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Sun, 26 Jul 2026 19:08:34 +0200 Subject: [PATCH 13/13] fix: enforce user verification for login assertions --- backend/internal/webauthn/service.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/internal/webauthn/service.go b/backend/internal/webauthn/service.go index 0e35dc8a..07a1e247 100644 --- a/backend/internal/webauthn/service.go +++ b/backend/internal/webauthn/service.go @@ -248,8 +248,10 @@ func (s *Service) VerifyLogin(ctx context.Context, dbConfig *appconfig.AppConfig } session := gowebauthn.SessionData{ - Challenge: storedSession.Challenge, - Expires: storedSession.ExpiresAt.ToTime(), + Challenge: storedSession.Challenge, + Expires: storedSession.ExpiresAt.ToTime(), + UserVerification: protocol.UserVerificationRequirement(storedSession.UserVerification), + CredParams: storedSession.CredentialParams, } var user *model.User @@ -450,8 +452,10 @@ func (s *Service) CreateReauthenticationTokenWithWebauthn(ctx context.Context, s } session := gowebauthn.SessionData{ - Challenge: storedSession.Challenge, - Expires: storedSession.ExpiresAt.ToTime(), + Challenge: storedSession.Challenge, + Expires: storedSession.ExpiresAt.ToTime(), + UserVerification: protocol.UserVerificationRequirement(storedSession.UserVerification), + CredParams: storedSession.CredentialParams, } // Validate the credential assertion