diff --git a/Cargo.lock b/Cargo.lock --- a/Cargo.lock +++ b/Cargo.lock @@ -7405,7 +7405,7 @@ [[package]] name = "tranquil-api" -version = "0.5.0" +version = "0.5.1" dependencies = [ "anyhow", "axum", @@ -7456,7 +7456,7 @@ [[package]] name = "tranquil-auth" -version = "0.5.0" +version = "0.5.1" dependencies = [ "anyhow", "base32", @@ -7479,7 +7479,7 @@ [[package]] name = "tranquil-cache" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "base64 0.22.1", @@ -7493,7 +7493,7 @@ [[package]] name = "tranquil-comms" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "base64 0.22.1", @@ -7511,7 +7511,7 @@ [[package]] name = "tranquil-config" -version = "0.5.0" +version = "0.5.1" dependencies = [ "confique", "serde", @@ -7519,7 +7519,7 @@ [[package]] name = "tranquil-crypto" -version = "0.5.0" +version = "0.5.1" dependencies = [ "aes-gcm", "base64 0.22.1", @@ -7535,7 +7535,7 @@ [[package]] name = "tranquil-db" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "chrono", @@ -7552,7 +7552,7 @@ [[package]] name = "tranquil-db-traits" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "base64 0.22.1", @@ -7568,7 +7568,7 @@ [[package]] name = "tranquil-infra" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "bytes", @@ -7579,7 +7579,7 @@ [[package]] name = "tranquil-lexicon" -version = "0.5.0" +version = "0.5.1" dependencies = [ "chrono", "hickory-resolver", @@ -7597,7 +7597,7 @@ [[package]] name = "tranquil-oauth" -version = "0.5.0" +version = "0.5.1" dependencies = [ "anyhow", "axum", @@ -7620,7 +7620,7 @@ [[package]] name = "tranquil-oauth-server" -version = "0.5.0" +version = "0.5.1" dependencies = [ "axum", "base64 0.22.1", @@ -7653,7 +7653,7 @@ [[package]] name = "tranquil-pds" -version = "0.5.0" +version = "0.5.1" dependencies = [ "aes-gcm", "anyhow", @@ -7738,13 +7738,14 @@ "urlencoding", "uuid", "webauthn-rs", + "webauthn-rs-proto", "wiremock", "zip", ] [[package]] name = "tranquil-repo" -version = "0.5.0" +version = "0.5.1" dependencies = [ "bytes", "cid", @@ -7756,7 +7757,7 @@ [[package]] name = "tranquil-ripple" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "backon", @@ -7781,7 +7782,7 @@ [[package]] name = "tranquil-scopes" -version = "0.5.0" +version = "0.5.1" dependencies = [ "axum", "futures", @@ -7797,7 +7798,7 @@ [[package]] name = "tranquil-server" -version = "0.5.0" +version = "0.5.1" dependencies = [ "axum", "clap", @@ -7818,7 +7819,7 @@ [[package]] name = "tranquil-signal" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "chrono", @@ -7841,7 +7842,7 @@ [[package]] name = "tranquil-storage" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "aws-config", @@ -7858,7 +7859,7 @@ [[package]] name = "tranquil-store" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "bytes", @@ -7904,7 +7905,7 @@ [[package]] name = "tranquil-sync" -version = "0.5.0" +version = "0.5.1" dependencies = [ "anyhow", "axum", @@ -7926,7 +7927,7 @@ [[package]] name = "tranquil-types" -version = "0.5.0" +version = "0.5.1" dependencies = [ "chrono", "cid", diff --git a/Cargo.toml b/Cargo.toml --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ ] [workspace.package] -version = "0.5.0" +version = "0.5.1" edition = "2024" license = "AGPL-3.0-or-later" @@ -126,7 +126,7 @@ tracing-subscriber = "0.3" urlencoding = "2.1" uuid = { version = "1.19", features = ["v4", "v5", "v7", "fast-rng", "serde"] } -webauthn-rs = { version = "0.5", features = ["danger-allow-state-serialisation", "danger-user-presence-only-security-keys"] } +webauthn-rs = { version = "0.5", features = ["danger-allow-state-serialisation", "danger-user-presence-only-security-keys", "conditional-ui"] } webauthn-rs-proto = "0.5" zip = { version = "7.0", default-features = false, features = ["deflate"] } diff --git a/.sqlx/query-053c971024b0d29a441c3597d760b3e21db2383442c3e6f09de4eb49ea437e7c.json b/.sqlx/query-053c971024b0d29a441c3597d760b3e21db2383442c3e6f09de4eb49ea437e7c.json new file mode 100644 --- /dev/null +++ b/.sqlx/query-053c971024b0d29a441c3597d760b3e21db2383442c3e6f09de4eb49ea437e7c.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT did, password_hash FROM users WHERE handle = $1 OR did = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "did", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "password_hash", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + true + ] + }, + "hash": "053c971024b0d29a441c3597d760b3e21db2383442c3e6f09de4eb49ea437e7c" +} diff --git a/.sqlx/query-060c285c93a05252aab7d474df0186e7b5083fafedc582b8eac9916983e8fc2d.json b/.sqlx/query-060c285c93a05252aab7d474df0186e7b5083fafedc582b8eac9916983e8fc2d.json new file mode 100644 --- /dev/null +++ b/.sqlx/query-060c285c93a05252aab7d474df0186e7b5083fafedc582b8eac9916983e8fc2d.json @@ -0,0 +1,122 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id, did, email, password_hash, password_required, two_factor_enabled,\n preferred_comms_channel as \"preferred_comms_channel!: CommsChannel\",\n deactivated_at, takedown_ref,\n email_verified, discord_verified, telegram_verified, signal_verified,\n account_type as \"account_type!: AccountType\"\n FROM users\n WHERE handle = $1 OR did = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "did", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "password_required", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "two_factor_enabled", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "preferred_comms_channel!: CommsChannel", + "type_info": { + "Custom": { + "name": "comms_channel", + "kind": { + "Enum": [ + "email", + "discord", + "telegram", + "signal" + ] + } + } + } + }, + { + "ordinal": 7, + "name": "deactivated_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "takedown_ref", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "email_verified", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "discord_verified", + "type_info": "Bool" + }, + { + "ordinal": 11, + "name": "telegram_verified", + "type_info": "Bool" + }, + { + "ordinal": 12, + "name": "signal_verified", + "type_info": "Bool" + }, + { + "ordinal": 13, + "name": "account_type!: AccountType", + "type_info": { + "Custom": { + "name": "account_type", + "kind": { + "Enum": [ + "personal", + "delegated" + ] + } + } + } + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + true, + true, + false, + false, + false, + true, + true, + false, + false, + false, + false, + false + ] + }, + "hash": "060c285c93a05252aab7d474df0186e7b5083fafedc582b8eac9916983e8fc2d" +} diff --git a/.sqlx/query-3155ef4f35698a3fe6aa38d5d976fd51b7f6a0381c81c4907dad61d2f37992bd.json b/.sqlx/query-3155ef4f35698a3fe6aa38d5d976fd51b7f6a0381c81c4907dad61d2f37992bd.json new file mode 100644 --- /dev/null +++ b/.sqlx/query-3155ef4f35698a3fe6aa38d5d976fd51b7f6a0381c81c4907dad61d2f37992bd.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM webauthn_challenges WHERE did = $1 AND challenge_type = 'discoverable'", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "3155ef4f35698a3fe6aa38d5d976fd51b7f6a0381c81c4907dad61d2f37992bd" +} diff --git a/.sqlx/query-6969c478a0922bac4b79902313a0e28c94d6b8d6b16035474dd8f484e6171d60.json b/.sqlx/query-6969c478a0922bac4b79902313a0e28c94d6b8d6b16035474dd8f484e6171d60.json new file mode 100644 --- /dev/null +++ b/.sqlx/query-6969c478a0922bac4b79902313a0e28c94d6b8d6b16035474dd8f484e6171d60.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT state_json FROM webauthn_challenges\n WHERE did = $1 AND challenge_type = 'discoverable' AND expires_at > NOW()\n ORDER BY created_at DESC LIMIT 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "state_json", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "6969c478a0922bac4b79902313a0e28c94d6b8d6b16035474dd8f484e6171d60" +} diff --git a/.sqlx/query-7061e8763ef7d91ff152ed0124f99e1820172fd06916d225ca6c5137a507b8fa.json b/.sqlx/query-7061e8763ef7d91ff152ed0124f99e1820172fd06916d225ca6c5137a507b8fa.json deleted file mode 100644 --- a/.sqlx/query-7061e8763ef7d91ff152ed0124f99e1820172fd06916d225ca6c5137a507b8fa.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT id, did, email, password_hash, password_required, two_factor_enabled,\n preferred_comms_channel as \"preferred_comms_channel!: CommsChannel\",\n deactivated_at, takedown_ref,\n email_verified, discord_verified, telegram_verified, signal_verified,\n account_type as \"account_type!: AccountType\"\n FROM users\n WHERE handle = $1 OR email = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "did", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "password_required", - "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "two_factor_enabled", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "preferred_comms_channel!: CommsChannel", - "type_info": { - "Custom": { - "name": "comms_channel", - "kind": { - "Enum": [ - "email", - "discord", - "telegram", - "signal" - ] - } - } - } - }, - { - "ordinal": 7, - "name": "deactivated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 8, - "name": "takedown_ref", - "type_info": "Text" - }, - { - "ordinal": 9, - "name": "email_verified", - "type_info": "Bool" - }, - { - "ordinal": 10, - "name": "discord_verified", - "type_info": "Bool" - }, - { - "ordinal": 11, - "name": "telegram_verified", - "type_info": "Bool" - }, - { - "ordinal": 12, - "name": "signal_verified", - "type_info": "Bool" - }, - { - "ordinal": 13, - "name": "account_type!: AccountType", - "type_info": { - "Custom": { - "name": "account_type", - "kind": { - "Enum": [ - "personal", - "delegated" - ] - } - } - } - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - true, - true, - false, - false, - false, - true, - true, - false, - false, - false, - false, - false - ] - }, - "hash": "7061e8763ef7d91ff152ed0124f99e1820172fd06916d225ca6c5137a507b8fa" -} diff --git a/.sqlx/query-a960b981a146a0e422ef53601dfc31e29cf777aa194227c48c6ebc6905ea3249.json b/.sqlx/query-a960b981a146a0e422ef53601dfc31e29cf777aa194227c48c6ebc6905ea3249.json deleted file mode 100644 --- a/.sqlx/query-a960b981a146a0e422ef53601dfc31e29cf777aa194227c48c6ebc6905ea3249.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT\n u.id, u.did, u.handle, u.password_hash, u.email, u.deactivated_at, u.takedown_ref,\n u.email_verified, u.discord_verified, u.telegram_verified, u.signal_verified,\n u.allow_legacy_login, u.migrated_to_pds,\n u.preferred_comms_channel as \"preferred_comms_channel: CommsChannel\",\n k.key_bytes, k.encryption_version,\n (SELECT verified FROM user_totp WHERE did = u.did) as totp_enabled,\n COALESCE((SELECT (value_json)::boolean FROM account_preferences WHERE user_id = u.id AND name = 'email_auth_factor' ORDER BY created_at DESC LIMIT 1), false) as \"email_2fa_enabled!\"\n FROM users u\n JOIN user_keys k ON u.id = k.user_id\n WHERE u.handle = $1 OR u.email = $1 OR u.did = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "did", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "handle", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "deactivated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 6, - "name": "takedown_ref", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "email_verified", - "type_info": "Bool" - }, - { - "ordinal": 8, - "name": "discord_verified", - "type_info": "Bool" - }, - { - "ordinal": 9, - "name": "telegram_verified", - "type_info": "Bool" - }, - { - "ordinal": 10, - "name": "signal_verified", - "type_info": "Bool" - }, - { - "ordinal": 11, - "name": "allow_legacy_login", - "type_info": "Bool" - }, - { - "ordinal": 12, - "name": "migrated_to_pds", - "type_info": "Text" - }, - { - "ordinal": 13, - "name": "preferred_comms_channel: CommsChannel", - "type_info": { - "Custom": { - "name": "comms_channel", - "kind": { - "Enum": [ - "email", - "discord", - "telegram", - "signal" - ] - } - } - } - }, - { - "ordinal": 14, - "name": "key_bytes", - "type_info": "Bytea" - }, - { - "ordinal": 15, - "name": "encryption_version", - "type_info": "Int4" - }, - { - "ordinal": 16, - "name": "totp_enabled", - "type_info": "Bool" - }, - { - "ordinal": 17, - "name": "email_2fa_enabled!", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - true, - true, - true, - true, - false, - false, - false, - false, - false, - true, - false, - false, - true, - null, - null - ] - }, - "hash": "a960b981a146a0e422ef53601dfc31e29cf777aa194227c48c6ebc6905ea3249" -} diff --git a/.sqlx/query-aafc2a7e51200ca1e7071c63c13698bf34ef8b66758ca9ebab4ea706ffb62914.json b/.sqlx/query-aafc2a7e51200ca1e7071c63c13698bf34ef8b66758ca9ebab4ea706ffb62914.json new file mode 100644 --- /dev/null +++ b/.sqlx/query-aafc2a7e51200ca1e7071c63c13698bf34ef8b66758ca9ebab4ea706ffb62914.json @@ -0,0 +1,136 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n u.id, u.did, u.handle, u.password_hash, u.email, u.deactivated_at, u.takedown_ref,\n u.email_verified, u.discord_verified, u.telegram_verified, u.signal_verified,\n u.allow_legacy_login, u.migrated_to_pds,\n u.preferred_comms_channel as \"preferred_comms_channel: CommsChannel\",\n k.key_bytes, k.encryption_version,\n (SELECT verified FROM user_totp WHERE did = u.did) as totp_enabled,\n COALESCE((SELECT (value_json)::boolean FROM account_preferences WHERE user_id = u.id AND name = 'email_auth_factor' ORDER BY created_at DESC LIMIT 1), false) as \"email_2fa_enabled!\"\n FROM users u\n JOIN user_keys k ON u.id = k.user_id\n WHERE u.handle = $1 OR u.did = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "did", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "handle", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "deactivated_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 6, + "name": "takedown_ref", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "email_verified", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "discord_verified", + "type_info": "Bool" + }, + { + "ordinal": 9, + "name": "telegram_verified", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "signal_verified", + "type_info": "Bool" + }, + { + "ordinal": 11, + "name": "allow_legacy_login", + "type_info": "Bool" + }, + { + "ordinal": 12, + "name": "migrated_to_pds", + "type_info": "Text" + }, + { + "ordinal": 13, + "name": "preferred_comms_channel: CommsChannel", + "type_info": { + "Custom": { + "name": "comms_channel", + "kind": { + "Enum": [ + "email", + "discord", + "telegram", + "signal" + ] + } + } + } + }, + { + "ordinal": 14, + "name": "key_bytes", + "type_info": "Bytea" + }, + { + "ordinal": 15, + "name": "encryption_version", + "type_info": "Int4" + }, + { + "ordinal": 16, + "name": "totp_enabled", + "type_info": "Bool" + }, + { + "ordinal": 17, + "name": "email_2fa_enabled!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + true, + true, + true, + true, + false, + false, + false, + false, + false, + true, + false, + false, + true, + null, + null + ] + }, + "hash": "aafc2a7e51200ca1e7071c63c13698bf34ef8b66758ca9ebab4ea706ffb62914" +} diff --git a/.sqlx/query-c4621f6a8a1ab78a6355b09fdfc2bf8999d276564e93015792ec07cb05e79038.json b/.sqlx/query-c4621f6a8a1ab78a6355b09fdfc2bf8999d276564e93015792ec07cb05e79038.json deleted file mode 100644 --- a/.sqlx/query-c4621f6a8a1ab78a6355b09fdfc2bf8999d276564e93015792ec07cb05e79038.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT did, password_hash FROM users WHERE handle = $1 OR email = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "did", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "password_hash", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - true - ] - }, - "hash": "c4621f6a8a1ab78a6355b09fdfc2bf8999d276564e93015792ec07cb05e79038" -} diff --git a/.sqlx/query-c6e3388fc39983f1787917606ba3194c72322d2d1ec54402c262194791a2b06a.json b/.sqlx/query-c6e3388fc39983f1787917606ba3194c72322d2d1ec54402c262194791a2b06a.json new file mode 100644 --- /dev/null +++ b/.sqlx/query-c6e3388fc39983f1787917606ba3194c72322d2d1ec54402c262194791a2b06a.json @@ -0,0 +1,18 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO webauthn_challenges (id, did, challenge, challenge_type, state_json, expires_at)\n VALUES ($1, $2, $3, 'discoverable', $4, $5)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid", + "Text", + "Bytea", + "Text", + "Timestamptz" + ] + }, + "nullable": [] + }, + "hash": "c6e3388fc39983f1787917606ba3194c72322d2d1ec54402c262194791a2b06a" +} diff --git a/crates/tranquil-pds/Cargo.toml b/crates/tranquil-pds/Cargo.toml --- a/crates/tranquil-pds/Cargo.toml +++ b/crates/tranquil-pds/Cargo.toml @@ -78,6 +78,7 @@ urlencoding = { workspace = true } uuid = { workspace = true } webauthn-rs = { workspace = true } +webauthn-rs-proto = { workspace = true } zip = { workspace = true } aws-config = { workspace = true, optional = true } aws-sdk-s3 = { workspace = true, optional = true } diff --git a/crates/tranquil-db-traits/src/user.rs b/crates/tranquil-db-traits/src/user.rs --- a/crates/tranquil-db-traits/src/user.rs +++ b/crates/tranquil-db-traits/src/user.rs @@ -144,12 +144,12 @@ async fn get_by_email(&self, email: &str) -> Result, DbError>; - async fn get_login_check_by_handle_or_email( + async fn get_login_check_by_identifier( &self, identifier: &str, ) -> Result, DbError>; - async fn get_login_info_by_handle_or_email( + async fn get_login_info_by_identifier( &self, identifier: &str, ) -> Result, DbError>; @@ -357,6 +357,19 @@ did: &Did, challenge_type: WebauthnChallengeType, ) -> Result<(), DbError>; + + async fn save_discoverable_challenge( + &self, + request_key: &str, + state_json: &str, + ) -> Result; + + async fn load_discoverable_challenge( + &self, + request_key: &str, + ) -> Result, DbError>; + + async fn delete_discoverable_challenge(&self, request_key: &str) -> Result<(), DbError>; async fn get_totp_record(&self, did: &Did) -> Result, DbError>; diff --git a/crates/tranquil-lexicon/src/validate.rs b/crates/tranquil-lexicon/src/validate.rs --- a/crates/tranquil-lexicon/src/validate.rs +++ b/crates/tranquil-lexicon/src/validate.rs @@ -322,7 +322,9 @@ if let Some(ref accept) = lex_blob.accept { let mime_type = obj.get("mimeType").and_then(|v| v.as_str()).unwrap_or(""); - let matched = accept.iter().any(|pattern| mime_type_matches_accept_pattern(mime_type, pattern)); + let matched = accept + .iter() + .any(|pattern| mime_type_matches_accept_pattern(mime_type, pattern)); if !mime_type.is_empty() && !matched { return Err(LexValidationError::field( path, diff --git a/frontend/src/components/RandomHandle.svelte b/frontend/src/components/RandomHandle.svelte --- a/frontend/src/components/RandomHandle.svelte +++ b/frontend/src/components/RandomHandle.svelte @@ -1,7 +1,7 @@