diff --git a/.sqlx/query-336532632eb24ed923842cdc8976e26a7f9b8d28ef1d7011407f388d3e983e62.json b/.sqlx/query-054e9d1f22c77a3b793616e92ab97e16fc9d6814e41f8d30e3e2a8431181c347.json similarity index 80% rename from .sqlx/query-336532632eb24ed923842cdc8976e26a7f9b8d28ef1d7011407f388d3e983e62.json rename to .sqlx/query-054e9d1f22c77a3b793616e92ab97e16fc9d6814e41f8d30e3e2a8431181c347.json index ca8985f9c..4002b0276 100644 --- a/.sqlx/query-336532632eb24ed923842cdc8976e26a7f9b8d28ef1d7011407f388d3e983e62.json +++ b/.sqlx/query-054e9d1f22c77a3b793616e92ab97e16fc9d6814e41f8d30e3e2a8431181c347.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\",\"use_for_mfa\" FROM \"wallet\"", + "query": "SELECT id, \"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\",\"use_for_mfa\" FROM \"wallet\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -70,5 +70,5 @@ false ] }, - "hash": "336532632eb24ed923842cdc8976e26a7f9b8d28ef1d7011407f388d3e983e62" + "hash": "054e9d1f22c77a3b793616e92ab97e16fc9d6814e41f8d30e3e2a8431181c347" } diff --git a/.sqlx/query-5267ddfcbe18a9db34a0bc9f51baa5ef5a214dbb487981d9864f6469a04805c2.json b/.sqlx/query-05c83ab93f896e0555eabddab41b2440e7d6804e1f391155a4388060851d2ccf.json similarity index 73% rename from .sqlx/query-5267ddfcbe18a9db34a0bc9f51baa5ef5a214dbb487981d9864f6469a04805c2.json rename to .sqlx/query-05c83ab93f896e0555eabddab41b2440e7d6804e1f391155a4388060851d2ccf.json index ebc7341f4..a468ee3ab 100644 --- a/.sqlx/query-5267ddfcbe18a9db34a0bc9f51baa5ef5a214dbb487981d9864f6469a04805c2.json +++ b/.sqlx/query-05c83ab93f896e0555eabddab41b2440e7d6804e1f391155a4388060851d2ccf.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"token\",\"device_id\",\"created_at\" FROM \"pollingtoken\" WHERE id = $1", + "query": "SELECT id, \"token\",\"device_id\",\"created_at\" FROM \"pollingtoken\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -36,5 +36,5 @@ false ] }, - "hash": "5267ddfcbe18a9db34a0bc9f51baa5ef5a214dbb487981d9864f6469a04805c2" + "hash": "05c83ab93f896e0555eabddab41b2440e7d6804e1f391155a4388060851d2ccf" } diff --git a/.sqlx/query-6be6b6c94a21c0352c9653f2f5606187cfee43d39cf7e5ea9c2f53a9a4666f77.json b/.sqlx/query-0b6daf46f6493f08f28a50a0f79de7ed60d46d90c9f226418a0225fa34ccfdcf.json similarity index 79% rename from .sqlx/query-6be6b6c94a21c0352c9653f2f5606187cfee43d39cf7e5ea9c2f53a9a4666f77.json rename to .sqlx/query-0b6daf46f6493f08f28a50a0f79de7ed60d46d90c9f226418a0225fa34ccfdcf.json index 0cd0953fc..6d9dd22da 100644 --- a/.sqlx/query-6be6b6c94a21c0352c9653f2f5606187cfee43d39cf7e5ea9c2f53a9a4666f77.json +++ b/.sqlx/query-0b6daf46f6493f08f28a50a0f79de7ed60d46d90c9f226418a0225fa34ccfdcf.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"yubikey_id\",\"name\",\"user_id\",\"key\",\"key_type\" \"key_type: _\" FROM \"authentication_key\" WHERE id = $1", + "query": "SELECT id, \"yubikey_id\",\"name\",\"user_id\",\"key\",\"key_type\" \"key_type: _\" FROM \"authentication_key\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -58,5 +58,5 @@ false ] }, - "hash": "6be6b6c94a21c0352c9653f2f5606187cfee43d39cf7e5ea9c2f53a9a4666f77" + "hash": "0b6daf46f6493f08f28a50a0f79de7ed60d46d90c9f226418a0225fa34ccfdcf" } diff --git a/.sqlx/query-06be7b39c87c3a1d1465dfb007937b5b584f60e9094a18936b482c4dfb6b1aca.json b/.sqlx/query-0d04ddf1bf7cf709235a62b56ea6415eaf4f0e5c36dc95ee6b55e451f4715997.json similarity index 67% rename from .sqlx/query-06be7b39c87c3a1d1465dfb007937b5b584f60e9094a18936b482c4dfb6b1aca.json rename to .sqlx/query-0d04ddf1bf7cf709235a62b56ea6415eaf4f0e5c36dc95ee6b55e451f4715997.json index d70e49da1..8662cc635 100644 --- a/.sqlx/query-06be7b39c87c3a1d1465dfb007937b5b584f60e9094a18936b482c4dfb6b1aca.json +++ b/.sqlx/query-0d04ddf1bf7cf709235a62b56ea6415eaf4f0e5c36dc95ee6b55e451f4715997.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\" FROM \"group\"", + "query": "SELECT id, \"name\" FROM \"group\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -22,5 +22,5 @@ false ] }, - "hash": "06be7b39c87c3a1d1465dfb007937b5b584f60e9094a18936b482c4dfb6b1aca" + "hash": "0d04ddf1bf7cf709235a62b56ea6415eaf4f0e5c36dc95ee6b55e451f4715997" } diff --git a/.sqlx/query-1a45960695ad8a4f60cd94b610f9cf0c9b09ea742ea07a8de4ef8e6b199f2269.json b/.sqlx/query-0e9a8c2395d43898748c176db0d89fa20784cd93ca1dfd00b2755934937d8270.json similarity index 62% rename from .sqlx/query-1a45960695ad8a4f60cd94b610f9cf0c9b09ea742ea07a8de4ef8e6b199f2269.json rename to .sqlx/query-0e9a8c2395d43898748c176db0d89fa20784cd93ca1dfd00b2755934937d8270.json index 6b707bc7b..25c3f61e0 100644 --- a/.sqlx/query-1a45960695ad8a4f60cd94b610f9cf0c9b09ea742ea07a8de4ef8e6b199f2269.json +++ b/.sqlx/query-0e9a8c2395d43898748c176db0d89fa20784cd93ca1dfd00b2755934937d8270.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "WITH stats AS ( SELECT DISTINCT ON (device_id) device_id, endpoint, latest_handshake FROM wireguard_peer_stats WHERE network = $1 ORDER BY device_id, collected_at DESC ) SELECT d.id as \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN wireguard_network_device wnd ON wnd.device_id = d.id LEFT JOIN stats on d.id = stats.device_id WHERE wnd.wireguard_network_id = $1 AND wnd.is_authorized = true AND (wnd.authorized_at IS NULL OR (NOW() - wnd.authorized_at) > $2 * interval '1 second') AND (stats.latest_handshake IS NULL OR (NOW() - stats.latest_handshake) > $2 * interval '1 second')", + "query": "WITH stats AS ( SELECT DISTINCT ON (device_id) device_id, endpoint, latest_handshake FROM wireguard_peer_stats WHERE network = $1 ORDER BY device_id, collected_at DESC ) SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN wireguard_network_device wnd ON wnd.device_id = d.id LEFT JOIN stats on d.id = stats.device_id WHERE wnd.wireguard_network_id = $1 AND wnd.is_authorized = true AND (wnd.authorized_at IS NULL OR (NOW() - wnd.authorized_at) > $2 * interval '1 second') AND (stats.latest_handshake IS NULL OR (NOW() - stats.latest_handshake) > $2 * interval '1 second')", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -43,5 +43,5 @@ false ] }, - "hash": "1a45960695ad8a4f60cd94b610f9cf0c9b09ea742ea07a8de4ef8e6b199f2269" + "hash": "0e9a8c2395d43898748c176db0d89fa20784cd93ca1dfd00b2755934937d8270" } diff --git a/.sqlx/query-efe773da199984a169b636cafabd4ee0967c7dc68a04ec930f863d8f3f34c14a.json b/.sqlx/query-1403aba7198c132812aa5642166e70c14b3c40a969b496972d499a975fb7b0b4.json similarity index 64% rename from .sqlx/query-efe773da199984a169b636cafabd4ee0967c7dc68a04ec930f863d8f3f34c14a.json rename to .sqlx/query-1403aba7198c132812aa5642166e70c14b3c40a969b496972d499a975fb7b0b4.json index 408dde103..b753d65e6 100644 --- a/.sqlx/query-efe773da199984a169b636cafabd4ee0967c7dc68a04ec930f863d8f3f34c14a.json +++ b/.sqlx/query-1403aba7198c132812aa5642166e70c14b3c40a969b496972d499a975fb7b0b4.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "WITH stats AS ( SELECT DISTINCT ON (network) network, endpoint, latest_handshake FROM wireguard_peer_stats WHERE device_id = $2 ORDER BY network, collected_at DESC ) SELECT n.id as network_id, n.name as network_name, n.endpoint as gateway_endpoint, wnd.wireguard_ip as \"device_wireguard_ip: IpAddr\", stats.endpoint as device_endpoint, stats.latest_handshake as \"latest_handshake?\", COALESCE (((NOW() - stats.latest_handshake) < $1 * interval '1 minute'), false) as \"is_active!\" FROM wireguard_network_device wnd JOIN wireguard_network n ON n.id = wnd.wireguard_network_id LEFT JOIN stats on n.id = stats.network WHERE wnd.device_id = $2", + "query": "WITH stats AS ( SELECT DISTINCT ON (network) network, endpoint, latest_handshake FROM wireguard_peer_stats WHERE device_id = $2 ORDER BY network, collected_at DESC ) SELECT n.id network_id, n.name network_name, n.endpoint gateway_endpoint, wnd.wireguard_ip \"device_wireguard_ip: IpAddr\", stats.endpoint device_endpoint, stats.latest_handshake \"latest_handshake?\", COALESCE (((NOW() - stats.latest_handshake) < $1 * interval '1 minute'), false) as \"is_active!\" FROM wireguard_network_device wnd JOIN wireguard_network n ON n.id = wnd.wireguard_network_id LEFT JOIN stats on n.id = stats.network WHERE wnd.device_id = $2", "describe": { "columns": [ { @@ -55,5 +55,5 @@ null ] }, - "hash": "efe773da199984a169b636cafabd4ee0967c7dc68a04ec930f863d8f3f34c14a" + "hash": "1403aba7198c132812aa5642166e70c14b3c40a969b496972d499a975fb7b0b4" } diff --git a/.sqlx/query-78766325753732e8873adc2b04bf73a3770887ba3b28c8568e71a7e4b83ab7a4.json b/.sqlx/query-18a1ea41a270bdf89e241948ca49d5a1bc27b5b1253d0c87d122355dbc0ff3cc.json similarity index 80% rename from .sqlx/query-78766325753732e8873adc2b04bf73a3770887ba3b28c8568e71a7e4b83ab7a4.json rename to .sqlx/query-18a1ea41a270bdf89e241948ca49d5a1bc27b5b1253d0c87d122355dbc0ff3cc.json index 2a9f91497..c7dc27f07 100644 --- a/.sqlx/query-78766325753732e8873adc2b04bf73a3770887ba3b28c8568e71a7e4b83ab7a4.json +++ b/.sqlx/query-18a1ea41a270bdf89e241948ca49d5a1bc27b5b1253d0c87d122355dbc0ff3cc.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"client_id\",\"code\",\"redirect_uri\",\"scope\",\"auth_time\",\"nonce\",\"code_challenge\" FROM \"authorization_code\"", + "query": "SELECT id, \"user_id\",\"client_id\",\"code\",\"redirect_uri\",\"scope\",\"auth_time\",\"nonce\",\"code_challenge\" FROM \"authorization_code\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -64,5 +64,5 @@ true ] }, - "hash": "78766325753732e8873adc2b04bf73a3770887ba3b28c8568e71a7e4b83ab7a4" + "hash": "18a1ea41a270bdf89e241948ca49d5a1bc27b5b1253d0c87d122355dbc0ff3cc" } diff --git a/.sqlx/query-904ae3993de08b02a547db8d656a23d4a8e7e2d4bd556b1e2f60088b9f6fc679.json b/.sqlx/query-18d09bdf2b6780e6050a073a421df5397ed53cfba284942b402b8a0650f5a953.json similarity index 77% rename from .sqlx/query-904ae3993de08b02a547db8d656a23d4a8e7e2d4bd556b1e2f60088b9f6fc679.json rename to .sqlx/query-18d09bdf2b6780e6050a073a421df5397ed53cfba284942b402b8a0650f5a953.json index 6f2be25b6..2dbfacf39 100644 --- a/.sqlx/query-904ae3993de08b02a547db8d656a23d4a8e7e2d4bd556b1e2f60088b9f6fc679.json +++ b/.sqlx/query-18d09bdf2b6780e6050a073a421df5397ed53cfba284942b402b8a0650f5a953.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", client_id, client_secret, redirect_uri, scope, name, enabled FROM oauth2client WHERE client_id = $1 AND client_secret = $2 AND enabled", + "query": "SELECT id, client_id, client_secret, redirect_uri, scope, name, enabled FROM oauth2client WHERE client_id = $1 AND client_secret = $2 AND enabled", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -55,5 +55,5 @@ false ] }, - "hash": "904ae3993de08b02a547db8d656a23d4a8e7e2d4bd556b1e2f60088b9f6fc679" + "hash": "18d09bdf2b6780e6050a073a421df5397ed53cfba284942b402b8a0650f5a953" } diff --git a/.sqlx/query-1aab945b0f7df1c079ae359b17b0629e7c30d4c7e6605157f8cb2bb1cfec322a.json b/.sqlx/query-1aab945b0f7df1c079ae359b17b0629e7c30d4c7e6605157f8cb2bb1cfec322a.json new file mode 100644 index 000000000..eeb053bd9 --- /dev/null +++ b/.sqlx/query-1aab945b0f7df1c079ae359b17b0629e7c30d4c7e6605157f8cb2bb1cfec322a.json @@ -0,0 +1,125 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT \"user\".id, username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, totp_secret, email_mfa_enabled, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" INNER JOIN \"group_user\" ON \"user\".id = \"group_user\".user_id INNER JOIN \"group\" ON \"group_user\".group_id = \"group\".id WHERE \"group\".name = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "password_hash", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "last_name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "first_name", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "phone", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "mfa_enabled", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "totp_enabled", + "type_info": "Bool" + }, + { + "ordinal": 9, + "name": "totp_secret", + "type_info": "Bytea" + }, + { + "ordinal": 10, + "name": "email_mfa_enabled", + "type_info": "Bool" + }, + { + "ordinal": 11, + "name": "email_mfa_secret", + "type_info": "Bytea" + }, + { + "ordinal": 12, + "name": "mfa_method: _", + "type_info": { + "Custom": { + "name": "mfa_method", + "kind": { + "Enum": [ + "none", + "one_time_password", + "webauthn", + "web3", + "email" + ] + } + } + } + }, + { + "ordinal": 13, + "name": "recovery_codes", + "type_info": "TextArray" + }, + { + "ordinal": 14, + "name": "is_active", + "type_info": "Bool" + }, + { + "ordinal": 15, + "name": "openid_sub", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + true, + false, + false, + false, + true, + false, + false, + true, + false, + true, + false, + false, + false, + true + ] + }, + "hash": "1aab945b0f7df1c079ae359b17b0629e7c30d4c7e6605157f8cb2bb1cfec322a" +} diff --git a/.sqlx/query-3bdf311fd1b919363cf8de9292cbbe511c5f2c1e4b572ae791852daabe71981e.json b/.sqlx/query-1b1f0a82cbbf2abbafb7cae6718fb50c0a8294bce9757431ca3a6a0505f39df3.json similarity index 85% rename from .sqlx/query-3bdf311fd1b919363cf8de9292cbbe511c5f2c1e4b572ae791852daabe71981e.json rename to .sqlx/query-1b1f0a82cbbf2abbafb7cae6718fb50c0a8294bce9757431ca3a6a0505f39df3.json index 2f7fc501b..e9efbe782 100644 --- a/.sqlx/query-3bdf311fd1b919363cf8de9292cbbe511c5f2c1e4b572ae791852daabe71981e.json +++ b/.sqlx/query-1b1f0a82cbbf2abbafb7cae6718fb50c0a8294bce9757431ca3a6a0505f39df3.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" WHERE id = ANY($1)", + "query": "SELECT id, username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" WHERE id = ANY($1)", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -121,5 +121,5 @@ true ] }, - "hash": "3bdf311fd1b919363cf8de9292cbbe511c5f2c1e4b572ae791852daabe71981e" + "hash": "1b1f0a82cbbf2abbafb7cae6718fb50c0a8294bce9757431ca3a6a0505f39df3" } diff --git a/.sqlx/query-3e6fa53cc900724e25e127f472cb0cb5b5e76fbcf424a79b94862f623ea975fc.json b/.sqlx/query-1cad18ee4393faed671f07a2f0faeff413376b8c4760ef86f5de6a7e74b7c858.json similarity index 76% rename from .sqlx/query-3e6fa53cc900724e25e127f472cb0cb5b5e76fbcf424a79b94862f623ea975fc.json rename to .sqlx/query-1cad18ee4393faed671f07a2f0faeff413376b8c4760ef86f5de6a7e74b7c858.json index 431b7f8fa..97dac0244 100644 --- a/.sqlx/query-3e6fa53cc900724e25e127f472cb0cb5b5e76fbcf424a79b94862f623ea975fc.json +++ b/.sqlx/query-1cad18ee4393faed671f07a2f0faeff413376b8c4760ef86f5de6a7e74b7c858.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created FROM device WHERE user_id = $1", + "query": "SELECT id, \"name\",\"wireguard_pubkey\",\"user_id\",\"created\" FROM \"device\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -42,5 +42,5 @@ false ] }, - "hash": "3e6fa53cc900724e25e127f472cb0cb5b5e76fbcf424a79b94862f623ea975fc" + "hash": "1cad18ee4393faed671f07a2f0faeff413376b8c4760ef86f5de6a7e74b7c858" } diff --git a/.sqlx/query-2163bfa0df95cc31d0325f2a2a6521ef7e3b701ba5a97a74d5e6b3637c626dda.json b/.sqlx/query-2163bfa0df95cc31d0325f2a2a6521ef7e3b701ba5a97a74d5e6b3637c626dda.json deleted file mode 100644 index f83e89958..000000000 --- a/.sqlx/query-2163bfa0df95cc31d0325f2a2a6521ef7e3b701ba5a97a74d5e6b3637c626dda.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM \"settings\" WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "2163bfa0df95cc31d0325f2a2a6521ef7e3b701ba5a97a74d5e6b3637c626dda" -} diff --git a/.sqlx/query-443177d880a607fc5c545cad75db89ab8ca59b3b1bc9f5ed6847afd7fea9d68c.json b/.sqlx/query-227df2a09adeb2387780e7df5b270bd60d626294a6b9b855777641934d898d43.json similarity index 76% rename from .sqlx/query-443177d880a607fc5c545cad75db89ab8ca59b3b1bc9f5ed6847afd7fea9d68c.json rename to .sqlx/query-227df2a09adeb2387780e7df5b270bd60d626294a6b9b855777641934d898d43.json index 2efcfdf4d..e168bad94 100644 --- a/.sqlx/query-443177d880a607fc5c545cad75db89ab8ca59b3b1bc9f5ed6847afd7fea9d68c.json +++ b/.sqlx/query-227df2a09adeb2387780e7df5b270bd60d626294a6b9b855777641934d898d43.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"client_id\",\"client_secret\",\"redirect_uri\" \"redirect_uri: _\",\"scope\" \"scope: _\",\"name\",\"enabled\" FROM \"oauth2client\" WHERE id = $1", + "query": "SELECT id, \"client_id\",\"client_secret\",\"redirect_uri\" \"redirect_uri: _\",\"scope\" \"scope: _\",\"name\",\"enabled\" FROM \"oauth2client\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -54,5 +54,5 @@ false ] }, - "hash": "443177d880a607fc5c545cad75db89ab8ca59b3b1bc9f5ed6847afd7fea9d68c" + "hash": "227df2a09adeb2387780e7df5b270bd60d626294a6b9b855777641934d898d43" } diff --git a/.sqlx/query-c3f735782212d2b2d218f2ad66f6febabc15367dd4b7fdba1ae0b8045a84c4ee.json b/.sqlx/query-244d19b770616a733faa1626de860d1d6d5d836bf759a4747726000515b7ae3e.json similarity index 83% rename from .sqlx/query-c3f735782212d2b2d218f2ad66f6febabc15367dd4b7fdba1ae0b8045a84c4ee.json rename to .sqlx/query-244d19b770616a733faa1626de860d1d6d5d836bf759a4747726000515b7ae3e.json index 5fc015afb..48c3c62c8 100644 --- a/.sqlx/query-c3f735782212d2b2d218f2ad66f6febabc15367dd4b7fdba1ae0b8045a84c4ee.json +++ b/.sqlx/query-244d19b770616a733faa1626de860d1d6d5d836bf759a4747726000515b7ae3e.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"username\",\"password_hash\",\"last_name\",\"first_name\",\"email\",\"phone\",\"mfa_enabled\",\"is_active\",\"openid_sub\",\"totp_enabled\",\"email_mfa_enabled\",\"totp_secret\",\"email_mfa_secret\",\"mfa_method\" \"mfa_method: _\",\"recovery_codes\" \"recovery_codes: _\" FROM \"user\"", + "query": "SELECT id, \"username\",\"password_hash\",\"last_name\",\"first_name\",\"email\",\"phone\",\"mfa_enabled\",\"is_active\",\"openid_sub\",\"totp_enabled\",\"email_mfa_enabled\",\"totp_secret\",\"email_mfa_secret\",\"mfa_method\" \"mfa_method: _\",\"recovery_codes\" \"recovery_codes: _\" FROM \"user\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -119,5 +119,5 @@ false ] }, - "hash": "c3f735782212d2b2d218f2ad66f6febabc15367dd4b7fdba1ae0b8045a84c4ee" + "hash": "244d19b770616a733faa1626de860d1d6d5d836bf759a4747726000515b7ae3e" } diff --git a/.sqlx/query-65af2457c30994d33ef2d6d265e04523dc1c69fa0052089d341c41862fdc4425.json b/.sqlx/query-27fabf7f3fc353eea6f485d902dbbb5a1f83a01f0bacf7b50c2b5d11f6dd0fde.json similarity index 73% rename from .sqlx/query-65af2457c30994d33ef2d6d265e04523dc1c69fa0052089d341c41862fdc4425.json rename to .sqlx/query-27fabf7f3fc353eea6f485d902dbbb5a1f83a01f0bacf7b50c2b5d11f6dd0fde.json index 0111f9c65..bd0e7698f 100644 --- a/.sqlx/query-65af2457c30994d33ef2d6d265e04523dc1c69fa0052089d341c41862fdc4425.json +++ b/.sqlx/query-27fabf7f3fc353eea6f485d902dbbb5a1f83a01f0bacf7b50c2b5d11f6dd0fde.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"token\",\"device_id\",\"created_at\" FROM \"pollingtoken\"", + "query": "SELECT id, \"token\",\"device_id\",\"created_at\" FROM \"pollingtoken\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -34,5 +34,5 @@ false ] }, - "hash": "65af2457c30994d33ef2d6d265e04523dc1c69fa0052089d341c41862fdc4425" + "hash": "27fabf7f3fc353eea6f485d902dbbb5a1f83a01f0bacf7b50c2b5d11f6dd0fde" } diff --git a/.sqlx/query-18bffe90d894f5a122df97ef37c4afb69f97c74acf36ee7596476ab7fee850b3.json b/.sqlx/query-281722fbef2b32819f1022d99b8973eb7f1ae83a2d2a7406b8763687821a2c14.json similarity index 74% rename from .sqlx/query-18bffe90d894f5a122df97ef37c4afb69f97c74acf36ee7596476ab7fee850b3.json rename to .sqlx/query-281722fbef2b32819f1022d99b8973eb7f1ae83a2d2a7406b8763687821a2c14.json index 0902ed5b0..5110e6a87 100644 --- a/.sqlx/query-18bffe90d894f5a122df97ef37c4afb69f97c74acf36ee7596476ab7fee850b3.json +++ b/.sqlx/query-281722fbef2b32819f1022d99b8973eb7f1ae83a2d2a7406b8763687821a2c14.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, name, passkey FROM webauthn WHERE user_id = $1", + "query": "SELECT id, \"user_id\",\"name\",\"passkey\" FROM \"webauthn\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -36,5 +36,5 @@ false ] }, - "hash": "18bffe90d894f5a122df97ef37c4afb69f97c74acf36ee7596476ab7fee850b3" + "hash": "281722fbef2b32819f1022d99b8973eb7f1ae83a2d2a7406b8763687821a2c14" } diff --git a/.sqlx/query-e2483b9d167af9476ba49c63fa20a07aa53bfe7f783fcb9257312304094d5edc.json b/.sqlx/query-283e1c3d082f1388fc2b806bdcab715db1c1df67da573b0f132fea265e42b416.json similarity index 61% rename from .sqlx/query-e2483b9d167af9476ba49c63fa20a07aa53bfe7f783fcb9257312304094d5edc.json rename to .sqlx/query-283e1c3d082f1388fc2b806bdcab715db1c1df67da573b0f132fea265e42b416.json index beb848d75..c1696c5b0 100644 --- a/.sqlx/query-e2483b9d167af9476ba49c63fa20a07aa53bfe7f783fcb9257312304094d5edc.json +++ b/.sqlx/query-283e1c3d082f1388fc2b806bdcab715db1c1df67da573b0f132fea265e42b416.json @@ -1,25 +1,20 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"admin_device_management\",\"disable_all_traffic\",\"only_client_activation\" FROM \"enterprisesettings\"", + "query": "SELECT admin_device_management, disable_all_traffic, only_client_activation FROM \"enterprisesettings\" WHERE id = 1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", - "type_info": "Int8" - }, - { - "ordinal": 1, "name": "admin_device_management", "type_info": "Bool" }, { - "ordinal": 2, + "ordinal": 1, "name": "disable_all_traffic", "type_info": "Bool" }, { - "ordinal": 3, + "ordinal": 2, "name": "only_client_activation", "type_info": "Bool" } @@ -28,11 +23,10 @@ "Left": [] }, "nullable": [ - false, false, false, false ] }, - "hash": "e2483b9d167af9476ba49c63fa20a07aa53bfe7f783fcb9257312304094d5edc" + "hash": "283e1c3d082f1388fc2b806bdcab715db1c1df67da573b0f132fea265e42b416" } diff --git a/.sqlx/query-2d84370e3cabed5f8578ef3934d7f3a3863f267eab921370d4db55cf97fea979.json b/.sqlx/query-2d84370e3cabed5f8578ef3934d7f3a3863f267eab921370d4db55cf97fea979.json new file mode 100644 index 000000000..14798cf9d --- /dev/null +++ b/.sqlx/query-2d84370e3cabed5f8578ef3934d7f3a3863f267eab921370d4db55cf97fea979.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE \"settings\" SET license = $1 WHERE id = 1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "2d84370e3cabed5f8578ef3934d7f3a3863f267eab921370d4db55cf97fea979" +} diff --git a/.sqlx/query-1ba009aaa52a3f96a98e94c16992cb4d9a5da539a159d550b7f6207e67d5e802.json b/.sqlx/query-3017740cad14426beaca494cdf133e525b75984aa636f7cc8cc17a406e64b759.json similarity index 85% rename from .sqlx/query-1ba009aaa52a3f96a98e94c16992cb4d9a5da539a159d550b7f6207e67d5e802.json rename to .sqlx/query-3017740cad14426beaca494cdf133e525b75984aa636f7cc8cc17a406e64b759.json index 4a871dbc9..c64ac829c 100644 --- a/.sqlx/query-1ba009aaa52a3f96a98e94c16992cb4d9a5da539a159d550b7f6207e67d5e802.json +++ b/.sqlx/query-3017740cad14426beaca494cdf133e525b75984aa636f7cc8cc17a406e64b759.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" WHERE email = $1", + "query": "SELECT id, username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" WHERE email = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -121,5 +121,5 @@ true ] }, - "hash": "1ba009aaa52a3f96a98e94c16992cb4d9a5da539a159d550b7f6207e67d5e802" + "hash": "3017740cad14426beaca494cdf133e525b75984aa636f7cc8cc17a406e64b759" } diff --git a/.sqlx/query-30b117d8b863a0785fcb164bc428ea5591fe28ea9e07170906eb78c3ae4531d8.json b/.sqlx/query-30b117d8b863a0785fcb164bc428ea5591fe28ea9e07170906eb78c3ae4531d8.json deleted file mode 100644 index f26afcd0a..000000000 --- a/.sqlx/query-30b117d8b863a0785fcb164bc428ea5591fe28ea9e07170906eb78c3ae4531d8.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM \"enterprisesettings\" WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "30b117d8b863a0785fcb164bc428ea5591fe28ea9e07170906eb78c3ae4531d8" -} diff --git a/.sqlx/query-e0d59d4aa01a863eca5f00470c70112fd9a78c4565fd8e44185dc31c9b894b9e.json b/.sqlx/query-321ce9355db39c3d93d8b915ba7888357f5403c2e9e97fa17e502223bbcc918a.json similarity index 81% rename from .sqlx/query-e0d59d4aa01a863eca5f00470c70112fd9a78c4565fd8e44185dc31c9b894b9e.json rename to .sqlx/query-321ce9355db39c3d93d8b915ba7888357f5403c2e9e97fa17e502223bbcc918a.json index bb2b4ad1e..c4bb58463 100644 --- a/.sqlx/query-e0d59d4aa01a863eca5f00470c70112fd9a78c4565fd8e44185dc31c9b894b9e.json +++ b/.sqlx/query-321ce9355db39c3d93d8b915ba7888357f5403c2e9e97fa17e502223bbcc918a.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, client_id, code, redirect_uri, scope, auth_time, nonce, code_challenge FROM authorization_code WHERE code = $1", + "query": "SELECT id, user_id, client_id, code, redirect_uri, scope, auth_time, nonce, code_challenge FROM authorization_code WHERE code = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -66,5 +66,5 @@ true ] }, - "hash": "e0d59d4aa01a863eca5f00470c70112fd9a78c4565fd8e44185dc31c9b894b9e" + "hash": "321ce9355db39c3d93d8b915ba7888357f5403c2e9e97fa17e502223bbcc918a" } diff --git a/.sqlx/query-c4f0393fc5b1bb1603f1da3a6f2cb7e6253a0cb8fffd710e5a4d03cf9a4eef16.json b/.sqlx/query-342442c866249cb211377ad73d5e2cbd434104a0b0294ccdd2d0b4aedf611b18.json similarity index 74% rename from .sqlx/query-c4f0393fc5b1bb1603f1da3a6f2cb7e6253a0cb8fffd710e5a4d03cf9a4eef16.json rename to .sqlx/query-342442c866249cb211377ad73d5e2cbd434104a0b0294ccdd2d0b4aedf611b18.json index 2d73e7c9a..4717ef83f 100644 --- a/.sqlx/query-c4f0393fc5b1bb1603f1da3a6f2cb7e6253a0cb8fffd710e5a4d03cf9a4eef16.json +++ b/.sqlx/query-342442c866249cb211377ad73d5e2cbd434104a0b0294ccdd2d0b4aedf611b18.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"name\",\"passkey\" FROM \"webauthn\"", + "query": "SELECT id, \"user_id\",\"name\",\"passkey\" FROM \"webauthn\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -34,5 +34,5 @@ false ] }, - "hash": "c4f0393fc5b1bb1603f1da3a6f2cb7e6253a0cb8fffd710e5a4d03cf9a4eef16" + "hash": "342442c866249cb211377ad73d5e2cbd434104a0b0294ccdd2d0b4aedf611b18" } diff --git a/.sqlx/query-367204ba11a051f52d4b287eea7ca6a5c5c81a0ebc294a31c49e68e3fdbb20f0.json b/.sqlx/query-367204ba11a051f52d4b287eea7ca6a5c5c81a0ebc294a31c49e68e3fdbb20f0.json deleted file mode 100644 index 8b98d1ab2..000000000 --- a/.sqlx/query-367204ba11a051f52d4b287eea7ca6a5c5c81a0ebc294a31c49e68e3fdbb20f0.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO \"settings\" (\"openid_enabled\",\"wireguard_enabled\",\"webhooks_enabled\",\"worker_enabled\",\"challenge_template\",\"instance_name\",\"main_logo_url\",\"nav_logo_url\",\"smtp_server\",\"smtp_port\",\"smtp_encryption\",\"smtp_user\",\"smtp_password\",\"smtp_sender\",\"enrollment_vpn_step_optional\",\"enrollment_welcome_message\",\"enrollment_welcome_email\",\"enrollment_welcome_email_subject\",\"enrollment_use_welcome_message_as_email\",\"uuid\",\"ldap_url\",\"ldap_bind_username\",\"ldap_bind_password\",\"ldap_group_search_base\",\"ldap_user_search_base\",\"ldap_user_obj_class\",\"ldap_group_obj_class\",\"ldap_username_attr\",\"ldap_groupname_attr\",\"ldap_group_member_attr\",\"ldap_member_attr\",\"openid_create_account\",\"license\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33) RETURNING id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Bool", - "Bool", - "Bool", - "Bool", - "Text", - "Text", - "Text", - "Text", - "Text", - "Int4", - { - "Custom": { - "name": "smtp_encryption", - "kind": { - "Enum": [ - "none", - "starttls", - "implicittls" - ] - } - } - }, - "Text", - "Text", - "Text", - "Bool", - "Text", - "Text", - "Text", - "Bool", - "Uuid", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Bool", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "367204ba11a051f52d4b287eea7ca6a5c5c81a0ebc294a31c49e68e3fdbb20f0" -} diff --git a/.sqlx/query-393cacddf87582e0a72156580765dfb15404030a4f28406cbaa344ab279300d7.json b/.sqlx/query-393cacddf87582e0a72156580765dfb15404030a4f28406cbaa344ab279300d7.json deleted file mode 100644 index 6fa96fee8..000000000 --- a/.sqlx/query-393cacddf87582e0a72156580765dfb15404030a4f28406cbaa344ab279300d7.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT DISTINCT ON (d.id) d.id as \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN \"user\" u ON d.user_id = u.id JOIN group_user gu ON u.id = gu.user_id JOIN \"group\" g ON gu.group_id = g.id WHERE g.\"name\" IN (SELECT * FROM UNNEST($1::text[]))\n AND u.is_active = true\n ORDER BY d.id ASC", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id?", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "wireguard_pubkey", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "user_id", - "type_info": "Int8" - }, - { - "ordinal": 4, - "name": "created", - "type_info": "Timestamp" - } - ], - "parameters": { - "Left": [ - "TextArray" - ] - }, - "nullable": [ - false, - false, - false, - false, - false - ] - }, - "hash": "393cacddf87582e0a72156580765dfb15404030a4f28406cbaa344ab279300d7" -} diff --git a/.sqlx/query-f2a1c41dc09e84bd53764f118d120cff9be08cc24c6a6ef15bf404d239d919cc.json b/.sqlx/query-3c9f65c18fd93b02f5b01d13af7b984f1b2043615f2c5b2dafe5055ce27ee91b.json similarity index 80% rename from .sqlx/query-f2a1c41dc09e84bd53764f118d120cff9be08cc24c6a6ef15bf404d239d919cc.json rename to .sqlx/query-3c9f65c18fd93b02f5b01d13af7b984f1b2043615f2c5b2dafe5055ce27ee91b.json index 205c5e977..671abb813 100644 --- a/.sqlx/query-f2a1c41dc09e84bd53764f118d120cff9be08cc24c6a6ef15bf404d239d919cc.json +++ b/.sqlx/query-3c9f65c18fd93b02f5b01d13af7b984f1b2043615f2c5b2dafe5055ce27ee91b.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"yubikey_id\",\"name\",\"user_id\",\"key\",\"key_type\" \"key_type: _\" FROM \"authentication_key\"", + "query": "SELECT id, \"yubikey_id\",\"name\",\"user_id\",\"key\",\"key_type\" \"key_type: _\" FROM \"authentication_key\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -56,5 +56,5 @@ false ] }, - "hash": "f2a1c41dc09e84bd53764f118d120cff9be08cc24c6a6ef15bf404d239d919cc" + "hash": "3c9f65c18fd93b02f5b01d13af7b984f1b2043615f2c5b2dafe5055ce27ee91b" } diff --git a/.sqlx/query-ea43c7bfcd2de8f05d3db1123ddb2cd0652284d321d533454bb4f3e04d835904.json b/.sqlx/query-3d0439898529e2e68c87a1939ca331d564ed406f733c079b526f2a53031b859d.json similarity index 78% rename from .sqlx/query-ea43c7bfcd2de8f05d3db1123ddb2cd0652284d321d533454bb4f3e04d835904.json rename to .sqlx/query-3d0439898529e2e68c87a1939ca331d564ed406f733c079b526f2a53031b859d.json index 6bd9ca71d..b92f12fbe 100644 --- a/.sqlx/query-ea43c7bfcd2de8f05d3db1123ddb2cd0652284d321d533454bb4f3e04d835904.json +++ b/.sqlx/query-3d0439898529e2e68c87a1939ca331d564ed406f733c079b526f2a53031b859d.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, ip_address, model, family, brand, os_family, browser, event_type, created\n FROM device_login_event WHERE user_id = $1 AND event_type = $2 AND family = $3 AND brand = $4 AND model = $5 AND browser = $6", + "query": "SELECT id, user_id, ip_address, model, family, brand, os_family, browser, event_type, created FROM device_login_event WHERE user_id = $1 AND event_type = $2 AND family = $3 AND brand = $4 AND model = $5 AND browser = $6", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -77,5 +77,5 @@ false ] }, - "hash": "ea43c7bfcd2de8f05d3db1123ddb2cd0652284d321d533454bb4f3e04d835904" + "hash": "3d0439898529e2e68c87a1939ca331d564ed406f733c079b526f2a53031b859d" } diff --git a/.sqlx/query-48ecfa6318149ac712807ca8fa0ec1fcb972f917729ffb9b7849830113d04bd8.json b/.sqlx/query-3e548b5ec5f39cdd2134434781061dc596a7c772b7332c2b008f31db6f2f10d5.json similarity index 73% rename from .sqlx/query-48ecfa6318149ac712807ca8fa0ec1fcb972f917729ffb9b7849830113d04bd8.json rename to .sqlx/query-3e548b5ec5f39cdd2134434781061dc596a7c772b7332c2b008f31db6f2f10d5.json index c79058797..266b4ccf9 100644 --- a/.sqlx/query-48ecfa6318149ac712807ca8fa0ec1fcb972f917729ffb9b7849830113d04bd8.json +++ b/.sqlx/query-3e548b5ec5f39cdd2134434781061dc596a7c772b7332c2b008f31db6f2f10d5.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"name\",\"passkey\" FROM \"webauthn\" WHERE id = $1", + "query": "SELECT id, user_id, name, passkey FROM webauthn WHERE user_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -36,5 +36,5 @@ false ] }, - "hash": "48ecfa6318149ac712807ca8fa0ec1fcb972f917729ffb9b7849830113d04bd8" + "hash": "3e548b5ec5f39cdd2134434781061dc596a7c772b7332c2b008f31db6f2f10d5" } diff --git a/.sqlx/query-84679835466cb41a74dd9ef281c9a69451102dae52ffb5a4df99e160a1ec8907.json b/.sqlx/query-3f615b93c20a7d1e2740f6d826b78b62cfb433385383ffc8358be1a14ce13470.json similarity index 68% rename from .sqlx/query-84679835466cb41a74dd9ef281c9a69451102dae52ffb5a4df99e160a1ec8907.json rename to .sqlx/query-3f615b93c20a7d1e2740f6d826b78b62cfb433385383ffc8358be1a14ce13470.json index 15003141b..2fa7cce27 100644 --- a/.sqlx/query-84679835466cb41a74dd9ef281c9a69451102dae52ffb5a4df99e160a1ec8907.json +++ b/.sqlx/query-3f615b93c20a7d1e2740f6d826b78b62cfb433385383ffc8358be1a14ce13470.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT d.id \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN wireguard_network_device wnd ON d.id = wnd.device_id WHERE wnd.wireguard_ip = $1 AND wnd.wireguard_network_id = $2", + "query": "SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN wireguard_network_device wnd ON d.id = wnd.device_id WHERE wnd.wireguard_ip = $1 AND wnd.wireguard_network_id = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -43,5 +43,5 @@ false ] }, - "hash": "84679835466cb41a74dd9ef281c9a69451102dae52ffb5a4df99e160a1ec8907" + "hash": "3f615b93c20a7d1e2740f6d826b78b62cfb433385383ffc8358be1a14ce13470" } diff --git a/.sqlx/query-3d7def96ee88ac4f1c46ff6ef93b06b8acc1f4bc9e34ff1cca6d64d6d281c039.json b/.sqlx/query-45ab60569d24c9f9859a68c022cfc2fb13ea09e95ba0cb86f5419a85147dbf97.json similarity index 79% rename from .sqlx/query-3d7def96ee88ac4f1c46ff6ef93b06b8acc1f4bc9e34ff1cca6d64d6d281c039.json rename to .sqlx/query-45ab60569d24c9f9859a68c022cfc2fb13ea09e95ba0cb86f5419a85147dbf97.json index 0443a1d38..eefbf825a 100644 --- a/.sqlx/query-3d7def96ee88ac4f1c46ff6ef93b06b8acc1f4bc9e34ff1cca6d64d6d281c039.json +++ b/.sqlx/query-45ab60569d24c9f9859a68c022cfc2fb13ea09e95ba0cb86f5419a85147dbf97.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"device_id\",\"collected_at\",\"network\",\"endpoint\",\"upload\",\"download\",\"latest_handshake\",\"allowed_ips\" FROM \"wireguard_peer_stats\"", + "query": "SELECT id, \"device_id\",\"collected_at\",\"network\",\"endpoint\",\"upload\",\"download\",\"latest_handshake\",\"allowed_ips\" FROM \"wireguard_peer_stats\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -64,5 +64,5 @@ true ] }, - "hash": "3d7def96ee88ac4f1c46ff6ef93b06b8acc1f4bc9e34ff1cca6d64d6d281c039" + "hash": "45ab60569d24c9f9859a68c022cfc2fb13ea09e95ba0cb86f5419a85147dbf97" } diff --git a/.sqlx/query-15b81097f9a7f208205df7d316288e7fc380e2585495ddb1b726b008fa28bbf7.json b/.sqlx/query-45dd29c416448dbccfb3c3206684309f0470814bd94bdf06d77fdfcf98a7f109.json similarity index 81% rename from .sqlx/query-15b81097f9a7f208205df7d316288e7fc380e2585495ddb1b726b008fa28bbf7.json rename to .sqlx/query-45dd29c416448dbccfb3c3206684309f0470814bd94bdf06d77fdfcf98a7f109.json index 94da9a5fe..131af67c9 100644 --- a/.sqlx/query-15b81097f9a7f208205df7d316288e7fc380e2585495ddb1b726b008fa28bbf7.json +++ b/.sqlx/query-45dd29c416448dbccfb3c3206684309f0470814bd94bdf06d77fdfcf98a7f109.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"ip_address\",\"model\",\"family\",\"brand\",\"os_family\",\"browser\",\"event_type\",\"created\" FROM \"device_login_event\"", + "query": "SELECT id, \"user_id\",\"ip_address\",\"model\",\"family\",\"brand\",\"os_family\",\"browser\",\"event_type\",\"created\" FROM \"device_login_event\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -70,5 +70,5 @@ false ] }, - "hash": "15b81097f9a7f208205df7d316288e7fc380e2585495ddb1b726b008fa28bbf7" + "hash": "45dd29c416448dbccfb3c3206684309f0470814bd94bdf06d77fdfcf98a7f109" } diff --git a/.sqlx/query-48223bdfca4f2bd5c7cd14594f5d81db67dfb1c0c2c01cf1fd9c8df421f366ef.json b/.sqlx/query-48223bdfca4f2bd5c7cd14594f5d81db67dfb1c0c2c01cf1fd9c8df421f366ef.json deleted file mode 100644 index 2eb5251a9..000000000 --- a/.sqlx/query-48223bdfca4f2bd5c7cd14594f5d81db67dfb1c0c2c01cf1fd9c8df421f366ef.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE \"enterprisesettings\" SET \"admin_device_management\" = $2,\"disable_all_traffic\" = $3,\"only_client_activation\" = $4 WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Bool", - "Bool", - "Bool" - ] - }, - "nullable": [] - }, - "hash": "48223bdfca4f2bd5c7cd14594f5d81db67dfb1c0c2c01cf1fd9c8df421f366ef" -} diff --git a/.sqlx/query-06ecfe6d6dc628eaa2048071fc019f2c5100d73bee6759728af5d707fc68d725.json b/.sqlx/query-4b9ef60e8d5f1d361a51981740f470cbfe9779a169c432ec8f73efd8b69b082e.json similarity index 80% rename from .sqlx/query-06ecfe6d6dc628eaa2048071fc019f2c5100d73bee6759728af5d707fc68d725.json rename to .sqlx/query-4b9ef60e8d5f1d361a51981740f470cbfe9779a169c432ec8f73efd8b69b082e.json index e445e9c72..1e2eb1cad 100644 --- a/.sqlx/query-06ecfe6d6dc628eaa2048071fc019f2c5100d73bee6759728af5d707fc68d725.json +++ b/.sqlx/query-4b9ef60e8d5f1d361a51981740f470cbfe9779a169c432ec8f73efd8b69b082e.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"ip_address\",\"model\",\"family\",\"brand\",\"os_family\",\"browser\",\"event_type\",\"created\" FROM \"device_login_event\" WHERE id = $1", + "query": "SELECT id, \"user_id\",\"ip_address\",\"model\",\"family\",\"brand\",\"os_family\",\"browser\",\"event_type\",\"created\" FROM \"device_login_event\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -72,5 +72,5 @@ false ] }, - "hash": "06ecfe6d6dc628eaa2048071fc019f2c5100d73bee6759728af5d707fc68d725" + "hash": "4b9ef60e8d5f1d361a51981740f470cbfe9779a169c432ec8f73efd8b69b082e" } diff --git a/.sqlx/query-63dd22326d77a452d5624d378b0653a7b6b98d71caaf55c6651a44bbd57df017.json b/.sqlx/query-4d43391c1eda0e6e74187d3c7ade0a852264d7465295de0223e00cf1f69c98c1.json similarity index 70% rename from .sqlx/query-63dd22326d77a452d5624d378b0653a7b6b98d71caaf55c6651a44bbd57df017.json rename to .sqlx/query-4d43391c1eda0e6e74187d3c7ade0a852264d7465295de0223e00cf1f69c98c1.json index 758eb4113..e63c11d96 100644 --- a/.sqlx/query-63dd22326d77a452d5624d378b0653a7b6b98d71caaf55c6651a44bbd57df017.json +++ b/.sqlx/query-4d43391c1eda0e6e74187d3c7ade0a852264d7465295de0223e00cf1f69c98c1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT wireguard_network_id as network_id, wireguard_ip as \"device_wireguard_ip: IpAddr\", preshared_key, is_authorized FROM wireguard_network_device WHERE device_id = $1", + "query": "SELECT wireguard_network_id network_id, wireguard_ip \"device_wireguard_ip: IpAddr\", preshared_key, is_authorized FROM wireguard_network_device WHERE device_id = $1", "describe": { "columns": [ { @@ -36,5 +36,5 @@ false ] }, - "hash": "63dd22326d77a452d5624d378b0653a7b6b98d71caaf55c6651a44bbd57df017" + "hash": "4d43391c1eda0e6e74187d3c7ade0a852264d7465295de0223e00cf1f69c98c1" } diff --git a/.sqlx/query-15d8aae5b2f7cbc1c415f8e589b12c762f8f7f386beea4543f96f471814a6929.json b/.sqlx/query-4fd61ae53fb5cf6ccb74d124871049b180c6a873393cd229396074549b742a59.json similarity index 69% rename from .sqlx/query-15d8aae5b2f7cbc1c415f8e589b12c762f8f7f386beea4543f96f471814a6929.json rename to .sqlx/query-4fd61ae53fb5cf6ccb74d124871049b180c6a873393cd229396074549b742a59.json index 84e74c0c3..e87c246be 100644 --- a/.sqlx/query-15d8aae5b2f7cbc1c415f8e589b12c762f8f7f386beea4543f96f471814a6929.json +++ b/.sqlx/query-4fd61ae53fb5cf6ccb74d124871049b180c6a873393cd229396074549b742a59.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"oauth2client_id\" FROM \"oauth2authorizedapp\"", + "query": "SELECT id, \"user_id\",\"oauth2client_id\" FROM \"oauth2authorizedapp\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -28,5 +28,5 @@ false ] }, - "hash": "15d8aae5b2f7cbc1c415f8e589b12c762f8f7f386beea4543f96f471814a6929" + "hash": "4fd61ae53fb5cf6ccb74d124871049b180c6a873393cd229396074549b742a59" } diff --git a/.sqlx/query-51986b429728226f4c2b17d0af4eb0baed9bb96f3d19b38d9573b8a4422155d9.json b/.sqlx/query-55568f51eda479e3cdaeefd641802ccf6cdcebe76c12cde524b162552b002d89.json similarity index 81% rename from .sqlx/query-51986b429728226f4c2b17d0af4eb0baed9bb96f3d19b38d9573b8a4422155d9.json rename to .sqlx/query-55568f51eda479e3cdaeefd641802ccf6cdcebe76c12cde524b162552b002d89.json index 664cf4d35..819a0e66e 100644 --- a/.sqlx/query-51986b429728226f4c2b17d0af4eb0baed9bb96f3d19b38d9573b8a4422155d9.json +++ b/.sqlx/query-55568f51eda479e3cdaeefd641802ccf6cdcebe76c12cde524b162552b002d89.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT device_id, wireguard_network_id, wireguard_ip as \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at FROM wireguard_network_device WHERE wireguard_network_id = $1", + "query": "SELECT device_id, wireguard_network_id, wireguard_ip \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at FROM wireguard_network_device WHERE wireguard_network_id = $1", "describe": { "columns": [ { @@ -48,5 +48,5 @@ true ] }, - "hash": "51986b429728226f4c2b17d0af4eb0baed9bb96f3d19b38d9573b8a4422155d9" + "hash": "55568f51eda479e3cdaeefd641802ccf6cdcebe76c12cde524b162552b002d89" } diff --git a/.sqlx/query-1a01b8b88444b493abf74b2ec0ad649018244de3c7f23d98bfab71faa1a9fae1.json b/.sqlx/query-5678d744e1e6ba8e7301a3b9e200b61fcc18530d40e13549b4b0d99ed10cb86c.json similarity index 82% rename from .sqlx/query-1a01b8b88444b493abf74b2ec0ad649018244de3c7f23d98bfab71faa1a9fae1.json rename to .sqlx/query-5678d744e1e6ba8e7301a3b9e200b61fcc18530d40e13549b4b0d99ed10cb86c.json index c6790b55f..bb4d744b8 100644 --- a/.sqlx/query-1a01b8b88444b493abf74b2ec0ad649018244de3c7f23d98bfab71faa1a9fae1.json +++ b/.sqlx/query-5678d744e1e6ba8e7301a3b9e200b61fcc18530d40e13549b4b0d99ed10cb86c.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id as \"id?\", name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, mfa_enabled, keepalive_interval, peer_disconnect_threshold FROM wireguard_network WHERE mfa_enabled = true", + "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, mfa_enabled, keepalive_interval, peer_disconnect_threshold FROM wireguard_network WHERE mfa_enabled = true", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -88,5 +88,5 @@ false ] }, - "hash": "1a01b8b88444b493abf74b2ec0ad649018244de3c7f23d98bfab71faa1a9fae1" + "hash": "5678d744e1e6ba8e7301a3b9e200b61fcc18530d40e13549b4b0d99ed10cb86c" } diff --git a/.sqlx/query-7acf1bf5bd1597e01908c3a9e4d5540db061f77d69de853abe8e5f09149dfdf8.json b/.sqlx/query-591aa58e7b79ac54309941efbe447b945d933569289803126d906adf32a56b29.json similarity index 67% rename from .sqlx/query-7acf1bf5bd1597e01908c3a9e4d5540db061f77d69de853abe8e5f09149dfdf8.json rename to .sqlx/query-591aa58e7b79ac54309941efbe447b945d933569289803126d906adf32a56b29.json index 55fb6f943..b7ab4a77c 100644 --- a/.sqlx/query-7acf1bf5bd1597e01908c3a9e4d5540db061f77d69de853abe8e5f09149dfdf8.json +++ b/.sqlx/query-591aa58e7b79ac54309941efbe447b945d933569289803126d906adf32a56b29.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, oauth2client_id FROM oauth2authorizedapp WHERE user_id = $1 AND oauth2client_id = $2", + "query": "SELECT id, user_id, oauth2client_id FROM oauth2authorizedapp WHERE user_id = $1 AND oauth2client_id = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -31,5 +31,5 @@ false ] }, - "hash": "7acf1bf5bd1597e01908c3a9e4d5540db061f77d69de853abe8e5f09149dfdf8" + "hash": "591aa58e7b79ac54309941efbe447b945d933569289803126d906adf32a56b29" } diff --git a/.sqlx/query-8c69910bf01556b4e35a26a4bd583d1694b8053c8522e2e6a311e1e1b4b4de15.json b/.sqlx/query-59bf54c747b02c1611b71028e662634bb056938becc3eb63fc60c661a2afaab0.json similarity index 59% rename from .sqlx/query-8c69910bf01556b4e35a26a4bd583d1694b8053c8522e2e6a311e1e1b4b4de15.json rename to .sqlx/query-59bf54c747b02c1611b71028e662634bb056938becc3eb63fc60c661a2afaab0.json index 4aef0c1ba..aa10043a8 100644 --- a/.sqlx/query-8c69910bf01556b4e35a26a4bd583d1694b8053c8522e2e6a311e1e1b4b4de15.json +++ b/.sqlx/query-59bf54c747b02c1611b71028e662634bb056938becc3eb63fc60c661a2afaab0.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", name FROM \"group\" JOIN group_user ON \"group\".id = group_user.group_id WHERE group_user.user_id = $1", + "query": "SELECT id, name FROM \"group\" JOIN group_user ON \"group\".id = group_user.group_id WHERE group_user.user_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -24,5 +24,5 @@ false ] }, - "hash": "8c69910bf01556b4e35a26a4bd583d1694b8053c8522e2e6a311e1e1b4b4de15" + "hash": "59bf54c747b02c1611b71028e662634bb056938becc3eb63fc60c661a2afaab0" } diff --git a/.sqlx/query-439bef62ccc846cccca2c6979e5698a7aaba2beb19645b720eefa73e4dcac942.json b/.sqlx/query-5b4f2b3fabb2afc84764dfb06714823cb9b6fccbf717f531c09f0c8e8d0326e2.json similarity index 70% rename from .sqlx/query-439bef62ccc846cccca2c6979e5698a7aaba2beb19645b720eefa73e4dcac942.json rename to .sqlx/query-5b4f2b3fabb2afc84764dfb06714823cb9b6fccbf717f531c09f0c8e8d0326e2.json index b5ca2d931..272e72966 100644 --- a/.sqlx/query-439bef62ccc846cccca2c6979e5698a7aaba2beb19645b720eefa73e4dcac942.json +++ b/.sqlx/query-5b4f2b3fabb2afc84764dfb06714823cb9b6fccbf717f531c09f0c8e8d0326e2.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created FROM device JOIN \"user\" ON device.user_id = \"user\".id WHERE device.id = $1 AND \"user\".username = $2", + "query": "SELECT device.id, name, wireguard_pubkey, user_id, created FROM device JOIN \"user\" ON device.user_id = \"user\".id WHERE device.id = $1 AND \"user\".username = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -43,5 +43,5 @@ false ] }, - "hash": "439bef62ccc846cccca2c6979e5698a7aaba2beb19645b720eefa73e4dcac942" + "hash": "5b4f2b3fabb2afc84764dfb06714823cb9b6fccbf717f531c09f0c8e8d0326e2" } diff --git a/.sqlx/query-750c3343a64d8f02d6952ab115318f79cc32d8013a8255a87c745f73ef08a2df.json b/.sqlx/query-5df51ef040adc83fbfbe3c243dc4574b1ab955c7a9f8064996aae94f8752daec.json similarity index 76% rename from .sqlx/query-750c3343a64d8f02d6952ab115318f79cc32d8013a8255a87c745f73ef08a2df.json rename to .sqlx/query-5df51ef040adc83fbfbe3c243dc4574b1ab955c7a9f8064996aae94f8752daec.json index ee81bee7e..3b325e35c 100644 --- a/.sqlx/query-750c3343a64d8f02d6952ab115318f79cc32d8013a8255a87c745f73ef08a2df.json +++ b/.sqlx/query-5df51ef040adc83fbfbe3c243dc4574b1ab955c7a9f8064996aae94f8752daec.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, token, device_id, created_at\n FROM pollingtoken WHERE token = $1", + "query": "SELECT id, token, device_id, created_at FROM pollingtoken WHERE token = $1", "describe": { "columns": [ { @@ -36,5 +36,5 @@ false ] }, - "hash": "750c3343a64d8f02d6952ab115318f79cc32d8013a8255a87c745f73ef08a2df" + "hash": "5df51ef040adc83fbfbe3c243dc4574b1ab955c7a9f8064996aae94f8752daec" } diff --git a/.sqlx/query-b6c2a4ad60b6ec057e3d9ccda459fe361bec61cddd4d57dbaa32306aab754e7c.json b/.sqlx/query-5e6153dff9ca15df757c9fb79eeafca7785a03923c43adec1ea7bf3035a32f3c.json similarity index 78% rename from .sqlx/query-b6c2a4ad60b6ec057e3d9ccda459fe361bec61cddd4d57dbaa32306aab754e7c.json rename to .sqlx/query-5e6153dff9ca15df757c9fb79eeafca7785a03923c43adec1ea7bf3035a32f3c.json index e8d549181..b66bb8723 100644 --- a/.sqlx/query-b6c2a4ad60b6ec057e3d9ccda459fe361bec61cddd4d57dbaa32306aab754e7c.json +++ b/.sqlx/query-5e6153dff9ca15df757c9fb79eeafca7785a03923c43adec1ea7bf3035a32f3c.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, yubikey_id \"yubikey_id?\", key, name, key_type \"key_type: AuthenticationKeyType\" FROM authentication_key WHERE user_id = $1", + "query": "SELECT id, user_id, yubikey_id \"yubikey_id?\", key, name, key_type \"key_type: AuthenticationKeyType\" FROM authentication_key WHERE user_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -58,5 +58,5 @@ false ] }, - "hash": "b6c2a4ad60b6ec057e3d9ccda459fe361bec61cddd4d57dbaa32306aab754e7c" + "hash": "5e6153dff9ca15df757c9fb79eeafca7785a03923c43adec1ea7bf3035a32f3c" } diff --git a/.sqlx/query-9e3c1c1f52bc1a576012c57c7472f9f7601e1128b15fc0ecc7676cd5aa01c88c.json b/.sqlx/query-60689e3ad76c36ed3fed76d77244de90fda17abfee564c480274bd4a1446fb37.json similarity index 71% rename from .sqlx/query-9e3c1c1f52bc1a576012c57c7472f9f7601e1128b15fc0ecc7676cd5aa01c88c.json rename to .sqlx/query-60689e3ad76c36ed3fed76d77244de90fda17abfee564c480274bd4a1446fb37.json index 82874b3d7..ae5f88eae 100644 --- a/.sqlx/query-9e3c1c1f52bc1a576012c57c7472f9f7601e1128b15fc0ecc7676cd5aa01c88c.json +++ b/.sqlx/query-60689e3ad76c36ed3fed76d77244de90fda17abfee564c480274bd4a1446fb37.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created FROM device JOIN \"user\" ON device.user_id = \"user\".id WHERE device.id = $1 AND \"user\".id = $2", + "query": "SELECT device.id, name, wireguard_pubkey, user_id, created FROM device JOIN \"user\" ON device.user_id = \"user\".id WHERE device.id = $1 AND \"user\".id = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -43,5 +43,5 @@ false ] }, - "hash": "9e3c1c1f52bc1a576012c57c7472f9f7601e1128b15fc0ecc7676cd5aa01c88c" + "hash": "60689e3ad76c36ed3fed76d77244de90fda17abfee564c480274bd4a1446fb37" } diff --git a/.sqlx/query-59ff9c1093cb41a5f902cf42f92afbac37e0c581e96285340196841504ed3cf0.json b/.sqlx/query-64d373fbed97c81654689a8fbb26ffe854693a0d4ae16000242ef8db9a864b84.json similarity index 70% rename from .sqlx/query-59ff9c1093cb41a5f902cf42f92afbac37e0c581e96285340196841504ed3cf0.json rename to .sqlx/query-64d373fbed97c81654689a8fbb26ffe854693a0d4ae16000242ef8db9a864b84.json index 1f364cea3..e7f4770ad 100644 --- a/.sqlx/query-59ff9c1093cb41a5f902cf42f92afbac37e0c581e96285340196841504ed3cf0.json +++ b/.sqlx/query-64d373fbed97c81654689a8fbb26ffe854693a0d4ae16000242ef8db9a864b84.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT d.id as \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN \"user\" u ON d.user_id = u.id WHERE u.is_active = true ORDER BY d.id ASC", + "query": "SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN \"user\" u ON d.user_id = u.id WHERE u.is_active = true ORDER BY d.id ASC", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -40,5 +40,5 @@ false ] }, - "hash": "59ff9c1093cb41a5f902cf42f92afbac37e0c581e96285340196841504ed3cf0" + "hash": "64d373fbed97c81654689a8fbb26ffe854693a0d4ae16000242ef8db9a864b84" } diff --git a/.sqlx/query-68fc762354c30e66a83ba47746a0d4707e9b8e9829965bf6e4b334d1ab41bb6b.json b/.sqlx/query-68fc762354c30e66a83ba47746a0d4707e9b8e9829965bf6e4b334d1ab41bb6b.json deleted file mode 100644 index 010db59cc..000000000 --- a/.sqlx/query-68fc762354c30e66a83ba47746a0d4707e9b8e9829965bf6e4b334d1ab41bb6b.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"admin_device_management\",\"disable_all_traffic\",\"only_client_activation\" FROM \"enterprisesettings\" WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id?", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "admin_device_management", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "disable_all_traffic", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "only_client_activation", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "68fc762354c30e66a83ba47746a0d4707e9b8e9829965bf6e4b334d1ab41bb6b" -} diff --git a/.sqlx/query-beafeefdf6fe9ac1c3ccce8a4c3917ba8a651c86061a3912365c390492da0ef9.json b/.sqlx/query-69ddae4f160f29172f491b926ffa357cf56d6a61c792c619b3d127bf285ff680.json similarity index 83% rename from .sqlx/query-beafeefdf6fe9ac1c3ccce8a4c3917ba8a651c86061a3912365c390492da0ef9.json rename to .sqlx/query-69ddae4f160f29172f491b926ffa357cf56d6a61c792c619b3d127bf285ff680.json index b4cf4cc0c..ec51e42ff 100644 --- a/.sqlx/query-beafeefdf6fe9ac1c3ccce8a4c3917ba8a651c86061a3912365c390492da0ef9.json +++ b/.sqlx/query-69ddae4f160f29172f491b926ffa357cf56d6a61c792c619b3d127bf285ff680.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"username\",\"password_hash\",\"last_name\",\"first_name\",\"email\",\"phone\",\"mfa_enabled\",\"is_active\",\"openid_sub\",\"totp_enabled\",\"email_mfa_enabled\",\"totp_secret\",\"email_mfa_secret\",\"mfa_method\" \"mfa_method: _\",\"recovery_codes\" \"recovery_codes: _\" FROM \"user\" WHERE id = $1", + "query": "SELECT id, \"username\",\"password_hash\",\"last_name\",\"first_name\",\"email\",\"phone\",\"mfa_enabled\",\"is_active\",\"openid_sub\",\"totp_enabled\",\"email_mfa_enabled\",\"totp_secret\",\"email_mfa_secret\",\"mfa_method\" \"mfa_method: _\",\"recovery_codes\" \"recovery_codes: _\" FROM \"user\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -121,5 +121,5 @@ false ] }, - "hash": "beafeefdf6fe9ac1c3ccce8a4c3917ba8a651c86061a3912365c390492da0ef9" + "hash": "69ddae4f160f29172f491b926ffa357cf56d6a61c792c619b3d127bf285ff680" } diff --git a/.sqlx/query-b2410cc60e0e1dcacd03050d2a3ac417ca5822beebe08be3f23cc0e217e27740.json b/.sqlx/query-6fde26b448d0a4783060bf3e606e16931c0b71a91c88025eb1316287bd1f9d8c.json similarity index 67% rename from .sqlx/query-b2410cc60e0e1dcacd03050d2a3ac417ca5822beebe08be3f23cc0e217e27740.json rename to .sqlx/query-6fde26b448d0a4783060bf3e606e16931c0b71a91c88025eb1316287bd1f9d8c.json index bb5e7f6ad..5ba207612 100644 --- a/.sqlx/query-b2410cc60e0e1dcacd03050d2a3ac417ca5822beebe08be3f23cc0e217e27740.json +++ b/.sqlx/query-6fde26b448d0a4783060bf3e606e16931c0b71a91c88025eb1316287bd1f9d8c.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\" FROM \"group\" WHERE id = $1", + "query": "SELECT id, \"name\" FROM \"group\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -24,5 +24,5 @@ false ] }, - "hash": "b2410cc60e0e1dcacd03050d2a3ac417ca5822beebe08be3f23cc0e217e27740" + "hash": "6fde26b448d0a4783060bf3e606e16931c0b71a91c88025eb1316287bd1f9d8c" } diff --git a/.sqlx/query-61f006defe9db54d750f92c978522eae2b8fa401b802a07196f5541fa96524eb.json b/.sqlx/query-72958cb69e1b737ca4a3eb2915bbf8bde2ff8f3db10090cef071aeb932c99ab2.json similarity index 76% rename from .sqlx/query-61f006defe9db54d750f92c978522eae2b8fa401b802a07196f5541fa96524eb.json rename to .sqlx/query-72958cb69e1b737ca4a3eb2915bbf8bde2ff8f3db10090cef071aeb932c99ab2.json index 700ded04c..46c6f250e 100644 --- a/.sqlx/query-61f006defe9db54d750f92c978522eae2b8fa401b802a07196f5541fa96524eb.json +++ b/.sqlx/query-72958cb69e1b737ca4a3eb2915bbf8bde2ff8f3db10090cef071aeb932c99ab2.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", name, base_url, client_id, client_secret FROM openidprovider WHERE name = $1", + "query": "SELECT id, name, base_url, client_id, client_secret FROM openidprovider WHERE name = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -42,5 +42,5 @@ false ] }, - "hash": "61f006defe9db54d750f92c978522eae2b8fa401b802a07196f5541fa96524eb" + "hash": "72958cb69e1b737ca4a3eb2915bbf8bde2ff8f3db10090cef071aeb932c99ab2" } diff --git a/.sqlx/query-0377049270e6dcc4f0ec3571bd998dcdbab8366524ff1f20f48a208b9f7dd9c6.json b/.sqlx/query-77291d31a566e9deca452048a933ffce58f7d086e51d05e491333c0d55f6edb4.json similarity index 78% rename from .sqlx/query-0377049270e6dcc4f0ec3571bd998dcdbab8366524ff1f20f48a208b9f7dd9c6.json rename to .sqlx/query-77291d31a566e9deca452048a933ffce58f7d086e51d05e491333c0d55f6edb4.json index 73871505a..ac9724a83 100644 --- a/.sqlx/query-0377049270e6dcc4f0ec3571bd998dcdbab8366524ff1f20f48a208b9f7dd9c6.json +++ b/.sqlx/query-77291d31a566e9deca452048a933ffce58f7d086e51d05e491333c0d55f6edb4.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", client_id, client_secret, redirect_uri, scope, name, enabled FROM oauth2client WHERE client_id = $1 AND enabled", + "query": "SELECT id, client_id, client_secret, redirect_uri, scope, name, enabled FROM oauth2client WHERE client_id = $1 AND enabled", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -54,5 +54,5 @@ false ] }, - "hash": "0377049270e6dcc4f0ec3571bd998dcdbab8366524ff1f20f48a208b9f7dd9c6" + "hash": "77291d31a566e9deca452048a933ffce58f7d086e51d05e491333c0d55f6edb4" } diff --git a/.sqlx/query-7244c9c8e437770235fa6cdb48e66719abf054cae9a35f833b8418d974aa45e1.json b/.sqlx/query-772a39361c7e13ff5b4d21475271ebef14485356272da5674e2eb5ea76916dfc.json similarity index 79% rename from .sqlx/query-7244c9c8e437770235fa6cdb48e66719abf054cae9a35f833b8418d974aa45e1.json rename to .sqlx/query-772a39361c7e13ff5b4d21475271ebef14485356272da5674e2eb5ea76916dfc.json index d4010f8f0..5455837c6 100644 --- a/.sqlx/query-7244c9c8e437770235fa6cdb48e66719abf054cae9a35f833b8418d974aa45e1.json +++ b/.sqlx/query-772a39361c7e13ff5b4d21475271ebef14485356272da5674e2eb5ea76916dfc.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"client_id\",\"code\",\"redirect_uri\",\"scope\",\"auth_time\",\"nonce\",\"code_challenge\" FROM \"authorization_code\" WHERE id = $1", + "query": "SELECT id, \"user_id\",\"client_id\",\"code\",\"redirect_uri\",\"scope\",\"auth_time\",\"nonce\",\"code_challenge\" FROM \"authorization_code\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -66,5 +66,5 @@ true ] }, - "hash": "7244c9c8e437770235fa6cdb48e66719abf054cae9a35f833b8418d974aa45e1" + "hash": "772a39361c7e13ff5b4d21475271ebef14485356272da5674e2eb5ea76916dfc" } diff --git a/.sqlx/query-45a53587f6d8bee3de695b846d5bf85dbcf3fb1a202611480af6a7b9e28d864f.json b/.sqlx/query-7e10835be17818738155cb84070d878013fe50819f88285ac6f2606b42a3dd3d.json similarity index 83% rename from .sqlx/query-45a53587f6d8bee3de695b846d5bf85dbcf3fb1a202611480af6a7b9e28d864f.json rename to .sqlx/query-7e10835be17818738155cb84070d878013fe50819f88285ac6f2606b42a3dd3d.json index 57db8db09..3d8918576 100644 --- a/.sqlx/query-45a53587f6d8bee3de695b846d5bf85dbcf3fb1a202611480af6a7b9e28d864f.json +++ b/.sqlx/query-7e10835be17818738155cb84070d878013fe50819f88285ac6f2606b42a3dd3d.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id as \"id?\", name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, mfa_enabled, keepalive_interval, peer_disconnect_threshold FROM wireguard_network WHERE name = $1", + "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, mfa_enabled, keepalive_interval, peer_disconnect_threshold FROM wireguard_network WHERE name = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -90,5 +90,5 @@ false ] }, - "hash": "45a53587f6d8bee3de695b846d5bf85dbcf3fb1a202611480af6a7b9e28d864f" + "hash": "7e10835be17818738155cb84070d878013fe50819f88285ac6f2606b42a3dd3d" } diff --git a/.sqlx/query-834d5b83aebddf1e23511025d5f98b8a69e1227e8bfbd2b1d0110c8def72f8aa.json b/.sqlx/query-834d5b83aebddf1e23511025d5f98b8a69e1227e8bfbd2b1d0110c8def72f8aa.json deleted file mode 100644 index 96a0c7b4e..000000000 --- a/.sqlx/query-834d5b83aebddf1e23511025d5f98b8a69e1227e8bfbd2b1d0110c8def72f8aa.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO \"enterprisesettings\" (\"admin_device_management\",\"disable_all_traffic\",\"only_client_activation\") VALUES ($1,$2,$3) RETURNING id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Bool", - "Bool", - "Bool" - ] - }, - "nullable": [ - false - ] - }, - "hash": "834d5b83aebddf1e23511025d5f98b8a69e1227e8bfbd2b1d0110c8def72f8aa" -} diff --git a/.sqlx/query-b773bf99f9e3aafcade8bf57c6f9a49107df5e8d1452796c0bb4f2f657e2ec77.json b/.sqlx/query-865ffb2d7ee9809eaaac5bb7ad64371f0d066d89e565f72beed5965785dccfd2.json similarity index 55% rename from .sqlx/query-b773bf99f9e3aafcade8bf57c6f9a49107df5e8d1452796c0bb4f2f657e2ec77.json rename to .sqlx/query-865ffb2d7ee9809eaaac5bb7ad64371f0d066d89e565f72beed5965785dccfd2.json index 3c480bc09..969addc3c 100644 --- a/.sqlx/query-b773bf99f9e3aafcade8bf57c6f9a49107df5e8d1452796c0bb4f2f657e2ec77.json +++ b/.sqlx/query-865ffb2d7ee9809eaaac5bb7ad64371f0d066d89e565f72beed5965785dccfd2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT d.wireguard_pubkey as pubkey, preshared_key, array[host(wnd.wireguard_ip)] as \"allowed_ips!: Vec\" FROM wireguard_network_device wnd JOIN device d ON wnd.device_id = d.id JOIN \"user\" u ON d.user_id = u.id WHERE wireguard_network_id = $1 AND (is_authorized = true OR NOT $2) AND u.is_active = true ORDER BY d.id ASC", + "query": "SELECT d.wireguard_pubkey pubkey, preshared_key, array[host(wnd.wireguard_ip)] \"allowed_ips!: Vec\" FROM wireguard_network_device wnd JOIN device d ON wnd.device_id = d.id JOIN \"user\" u ON d.user_id = u.id WHERE wireguard_network_id = $1 AND (is_authorized = true OR NOT $2) AND u.is_active = true ORDER BY d.id ASC", "describe": { "columns": [ { @@ -31,5 +31,5 @@ null ] }, - "hash": "b773bf99f9e3aafcade8bf57c6f9a49107df5e8d1452796c0bb4f2f657e2ec77" + "hash": "865ffb2d7ee9809eaaac5bb7ad64371f0d066d89e565f72beed5965785dccfd2" } diff --git a/.sqlx/query-0c5df6263cfa9e2bdf273c51993d40442a7ed65c986eef4e79e9d12aeb32edef.json b/.sqlx/query-8c772071e52ac1fb10931f9f698b7555093606011a31706e23dc891321d8836c.json similarity index 80% rename from .sqlx/query-0c5df6263cfa9e2bdf273c51993d40442a7ed65c986eef4e79e9d12aeb32edef.json rename to .sqlx/query-8c772071e52ac1fb10931f9f698b7555093606011a31706e23dc891321d8836c.json index 3116a4814..ad29454b2 100644 --- a/.sqlx/query-0c5df6263cfa9e2bdf273c51993d40442a7ed65c986eef4e79e9d12aeb32edef.json +++ b/.sqlx/query-8c772071e52ac1fb10931f9f698b7555093606011a31706e23dc891321d8836c.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"address\" \"address: _\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\" \"allowed_ips: _\",\"connected_at\",\"mfa_enabled\",\"keepalive_interval\",\"peer_disconnect_threshold\" FROM \"wireguard_network\"", + "query": "SELECT id, \"name\",\"address\" \"address: _\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\" \"allowed_ips: _\",\"connected_at\",\"mfa_enabled\",\"keepalive_interval\",\"peer_disconnect_threshold\" FROM \"wireguard_network\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -88,5 +88,5 @@ false ] }, - "hash": "0c5df6263cfa9e2bdf273c51993d40442a7ed65c986eef4e79e9d12aeb32edef" + "hash": "8c772071e52ac1fb10931f9f698b7555093606011a31706e23dc891321d8836c" } diff --git a/.sqlx/query-b2428c955f71b2b71ed436c99e913348520849a2e08096962604858abcaeae6c.json b/.sqlx/query-96b202cd0ffe9bf6d99cd0a02304b2b70ee32801870561190ba787a67068216b.json similarity index 88% rename from .sqlx/query-b2428c955f71b2b71ed436c99e913348520849a2e08096962604858abcaeae6c.json rename to .sqlx/query-96b202cd0ffe9bf6d99cd0a02304b2b70ee32801870561190ba787a67068216b.json index 66d6c1d76..190b99ceb 100644 --- a/.sqlx/query-b2428c955f71b2b71ed436c99e913348520849a2e08096962604858abcaeae6c.json +++ b/.sqlx/query-96b202cd0ffe9bf6d99cd0a02304b2b70ee32801870561190ba787a67068216b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, mfa_enabled, totp_enabled, email_mfa_enabled, mfa_method as \"mfa_method: MFAMethod\", password_hash, is_active, openid_sub FROM \"user\"", + "query": "SELECT id, mfa_enabled, totp_enabled, email_mfa_enabled, mfa_method \"mfa_method: MFAMethod\", password_hash, is_active, openid_sub FROM \"user\"", "describe": { "columns": [ { @@ -71,5 +71,5 @@ true ] }, - "hash": "b2428c955f71b2b71ed436c99e913348520849a2e08096962604858abcaeae6c" + "hash": "96b202cd0ffe9bf6d99cd0a02304b2b70ee32801870561190ba787a67068216b" } diff --git a/.sqlx/query-9c8efc3773f13460b73bd9d1a93839a587d727a9d036505203b4260603cf0ee6.json b/.sqlx/query-9c8efc3773f13460b73bd9d1a93839a587d727a9d036505203b4260603cf0ee6.json deleted file mode 100644 index 9c3826048..000000000 --- a/.sqlx/query-9c8efc3773f13460b73bd9d1a93839a587d727a9d036505203b4260603cf0ee6.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE \"settings\" SET \"openid_enabled\" = $2,\"wireguard_enabled\" = $3,\"webhooks_enabled\" = $4,\"worker_enabled\" = $5,\"challenge_template\" = $6,\"instance_name\" = $7,\"main_logo_url\" = $8,\"nav_logo_url\" = $9,\"smtp_server\" = $10,\"smtp_port\" = $11,\"smtp_encryption\" = $12,\"smtp_user\" = $13,\"smtp_password\" = $14,\"smtp_sender\" = $15,\"enrollment_vpn_step_optional\" = $16,\"enrollment_welcome_message\" = $17,\"enrollment_welcome_email\" = $18,\"enrollment_welcome_email_subject\" = $19,\"enrollment_use_welcome_message_as_email\" = $20,\"uuid\" = $21,\"ldap_url\" = $22,\"ldap_bind_username\" = $23,\"ldap_bind_password\" = $24,\"ldap_group_search_base\" = $25,\"ldap_user_search_base\" = $26,\"ldap_user_obj_class\" = $27,\"ldap_group_obj_class\" = $28,\"ldap_username_attr\" = $29,\"ldap_groupname_attr\" = $30,\"ldap_group_member_attr\" = $31,\"ldap_member_attr\" = $32,\"openid_create_account\" = $33,\"license\" = $34 WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Bool", - "Bool", - "Bool", - "Bool", - "Text", - "Text", - "Text", - "Text", - "Text", - "Int4", - { - "Custom": { - "name": "smtp_encryption", - "kind": { - "Enum": [ - "none", - "starttls", - "implicittls" - ] - } - } - }, - "Text", - "Text", - "Text", - "Bool", - "Text", - "Text", - "Text", - "Bool", - "Uuid", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Bool", - "Text" - ] - }, - "nullable": [] - }, - "hash": "9c8efc3773f13460b73bd9d1a93839a587d727a9d036505203b4260603cf0ee6" -} diff --git a/.sqlx/query-90eb99cdd712a78d180babe984f73364b510a1a46236ad4b5f80f4d2666b1567.json b/.sqlx/query-a09a0e653c8e7fdd3c8a7a5a9d6bf3e4385adeaff6b1b57f251055f8b47316a6.json similarity index 69% rename from .sqlx/query-90eb99cdd712a78d180babe984f73364b510a1a46236ad4b5f80f4d2666b1567.json rename to .sqlx/query-a09a0e653c8e7fdd3c8a7a5a9d6bf3e4385adeaff6b1b57f251055f8b47316a6.json index 59bdba31b..82ed536c9 100644 --- a/.sqlx/query-90eb99cdd712a78d180babe984f73364b510a1a46236ad4b5f80f4d2666b1567.json +++ b/.sqlx/query-a09a0e653c8e7fdd3c8a7a5a9d6bf3e4385adeaff6b1b57f251055f8b47316a6.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"oauth2client_id\" FROM \"oauth2authorizedapp\" WHERE id = $1", + "query": "SELECT id, \"user_id\",\"oauth2client_id\" FROM \"oauth2authorizedapp\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -30,5 +30,5 @@ false ] }, - "hash": "90eb99cdd712a78d180babe984f73364b510a1a46236ad4b5f80f4d2666b1567" + "hash": "a09a0e653c8e7fdd3c8a7a5a9d6bf3e4385adeaff6b1b57f251055f8b47316a6" } diff --git a/.sqlx/query-a2f21ca8cb0f17686bee01a4fcb14e1be4cddff56ad50f27b2e3d0f42188a6cd.json b/.sqlx/query-a2f21ca8cb0f17686bee01a4fcb14e1be4cddff56ad50f27b2e3d0f42188a6cd.json new file mode 100644 index 000000000..eec1c651b --- /dev/null +++ b/.sqlx/query-a2f21ca8cb0f17686bee01a4fcb14e1be4cddff56ad50f27b2e3d0f42188a6cd.json @@ -0,0 +1,57 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE \"settings\" SET openid_enabled = $1, wireguard_enabled = $2, webhooks_enabled = $3, worker_enabled = $4, challenge_template = $5, instance_name = $6, main_logo_url = $7, nav_logo_url = $8, smtp_server = $9, smtp_port = $10, smtp_encryption = $11, smtp_user = $12, smtp_password = $13, smtp_sender = $14, enrollment_vpn_step_optional = $15, enrollment_welcome_message = $16, enrollment_welcome_email = $17, enrollment_welcome_email_subject = $18, enrollment_use_welcome_message_as_email = $19, uuid = $20, ldap_url = $21, ldap_bind_username = $22, ldap_bind_password = $23, ldap_group_search_base = $24, ldap_user_search_base = $25, ldap_user_obj_class = $26, ldap_group_obj_class = $27, ldap_username_attr = $28, ldap_groupname_attr = $29, ldap_group_member_attr = $30, ldap_member_attr = $31, openid_create_account = $32, license = $33 WHERE id = 1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Bool", + "Bool", + "Bool", + "Bool", + "Text", + "Text", + "Text", + "Text", + "Text", + "Int4", + { + "Custom": { + "name": "smtp_encryption", + "kind": { + "Enum": [ + "none", + "starttls", + "implicittls" + ] + } + } + }, + "Text", + "Text", + "Text", + "Bool", + "Text", + "Text", + "Text", + "Bool", + "Uuid", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Bool", + "Text" + ] + }, + "nullable": [] + }, + "hash": "a2f21ca8cb0f17686bee01a4fcb14e1be4cddff56ad50f27b2e3d0f42188a6cd" +} diff --git a/.sqlx/query-295046c6b12e917661abebd70d047d23b9102682c25541a0cb51080689599405.json b/.sqlx/query-a74e2fda629b2b2916e3f034df84470cf7fb21a2f066559fad80a3d2396f9e21.json similarity index 79% rename from .sqlx/query-295046c6b12e917661abebd70d047d23b9102682c25541a0cb51080689599405.json rename to .sqlx/query-a74e2fda629b2b2916e3f034df84470cf7fb21a2f066559fad80a3d2396f9e21.json index bbbacd131..6bc42a2bc 100644 --- a/.sqlx/query-295046c6b12e917661abebd70d047d23b9102682c25541a0cb51080689599405.json +++ b/.sqlx/query-a74e2fda629b2b2916e3f034df84470cf7fb21a2f066559fad80a3d2396f9e21.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT device_id, wireguard_network_id, wireguard_ip as \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at FROM wireguard_network_device WHERE device_id = $1 AND wireguard_network_id = $2", + "query": "SELECT device_id, wireguard_network_id, wireguard_ip \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at FROM wireguard_network_device WHERE device_id = $1 AND wireguard_network_id = $2", "describe": { "columns": [ { @@ -49,5 +49,5 @@ true ] }, - "hash": "295046c6b12e917661abebd70d047d23b9102682c25541a0cb51080689599405" + "hash": "a74e2fda629b2b2916e3f034df84470cf7fb21a2f066559fad80a3d2396f9e21" } diff --git a/.sqlx/query-6ed1d008a99fa449325cb3d6d4b0231304b857b23460f0ca2f74e0d17f8dbc77.json b/.sqlx/query-a944946c95a75f4bc7d0a96c802886816f2aef26cc0f349426228f292c110bfd.json similarity index 74% rename from .sqlx/query-6ed1d008a99fa449325cb3d6d4b0231304b857b23460f0ca2f74e0d17f8dbc77.json rename to .sqlx/query-a944946c95a75f4bc7d0a96c802886816f2aef26cc0f349426228f292c110bfd.json index 55a127b3c..a2e2f887b 100644 --- a/.sqlx/query-6ed1d008a99fa449325cb3d6d4b0231304b857b23460f0ca2f74e0d17f8dbc77.json +++ b/.sqlx/query-a944946c95a75f4bc7d0a96c802886816f2aef26cc0f349426228f292c110bfd.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"serial\",\"user_id\" FROM \"yubikey\"", + "query": "SELECT id, \"name\",\"serial\",\"user_id\" FROM \"yubikey\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -34,5 +34,5 @@ false ] }, - "hash": "6ed1d008a99fa449325cb3d6d4b0231304b857b23460f0ca2f74e0d17f8dbc77" + "hash": "a944946c95a75f4bc7d0a96c802886816f2aef26cc0f349426228f292c110bfd" } diff --git a/.sqlx/query-33a1e2f1904757c775d389fa99d67916b7b220d6aa1fe8bb6690f85ed1cd5666.json b/.sqlx/query-ab42d3c8bd9969e10eb02c4eccf176198608932ae8b0238ef10afb2fb548f25b.json similarity index 71% rename from .sqlx/query-33a1e2f1904757c775d389fa99d67916b7b220d6aa1fe8bb6690f85ed1cd5666.json rename to .sqlx/query-ab42d3c8bd9969e10eb02c4eccf176198608932ae8b0238ef10afb2fb548f25b.json index 462ab0695..387bd4153 100644 --- a/.sqlx/query-33a1e2f1904757c775d389fa99d67916b7b220d6aa1fe8bb6690f85ed1cd5666.json +++ b/.sqlx/query-ab42d3c8bd9969e10eb02c4eccf176198608932ae8b0238ef10afb2fb548f25b.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created FROM device JOIN \"user\" ON device.user_id = \"user\".id WHERE \"user\".username = $1", + "query": "SELECT device.id, name, wireguard_pubkey, user_id, created FROM device JOIN \"user\" ON device.user_id = \"user\".id WHERE \"user\".username = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -42,5 +42,5 @@ false ] }, - "hash": "33a1e2f1904757c775d389fa99d67916b7b220d6aa1fe8bb6690f85ed1cd5666" + "hash": "ab42d3c8bd9969e10eb02c4eccf176198608932ae8b0238ef10afb2fb548f25b" } diff --git a/.sqlx/query-79b071befac3742d78ff5e928920448547ee257d17a082be2c1debb935ff86a1.json b/.sqlx/query-b0d21b63dc414e3738a85e9e5ed8a71cbb3019f86f5d69e186efba7f0fabd4d1.json similarity index 85% rename from .sqlx/query-79b071befac3742d78ff5e928920448547ee257d17a082be2c1debb935ff86a1.json rename to .sqlx/query-b0d21b63dc414e3738a85e9e5ed8a71cbb3019f86f5d69e186efba7f0fabd4d1.json index 1a445b94f..84f36c6df 100644 --- a/.sqlx/query-79b071befac3742d78ff5e928920448547ee257d17a082be2c1debb935ff86a1.json +++ b/.sqlx/query-b0d21b63dc414e3738a85e9e5ed8a71cbb3019f86f5d69e186efba7f0fabd4d1.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" WHERE username = $1", + "query": "SELECT id, username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" WHERE openid_sub = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -121,5 +121,5 @@ true ] }, - "hash": "79b071befac3742d78ff5e928920448547ee257d17a082be2c1debb935ff86a1" + "hash": "b0d21b63dc414e3738a85e9e5ed8a71cbb3019f86f5d69e186efba7f0fabd4d1" } diff --git a/.sqlx/query-afd7f44318c4c9d79211e2978ee5a2953ae5a36ad1fe509b84a9d71ff8c24f79.json b/.sqlx/query-b7d7474762519f378dcb1e9ebd244f0704634b387855fac359c7fd407eda4b44.json similarity index 83% rename from .sqlx/query-afd7f44318c4c9d79211e2978ee5a2953ae5a36ad1fe509b84a9d71ff8c24f79.json rename to .sqlx/query-b7d7474762519f378dcb1e9ebd244f0704634b387855fac359c7fd407eda4b44.json index 5066745f6..dce9b16b2 100644 --- a/.sqlx/query-afd7f44318c4c9d79211e2978ee5a2953ae5a36ad1fe509b84a9d71ff8c24f79.json +++ b/.sqlx/query-b7d7474762519f378dcb1e9ebd244f0704634b387855fac359c7fd407eda4b44.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT \"user\".id \"id?\", username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, totp_secret, email_mfa_enabled, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" JOIN group_user ON \"user\".id = group_user.user_id WHERE group_user.group_id = $1", + "query": "SELECT u.id, u.username, u.password_hash, u.last_name, u.first_name, u.email, u.phone, u.mfa_enabled, u.totp_enabled, u.email_mfa_enabled, u.totp_secret, u.email_mfa_secret, u.mfa_method \"mfa_method: _\", u.recovery_codes, u.is_active, u.openid_sub FROM \"user\" u JOIN \"device\" d ON u.id = d.user_id WHERE d.id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -50,13 +50,13 @@ }, { "ordinal": 9, - "name": "totp_secret", - "type_info": "Bytea" + "name": "email_mfa_enabled", + "type_info": "Bool" }, { "ordinal": 10, - "name": "email_mfa_enabled", - "type_info": "Bool" + "name": "totp_secret", + "type_info": "Bytea" }, { "ordinal": 11, @@ -112,14 +112,14 @@ true, false, false, - true, false, true, + true, false, false, false, true ] }, - "hash": "afd7f44318c4c9d79211e2978ee5a2953ae5a36ad1fe509b84a9d71ff8c24f79" + "hash": "b7d7474762519f378dcb1e9ebd244f0704634b387855fac359c7fd407eda4b44" } diff --git a/.sqlx/query-20dda55cfcf38db23d5553b65a9e4f48275d7b1d588869791a6741fa11be38a5.json b/.sqlx/query-b83158ecab3024ae6e63c2f59e36c94b531af794d9540d1585eb5512db787a38.json similarity index 73% rename from .sqlx/query-20dda55cfcf38db23d5553b65a9e4f48275d7b1d588869791a6741fa11be38a5.json rename to .sqlx/query-b83158ecab3024ae6e63c2f59e36c94b531af794d9540d1585eb5512db787a38.json index 4b6e6f131..b7f40eaee 100644 --- a/.sqlx/query-20dda55cfcf38db23d5553b65a9e4f48275d7b1d588869791a6741fa11be38a5.json +++ b/.sqlx/query-b83158ecab3024ae6e63c2f59e36c94b531af794d9540d1585eb5512db787a38.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", device_id \"device_id!\", collected_at \"collected_at!\", network \"network!\", endpoint, upload \"upload!\", download \"download!\", latest_handshake \"latest_handshake!\", allowed_ips FROM wireguard_peer_stats WHERE device_id = $1 AND network = $2 ORDER BY collected_at DESC LIMIT 1", + "query": "SELECT id, device_id \"device_id!\", collected_at \"collected_at!\", network \"network!\", endpoint, upload \"upload!\", download \"download!\", latest_handshake \"latest_handshake!\", allowed_ips FROM wireguard_peer_stats WHERE device_id = $1 AND network = $2 ORDER BY collected_at DESC LIMIT 1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -67,5 +67,5 @@ true ] }, - "hash": "20dda55cfcf38db23d5553b65a9e4f48275d7b1d588869791a6741fa11be38a5" + "hash": "b83158ecab3024ae6e63c2f59e36c94b531af794d9540d1585eb5512db787a38" } diff --git a/.sqlx/query-b90afee0fbb1a4590e0e9d032e34f24ab82b04015634f99ddac9284e47b10f8d.json b/.sqlx/query-b90afee0fbb1a4590e0e9d032e34f24ab82b04015634f99ddac9284e47b10f8d.json deleted file mode 100644 index 4949c3e33..000000000 --- a/.sqlx/query-b90afee0fbb1a4590e0e9d032e34f24ab82b04015634f99ddac9284e47b10f8d.json +++ /dev/null @@ -1,231 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"openid_enabled\",\"wireguard_enabled\",\"webhooks_enabled\",\"worker_enabled\",\"challenge_template\",\"instance_name\",\"main_logo_url\",\"nav_logo_url\",\"smtp_server\",\"smtp_port\",\"smtp_encryption\" \"smtp_encryption: _\",\"smtp_user\",\"smtp_password\" \"smtp_password?: SecretString\",\"smtp_sender\",\"enrollment_vpn_step_optional\",\"enrollment_welcome_message\",\"enrollment_welcome_email\",\"enrollment_welcome_email_subject\",\"enrollment_use_welcome_message_as_email\",\"uuid\",\"ldap_url\",\"ldap_bind_username\",\"ldap_bind_password\" \"ldap_bind_password?: SecretString\",\"ldap_group_search_base\",\"ldap_user_search_base\",\"ldap_user_obj_class\",\"ldap_group_obj_class\",\"ldap_username_attr\",\"ldap_groupname_attr\",\"ldap_group_member_attr\",\"ldap_member_attr\",\"openid_create_account\",\"license\" FROM \"settings\" WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id?", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "openid_enabled", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "wireguard_enabled", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "webhooks_enabled", - "type_info": "Bool" - }, - { - "ordinal": 4, - "name": "worker_enabled", - "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "challenge_template", - "type_info": "Text" - }, - { - "ordinal": 6, - "name": "instance_name", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "main_logo_url", - "type_info": "Text" - }, - { - "ordinal": 8, - "name": "nav_logo_url", - "type_info": "Text" - }, - { - "ordinal": 9, - "name": "smtp_server", - "type_info": "Text" - }, - { - "ordinal": 10, - "name": "smtp_port", - "type_info": "Int4" - }, - { - "ordinal": 11, - "name": "smtp_encryption: _", - "type_info": { - "Custom": { - "name": "smtp_encryption", - "kind": { - "Enum": [ - "none", - "starttls", - "implicittls" - ] - } - } - } - }, - { - "ordinal": 12, - "name": "smtp_user", - "type_info": "Text" - }, - { - "ordinal": 13, - "name": "smtp_password?: SecretString", - "type_info": "Text" - }, - { - "ordinal": 14, - "name": "smtp_sender", - "type_info": "Text" - }, - { - "ordinal": 15, - "name": "enrollment_vpn_step_optional", - "type_info": "Bool" - }, - { - "ordinal": 16, - "name": "enrollment_welcome_message", - "type_info": "Text" - }, - { - "ordinal": 17, - "name": "enrollment_welcome_email", - "type_info": "Text" - }, - { - "ordinal": 18, - "name": "enrollment_welcome_email_subject", - "type_info": "Text" - }, - { - "ordinal": 19, - "name": "enrollment_use_welcome_message_as_email", - "type_info": "Bool" - }, - { - "ordinal": 20, - "name": "uuid", - "type_info": "Uuid" - }, - { - "ordinal": 21, - "name": "ldap_url", - "type_info": "Text" - }, - { - "ordinal": 22, - "name": "ldap_bind_username", - "type_info": "Text" - }, - { - "ordinal": 23, - "name": "ldap_bind_password?: SecretString", - "type_info": "Text" - }, - { - "ordinal": 24, - "name": "ldap_group_search_base", - "type_info": "Text" - }, - { - "ordinal": 25, - "name": "ldap_user_search_base", - "type_info": "Text" - }, - { - "ordinal": 26, - "name": "ldap_user_obj_class", - "type_info": "Text" - }, - { - "ordinal": 27, - "name": "ldap_group_obj_class", - "type_info": "Text" - }, - { - "ordinal": 28, - "name": "ldap_username_attr", - "type_info": "Text" - }, - { - "ordinal": 29, - "name": "ldap_groupname_attr", - "type_info": "Text" - }, - { - "ordinal": 30, - "name": "ldap_group_member_attr", - "type_info": "Text" - }, - { - "ordinal": 31, - "name": "ldap_member_attr", - "type_info": "Text" - }, - { - "ordinal": 32, - "name": "openid_create_account", - "type_info": "Bool" - }, - { - "ordinal": 33, - "name": "license", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - true, - true, - false, - true, - true, - true, - false, - true, - true, - true, - false, - false, - true, - true, - true, - true, - true, - true, - true, - true, - true, - true, - true, - false, - true - ] - }, - "hash": "b90afee0fbb1a4590e0e9d032e34f24ab82b04015634f99ddac9284e47b10f8d" -} diff --git a/.sqlx/query-e1a3ec06d90dc8e09f00a26f1f3d272e338aa169785058f71db480cc511bc658.json b/.sqlx/query-b915edbe87546e273c000d9494442a5b65504dd01c4e7271cdd5ec6dcf2b4c36.json similarity index 78% rename from .sqlx/query-e1a3ec06d90dc8e09f00a26f1f3d272e338aa169785058f71db480cc511bc658.json rename to .sqlx/query-b915edbe87546e273c000d9494442a5b65504dd01c4e7271cdd5ec6dcf2b4c36.json index 9047e30d9..e77aaa5f9 100644 --- a/.sqlx/query-e1a3ec06d90dc8e09f00a26f1f3d272e338aa169785058f71db480cc511bc658.json +++ b/.sqlx/query-b915edbe87546e273c000d9494442a5b65504dd01c4e7271cdd5ec6dcf2b4c36.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM \"yubikey\" WHERE user_id = $1", + "query": "SELECT id, name, serial, user_id FROM \"yubikey\" WHERE user_id = $1", "describe": { "columns": [ { @@ -10,12 +10,12 @@ }, { "ordinal": 1, - "name": "serial", + "name": "name", "type_info": "Text" }, { "ordinal": 2, - "name": "name", + "name": "serial", "type_info": "Text" }, { @@ -36,5 +36,5 @@ false ] }, - "hash": "e1a3ec06d90dc8e09f00a26f1f3d272e338aa169785058f71db480cc511bc658" + "hash": "b915edbe87546e273c000d9494442a5b65504dd01c4e7271cdd5ec6dcf2b4c36" } diff --git a/.sqlx/query-b33fa60772e858280c021b3774d01273453d13af4321cae2d244748ca4254b29.json b/.sqlx/query-ba65214847bd66c0b57d1d2eea65d862c062b3224e61598247ec8c927216caf3.json similarity index 67% rename from .sqlx/query-b33fa60772e858280c021b3774d01273453d13af4321cae2d244748ca4254b29.json rename to .sqlx/query-ba65214847bd66c0b57d1d2eea65d862c062b3224e61598247ec8c927216caf3.json index 89e360d76..0f206c3b5 100644 --- a/.sqlx/query-b33fa60772e858280c021b3774d01273453d13af4321cae2d244748ca4254b29.json +++ b/.sqlx/query-ba65214847bd66c0b57d1d2eea65d862c062b3224e61598247ec8c927216caf3.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", name FROM \"group\" WHERE name = $1", + "query": "SELECT id, name FROM \"group\" WHERE name = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -24,5 +24,5 @@ false ] }, - "hash": "b33fa60772e858280c021b3774d01273453d13af4321cae2d244748ca4254b29" + "hash": "ba65214847bd66c0b57d1d2eea65d862c062b3224e61598247ec8c927216caf3" } diff --git a/.sqlx/query-965e1d9ae54914a8a34a651f489d218f060fc9608c6d233b6f6a4137cf199964.json b/.sqlx/query-bc5b07156c76b4d68f12dbf0b3a4d9108883708bcca7657a08f03abc31047026.json similarity index 83% rename from .sqlx/query-965e1d9ae54914a8a34a651f489d218f060fc9608c6d233b6f6a4137cf199964.json rename to .sqlx/query-bc5b07156c76b4d68f12dbf0b3a4d9108883708bcca7657a08f03abc31047026.json index 17b428d94..4b80445b1 100644 --- a/.sqlx/query-965e1d9ae54914a8a34a651f489d218f060fc9608c6d233b6f6a4137cf199964.json +++ b/.sqlx/query-bc5b07156c76b4d68f12dbf0b3a4d9108883708bcca7657a08f03abc31047026.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT u.id \"id?\", u.username, u.password_hash, u.last_name, u.first_name, u.email, u.phone, u.mfa_enabled, u.totp_enabled, u.email_mfa_enabled, u.totp_secret, u.email_mfa_secret, u.mfa_method \"mfa_method: _\", u.recovery_codes, u.is_active, u.openid_sub FROM \"user\" as u JOIN \"device\" as d ON u.id = d.user_id WHERE d.id = $1", + "query": "SELECT \"user\".id, username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, totp_secret, email_mfa_enabled, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" JOIN group_user ON \"user\".id = group_user.user_id WHERE group_user.group_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -50,13 +50,13 @@ }, { "ordinal": 9, - "name": "email_mfa_enabled", - "type_info": "Bool" + "name": "totp_secret", + "type_info": "Bytea" }, { "ordinal": 10, - "name": "totp_secret", - "type_info": "Bytea" + "name": "email_mfa_enabled", + "type_info": "Bool" }, { "ordinal": 11, @@ -112,8 +112,8 @@ true, false, false, - false, true, + false, true, false, false, @@ -121,5 +121,5 @@ true ] }, - "hash": "965e1d9ae54914a8a34a651f489d218f060fc9608c6d233b6f6a4137cf199964" + "hash": "bc5b07156c76b4d68f12dbf0b3a4d9108883708bcca7657a08f03abc31047026" } diff --git a/.sqlx/query-e77aec5f5c3e42b65ded303f5360c22bb3f882ae1d82b2f72ea5ff933a407d91.json b/.sqlx/query-bfeeea313651b1ca70eff19f6743904e2a39a5277f079c17ba90f6bdc20c3087.json similarity index 74% rename from .sqlx/query-e77aec5f5c3e42b65ded303f5360c22bb3f882ae1d82b2f72ea5ff933a407d91.json rename to .sqlx/query-bfeeea313651b1ca70eff19f6743904e2a39a5277f079c17ba90f6bdc20c3087.json index d7eac8f7b..0344215a9 100644 --- a/.sqlx/query-e77aec5f5c3e42b65ded303f5360c22bb3f882ae1d82b2f72ea5ff933a407d91.json +++ b/.sqlx/query-bfeeea313651b1ca70eff19f6743904e2a39a5277f079c17ba90f6bdc20c3087.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"serial\",\"user_id\" FROM \"yubikey\" WHERE id = $1", + "query": "SELECT id, \"name\",\"serial\",\"user_id\" FROM \"yubikey\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -36,5 +36,5 @@ false ] }, - "hash": "e77aec5f5c3e42b65ded303f5360c22bb3f882ae1d82b2f72ea5ff933a407d91" + "hash": "bfeeea313651b1ca70eff19f6743904e2a39a5277f079c17ba90f6bdc20c3087" } diff --git a/.sqlx/query-e626ef3ad2f23b3981f911da0c81d452f3b0e9c64f20488bf7f5d6c2fdd38a16.json b/.sqlx/query-c0050b334d9efcceb0cea4e9a04dda908d74c05e9b08389215dbcababde5d7fb.json similarity index 80% rename from .sqlx/query-e626ef3ad2f23b3981f911da0c81d452f3b0e9c64f20488bf7f5d6c2fdd38a16.json rename to .sqlx/query-c0050b334d9efcceb0cea4e9a04dda908d74c05e9b08389215dbcababde5d7fb.json index 3d8c67150..1b65d2d31 100644 --- a/.sqlx/query-e626ef3ad2f23b3981f911da0c81d452f3b0e9c64f20488bf7f5d6c2fdd38a16.json +++ b/.sqlx/query-c0050b334d9efcceb0cea4e9a04dda908d74c05e9b08389215dbcababde5d7fb.json @@ -1,65 +1,60 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"openid_enabled\",\"wireguard_enabled\",\"webhooks_enabled\",\"worker_enabled\",\"challenge_template\",\"instance_name\",\"main_logo_url\",\"nav_logo_url\",\"smtp_server\",\"smtp_port\",\"smtp_encryption\" \"smtp_encryption: _\",\"smtp_user\",\"smtp_password\" \"smtp_password?: SecretString\",\"smtp_sender\",\"enrollment_vpn_step_optional\",\"enrollment_welcome_message\",\"enrollment_welcome_email\",\"enrollment_welcome_email_subject\",\"enrollment_use_welcome_message_as_email\",\"uuid\",\"ldap_url\",\"ldap_bind_username\",\"ldap_bind_password\" \"ldap_bind_password?: SecretString\",\"ldap_group_search_base\",\"ldap_user_search_base\",\"ldap_user_obj_class\",\"ldap_group_obj_class\",\"ldap_username_attr\",\"ldap_groupname_attr\",\"ldap_group_member_attr\",\"ldap_member_attr\",\"openid_create_account\",\"license\" FROM \"settings\"", + "query": "SELECT openid_enabled, wireguard_enabled, webhooks_enabled, worker_enabled, challenge_template, instance_name, main_logo_url, nav_logo_url, smtp_server, smtp_port, smtp_encryption \"smtp_encryption: _\", smtp_user, smtp_password \"smtp_password?: SecretString\", smtp_sender, enrollment_vpn_step_optional, enrollment_welcome_message, enrollment_welcome_email, enrollment_welcome_email_subject, enrollment_use_welcome_message_as_email, uuid, ldap_url, ldap_bind_username, ldap_bind_password \"ldap_bind_password?: SecretString\", ldap_group_search_base, ldap_user_search_base, ldap_user_obj_class, ldap_group_obj_class, ldap_username_attr, ldap_groupname_attr, ldap_group_member_attr, ldap_member_attr, openid_create_account, license FROM \"settings\" WHERE id = 1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", - "type_info": "Int8" - }, - { - "ordinal": 1, "name": "openid_enabled", "type_info": "Bool" }, { - "ordinal": 2, + "ordinal": 1, "name": "wireguard_enabled", "type_info": "Bool" }, { - "ordinal": 3, + "ordinal": 2, "name": "webhooks_enabled", "type_info": "Bool" }, { - "ordinal": 4, + "ordinal": 3, "name": "worker_enabled", "type_info": "Bool" }, { - "ordinal": 5, + "ordinal": 4, "name": "challenge_template", "type_info": "Text" }, { - "ordinal": 6, + "ordinal": 5, "name": "instance_name", "type_info": "Text" }, { - "ordinal": 7, + "ordinal": 6, "name": "main_logo_url", "type_info": "Text" }, { - "ordinal": 8, + "ordinal": 7, "name": "nav_logo_url", "type_info": "Text" }, { - "ordinal": 9, + "ordinal": 8, "name": "smtp_server", "type_info": "Text" }, { - "ordinal": 10, + "ordinal": 9, "name": "smtp_port", "type_info": "Int4" }, { - "ordinal": 11, + "ordinal": 10, "name": "smtp_encryption: _", "type_info": { "Custom": { @@ -75,112 +70,112 @@ } }, { - "ordinal": 12, + "ordinal": 11, "name": "smtp_user", "type_info": "Text" }, { - "ordinal": 13, + "ordinal": 12, "name": "smtp_password?: SecretString", "type_info": "Text" }, { - "ordinal": 14, + "ordinal": 13, "name": "smtp_sender", "type_info": "Text" }, { - "ordinal": 15, + "ordinal": 14, "name": "enrollment_vpn_step_optional", "type_info": "Bool" }, { - "ordinal": 16, + "ordinal": 15, "name": "enrollment_welcome_message", "type_info": "Text" }, { - "ordinal": 17, + "ordinal": 16, "name": "enrollment_welcome_email", "type_info": "Text" }, { - "ordinal": 18, + "ordinal": 17, "name": "enrollment_welcome_email_subject", "type_info": "Text" }, { - "ordinal": 19, + "ordinal": 18, "name": "enrollment_use_welcome_message_as_email", "type_info": "Bool" }, { - "ordinal": 20, + "ordinal": 19, "name": "uuid", "type_info": "Uuid" }, { - "ordinal": 21, + "ordinal": 20, "name": "ldap_url", "type_info": "Text" }, { - "ordinal": 22, + "ordinal": 21, "name": "ldap_bind_username", "type_info": "Text" }, { - "ordinal": 23, + "ordinal": 22, "name": "ldap_bind_password?: SecretString", "type_info": "Text" }, { - "ordinal": 24, + "ordinal": 23, "name": "ldap_group_search_base", "type_info": "Text" }, { - "ordinal": 25, + "ordinal": 24, "name": "ldap_user_search_base", "type_info": "Text" }, { - "ordinal": 26, + "ordinal": 25, "name": "ldap_user_obj_class", "type_info": "Text" }, { - "ordinal": 27, + "ordinal": 26, "name": "ldap_group_obj_class", "type_info": "Text" }, { - "ordinal": 28, + "ordinal": 27, "name": "ldap_username_attr", "type_info": "Text" }, { - "ordinal": 29, + "ordinal": 28, "name": "ldap_groupname_attr", "type_info": "Text" }, { - "ordinal": 30, + "ordinal": 29, "name": "ldap_group_member_attr", "type_info": "Text" }, { - "ordinal": 31, + "ordinal": 30, "name": "ldap_member_attr", "type_info": "Text" }, { - "ordinal": 32, + "ordinal": 31, "name": "openid_create_account", "type_info": "Bool" }, { - "ordinal": 33, + "ordinal": 32, "name": "license", "type_info": "Text" } @@ -197,7 +192,6 @@ false, false, false, - false, true, true, false, @@ -225,5 +219,5 @@ true ] }, - "hash": "e626ef3ad2f23b3981f911da0c81d452f3b0e9c64f20488bf7f5d6c2fdd38a16" + "hash": "c0050b334d9efcceb0cea4e9a04dda908d74c05e9b08389215dbcababde5d7fb" } diff --git a/.sqlx/query-9b443a223ba94d9a696a0d8ebe188e50e49f404f8b0057a3015c067c5f57c1bf.json b/.sqlx/query-c4aae1903dfd0c63be33d58794f805c685a6d61c231dcbb4ce92fb3333a41a0d.json similarity index 79% rename from .sqlx/query-9b443a223ba94d9a696a0d8ebe188e50e49f404f8b0057a3015c067c5f57c1bf.json rename to .sqlx/query-c4aae1903dfd0c63be33d58794f805c685a6d61c231dcbb4ce92fb3333a41a0d.json index e45b1c905..9a93f3484 100644 --- a/.sqlx/query-9b443a223ba94d9a696a0d8ebe188e50e49f404f8b0057a3015c067c5f57c1bf.json +++ b/.sqlx/query-c4aae1903dfd0c63be33d58794f805c685a6d61c231dcbb4ce92fb3333a41a0d.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\",\"use_for_mfa\" FROM \"wallet\" WHERE id = $1", + "query": "SELECT id, \"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\",\"use_for_mfa\" FROM \"wallet\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -72,5 +72,5 @@ false ] }, - "hash": "9b443a223ba94d9a696a0d8ebe188e50e49f404f8b0057a3015c067c5f57c1bf" + "hash": "c4aae1903dfd0c63be33d58794f805c685a6d61c231dcbb4ce92fb3333a41a0d" } diff --git a/.sqlx/query-87ac2de4f3fc7801f3b9dcf51992f107c53f264016865af4379196f5e72acd4c.json b/.sqlx/query-ca74174177efd38a84835e809b91a7b39b9389ae01437b7d9405ffa074388279.json similarity index 75% rename from .sqlx/query-87ac2de4f3fc7801f3b9dcf51992f107c53f264016865af4379196f5e72acd4c.json rename to .sqlx/query-ca74174177efd38a84835e809b91a7b39b9389ae01437b7d9405ffa074388279.json index 11ad8e07f..bc0e41c84 100644 --- a/.sqlx/query-87ac2de4f3fc7801f3b9dcf51992f107c53f264016865af4379196f5e72acd4c.json +++ b/.sqlx/query-ca74174177efd38a84835e809b91a7b39b9389ae01437b7d9405ffa074388279.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"base_url\",\"client_id\",\"client_secret\" FROM \"openidprovider\"", + "query": "SELECT id, \"name\",\"base_url\",\"client_id\",\"client_secret\" FROM \"openidprovider\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -40,5 +40,5 @@ false ] }, - "hash": "87ac2de4f3fc7801f3b9dcf51992f107c53f264016865af4379196f5e72acd4c" + "hash": "ca74174177efd38a84835e809b91a7b39b9389ae01437b7d9405ffa074388279" } diff --git a/.sqlx/query-52b93ff07493589383745923dbe340b5adde936a4e4474d530b70002eb92063e.json b/.sqlx/query-cb09994909c0e17b4c1740c11878c1649c552173dbc94b1f0fadca7af4088a06.json similarity index 79% rename from .sqlx/query-52b93ff07493589383745923dbe340b5adde936a4e4474d530b70002eb92063e.json rename to .sqlx/query-cb09994909c0e17b4c1740c11878c1649c552173dbc94b1f0fadca7af4088a06.json index 50c5d3c5f..e3a06dea5 100644 --- a/.sqlx/query-52b93ff07493589383745923dbe340b5adde936a4e4474d530b70002eb92063e.json +++ b/.sqlx/query-cb09994909c0e17b4c1740c11878c1649c552173dbc94b1f0fadca7af4088a06.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"url\",\"description\",\"token\",\"enabled\",\"on_user_created\",\"on_user_deleted\",\"on_user_modified\",\"on_hwkey_provision\" FROM \"webhook\"", + "query": "SELECT id, \"url\",\"description\",\"token\",\"enabled\",\"on_user_created\",\"on_user_deleted\",\"on_user_modified\",\"on_hwkey_provision\" FROM \"webhook\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -64,5 +64,5 @@ false ] }, - "hash": "52b93ff07493589383745923dbe340b5adde936a4e4474d530b70002eb92063e" + "hash": "cb09994909c0e17b4c1740c11878c1649c552173dbc94b1f0fadca7af4088a06" } diff --git a/.sqlx/query-ccd62ea7526078c9db47812e7f6a5e7829eae217ad9f7f3b0b03aa02f8808dc2.json b/.sqlx/query-ccd62ea7526078c9db47812e7f6a5e7829eae217ad9f7f3b0b03aa02f8808dc2.json new file mode 100644 index 000000000..787a75d7d --- /dev/null +++ b/.sqlx/query-ccd62ea7526078c9db47812e7f6a5e7829eae217ad9f7f3b0b03aa02f8808dc2.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE \"enterprisesettings\" SET admin_device_management = $1, disable_all_traffic = $2, only_client_activation = $3 WHERE id = 1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Bool", + "Bool", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "ccd62ea7526078c9db47812e7f6a5e7829eae217ad9f7f3b0b03aa02f8808dc2" +} diff --git a/.sqlx/query-1a29720d6c1efc4460c6bc10b96ab42b6daf888de145d62fab1739ebff759b9b.json b/.sqlx/query-d5167701c8ca8437fa35810062288a2dc1550d4d4e750d846ddb6090f65566ac.json similarity index 77% rename from .sqlx/query-1a29720d6c1efc4460c6bc10b96ab42b6daf888de145d62fab1739ebff759b9b.json rename to .sqlx/query-d5167701c8ca8437fa35810062288a2dc1550d4d4e750d846ddb6090f65566ac.json index ed787b82b..7107a8b59 100644 --- a/.sqlx/query-1a29720d6c1efc4460c6bc10b96ab42b6daf888de145d62fab1739ebff759b9b.json +++ b/.sqlx/query-d5167701c8ca8437fa35810062288a2dc1550d4d4e750d846ddb6090f65566ac.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT k.id as key_id, k.name, k.key_type \"key_type: AuthenticationKeyType\", k.key, k.user_id, k.yubikey_id, y.name \"yubikey_name: Option\", y.serial \"serial: Option\" FROM \"authentication_key\" k LEFT JOIN \"yubikey\" y ON k.yubikey_id = y.id WHERE k.user_id = $1", + "query": "SELECT k.id key_id, k.name, k.key_type \"key_type: AuthenticationKeyType\", k.key, k.user_id, k.yubikey_id, y.name \"yubikey_name: Option\", y.serial \"serial: Option\" FROM \"authentication_key\" k LEFT JOIN \"yubikey\" y ON k.yubikey_id = y.id WHERE k.user_id = $1", "describe": { "columns": [ { @@ -70,5 +70,5 @@ false ] }, - "hash": "1a29720d6c1efc4460c6bc10b96ab42b6daf888de145d62fab1739ebff759b9b" + "hash": "d5167701c8ca8437fa35810062288a2dc1550d4d4e750d846ddb6090f65566ac" } diff --git a/.sqlx/query-b12208760e0fc61c766c7c2d037d4e20c54f7bb6f612156de08f5ba26bd0b7df.json b/.sqlx/query-dafe0c3d80ed8e09771cf910d6a7696bb16daaece311438358321c8e8ea3b65f.json similarity index 75% rename from .sqlx/query-b12208760e0fc61c766c7c2d037d4e20c54f7bb6f612156de08f5ba26bd0b7df.json rename to .sqlx/query-dafe0c3d80ed8e09771cf910d6a7696bb16daaece311438358321c8e8ea3b65f.json index 0d1ced94e..461929642 100644 --- a/.sqlx/query-b12208760e0fc61c766c7c2d037d4e20c54f7bb6f612156de08f5ba26bd0b7df.json +++ b/.sqlx/query-dafe0c3d80ed8e09771cf910d6a7696bb16daaece311438358321c8e8ea3b65f.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"base_url\",\"client_id\",\"client_secret\" FROM \"openidprovider\" WHERE id = $1", + "query": "SELECT id, \"name\",\"base_url\",\"client_id\",\"client_secret\" FROM \"openidprovider\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -42,5 +42,5 @@ false ] }, - "hash": "b12208760e0fc61c766c7c2d037d4e20c54f7bb6f612156de08f5ba26bd0b7df" + "hash": "dafe0c3d80ed8e09771cf910d6a7696bb16daaece311438358321c8e8ea3b65f" } diff --git a/.sqlx/query-6b659b309306098a1f8b5b5592facba9cd54f93f5239fbc62a858bb91f502472.json b/.sqlx/query-dc4bc55d768a79bbb01f61b124578fbfe4b2c10709d6d176d4ac622854318d8c.json similarity index 76% rename from .sqlx/query-6b659b309306098a1f8b5b5592facba9cd54f93f5239fbc62a858bb91f502472.json rename to .sqlx/query-dc4bc55d768a79bbb01f61b124578fbfe4b2c10709d6d176d4ac622854318d8c.json index 0bbbc964f..1b2cf0aaf 100644 --- a/.sqlx/query-6b659b309306098a1f8b5b5592facba9cd54f93f5239fbc62a858bb91f502472.json +++ b/.sqlx/query-dc4bc55d768a79bbb01f61b124578fbfe4b2c10709d6d176d4ac622854318d8c.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"client_id\",\"client_secret\",\"redirect_uri\" \"redirect_uri: _\",\"scope\" \"scope: _\",\"name\",\"enabled\" FROM \"oauth2client\"", + "query": "SELECT id, \"client_id\",\"client_secret\",\"redirect_uri\" \"redirect_uri: _\",\"scope\" \"scope: _\",\"name\",\"enabled\" FROM \"oauth2client\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -52,5 +52,5 @@ false ] }, - "hash": "6b659b309306098a1f8b5b5592facba9cd54f93f5239fbc62a858bb91f502472" + "hash": "dc4bc55d768a79bbb01f61b124578fbfe4b2c10709d6d176d4ac622854318d8c" } diff --git a/.sqlx/query-40464c39168b11d05247d01336343bdbf791077e0c081b16e3d1c2d5b3cc074b.json b/.sqlx/query-dea2f3d8b9508ef1df84e204816a9cdc53103547d3d273803313bc091c72c323.json similarity index 77% rename from .sqlx/query-40464c39168b11d05247d01336343bdbf791077e0c081b16e3d1c2d5b3cc074b.json rename to .sqlx/query-dea2f3d8b9508ef1df84e204816a9cdc53103547d3d273803313bc091c72c323.json index 6ae98c3f0..83298bd6b 100644 --- a/.sqlx/query-40464c39168b11d05247d01336343bdbf791077e0c081b16e3d1c2d5b3cc074b.json +++ b/.sqlx/query-dea2f3d8b9508ef1df84e204816a9cdc53103547d3d273803313bc091c72c323.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", name, base_url, client_id, client_secret FROM openidprovider", + "query": "SELECT id, name, base_url, client_id, client_secret FROM openidprovider", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -40,5 +40,5 @@ false ] }, - "hash": "40464c39168b11d05247d01336343bdbf791077e0c081b16e3d1c2d5b3cc074b" + "hash": "dea2f3d8b9508ef1df84e204816a9cdc53103547d3d273803313bc091c72c323" } diff --git a/.sqlx/query-e358d2799f31f9ed8374fa480bf5d431370a39d38eda251ab9462e6bf7eba642.json b/.sqlx/query-e358d2799f31f9ed8374fa480bf5d431370a39d38eda251ab9462e6bf7eba642.json new file mode 100644 index 000000000..820c4c4a5 --- /dev/null +++ b/.sqlx/query-e358d2799f31f9ed8374fa480bf5d431370a39d38eda251ab9462e6bf7eba642.json @@ -0,0 +1,32 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT g.name name, COALESCE(ARRAY_AGG(DISTINCT u.username) FILTER (WHERE u.username IS NOT NULL), '{}') \"members!\", COALESCE(ARRAY_AGG(DISTINCT wn.name) FILTER (WHERE wn.name IS NOT NULL), '{}') \"vpn_locations!\" FROM \"group\" g LEFT JOIN \"group_user\" gu ON gu.group_id = g.id LEFT JOIN \"user\" u ON u.id = gu.user_id LEFT JOIN \"wireguard_network_allowed_group\" wnag ON wnag.group_id = g.id LEFT JOIN \"wireguard_network\" wn ON wn.id = wnag.network_id GROUP BY g.name", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "members!", + "type_info": "TextArray" + }, + { + "ordinal": 2, + "name": "vpn_locations!", + "type_info": "TextArray" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + null, + null + ] + }, + "hash": "e358d2799f31f9ed8374fa480bf5d431370a39d38eda251ab9462e6bf7eba642" +} diff --git a/.sqlx/query-9f7a75ac4a3c5b767746a836e409e3e3f02a029cf9456bed8c5291f92f1d5f8a.json b/.sqlx/query-e48af3ee182c0f3829a2248faf705626b1ad29755fef9758f88353e786a1b2ed.json similarity index 80% rename from .sqlx/query-9f7a75ac4a3c5b767746a836e409e3e3f02a029cf9456bed8c5291f92f1d5f8a.json rename to .sqlx/query-e48af3ee182c0f3829a2248faf705626b1ad29755fef9758f88353e786a1b2ed.json index 2c5155873..d72c99d11 100644 --- a/.sqlx/query-9f7a75ac4a3c5b767746a836e409e3e3f02a029cf9456bed8c5291f92f1d5f8a.json +++ b/.sqlx/query-e48af3ee182c0f3829a2248faf705626b1ad29755fef9758f88353e786a1b2ed.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, address, name, chain_id, challenge_message, challenge_signature, creation_timestamp, validation_timestamp, use_for_mfa FROM wallet WHERE user_id = $1 AND address = $2", + "query": "SELECT id, user_id, address, name, chain_id, challenge_message, challenge_signature, creation_timestamp, validation_timestamp, use_for_mfa FROM wallet WHERE user_id = $1 AND address = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -73,5 +73,5 @@ false ] }, - "hash": "9f7a75ac4a3c5b767746a836e409e3e3f02a029cf9456bed8c5291f92f1d5f8a" + "hash": "e48af3ee182c0f3829a2248faf705626b1ad29755fef9758f88353e786a1b2ed" } diff --git a/.sqlx/query-7fa302170061ee58a46152f581f13dbae26b7dd6ba59169cb5a18d443d039c14.json b/.sqlx/query-e52ba8f1cdce5ef217602017fe59ee0038f6b5239ff6cb7e9d3acccd6697de00.json similarity index 79% rename from .sqlx/query-7fa302170061ee58a46152f581f13dbae26b7dd6ba59169cb5a18d443d039c14.json rename to .sqlx/query-e52ba8f1cdce5ef217602017fe59ee0038f6b5239ff6cb7e9d3acccd6697de00.json index e11dbc205..16a9a4abe 100644 --- a/.sqlx/query-7fa302170061ee58a46152f581f13dbae26b7dd6ba59169cb5a18d443d039c14.json +++ b/.sqlx/query-e52ba8f1cdce5ef217602017fe59ee0038f6b5239ff6cb7e9d3acccd6697de00.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"url\",\"description\",\"token\",\"enabled\",\"on_user_created\",\"on_user_deleted\",\"on_user_modified\",\"on_hwkey_provision\" FROM \"webhook\" WHERE id = $1", + "query": "SELECT id, \"url\",\"description\",\"token\",\"enabled\",\"on_user_created\",\"on_user_deleted\",\"on_user_modified\",\"on_hwkey_provision\" FROM \"webhook\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -66,5 +66,5 @@ false ] }, - "hash": "7fa302170061ee58a46152f581f13dbae26b7dd6ba59169cb5a18d443d039c14" + "hash": "e52ba8f1cdce5ef217602017fe59ee0038f6b5239ff6cb7e9d3acccd6697de00" } diff --git a/.sqlx/query-06f847d99d452dafd10f4a6bec309c330d76eb5d0a34012bd75395b13e3ff659.json b/.sqlx/query-e6537b70e7f27eb6103a7715d520ec1bb8491908a059422e751592b7da96028f.json similarity index 76% rename from .sqlx/query-06f847d99d452dafd10f4a6bec309c330d76eb5d0a34012bd75395b13e3ff659.json rename to .sqlx/query-e6537b70e7f27eb6103a7715d520ec1bb8491908a059422e751592b7da96028f.json index c291b5a4e..c531cd830 100644 --- a/.sqlx/query-06f847d99d452dafd10f4a6bec309c330d76eb5d0a34012bd75395b13e3ff659.json +++ b/.sqlx/query-e6537b70e7f27eb6103a7715d520ec1bb8491908a059422e751592b7da96028f.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"wireguard_pubkey\",\"user_id\",\"created\" FROM \"device\"", + "query": "SELECT id, \"name\",\"wireguard_pubkey\",\"user_id\",\"created\" FROM \"device\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -40,5 +40,5 @@ false ] }, - "hash": "06f847d99d452dafd10f4a6bec309c330d76eb5d0a34012bd75395b13e3ff659" + "hash": "e6537b70e7f27eb6103a7715d520ec1bb8491908a059422e751592b7da96028f" } diff --git a/.sqlx/query-3d5d8b6f640435a1986561c73cd809038cdddfb44133b97b5ba7392573239539.json b/.sqlx/query-e699a4be7c892b6c3fa44c41970381f11072cba02dd59f7e5fb7f4925e90692b.json similarity index 52% rename from .sqlx/query-3d5d8b6f640435a1986561c73cd809038cdddfb44133b97b5ba7392573239539.json rename to .sqlx/query-e699a4be7c892b6c3fa44c41970381f11072cba02dd59f7e5fb7f4925e90692b.json index 3dd50e700..f94df7370 100644 --- a/.sqlx/query-3d5d8b6f640435a1986561c73cd809038cdddfb44133b97b5ba7392573239539.json +++ b/.sqlx/query-e699a4be7c892b6c3fa44c41970381f11072cba02dd59f7e5fb7f4925e90692b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT COALESCE(COUNT(DISTINCT(u.id)), 0) as \"active_users!\", COALESCE(COUNT(DISTINCT(s.device_id)), 0) as \"active_devices!\" FROM \"user\" u JOIN device d ON d.user_id = u.id JOIN wireguard_peer_stats s ON s.device_id = d.id WHERE latest_handshake >= $1 AND s.network = $2", + "query": "SELECT COALESCE(COUNT(DISTINCT(u.id)), 0) \"active_users!\", COALESCE(COUNT(DISTINCT(s.device_id)), 0) \"active_devices!\" FROM \"user\" u JOIN device d ON d.user_id = u.id JOIN wireguard_peer_stats s ON s.device_id = d.id WHERE latest_handshake >= $1 AND s.network = $2", "describe": { "columns": [ { @@ -25,5 +25,5 @@ null ] }, - "hash": "3d5d8b6f640435a1986561c73cd809038cdddfb44133b97b5ba7392573239539" + "hash": "e699a4be7c892b6c3fa44c41970381f11072cba02dd59f7e5fb7f4925e90692b" } diff --git a/.sqlx/query-eaf6524ec567e823b3d3dbf90ac1f18ea4a3d84e3d79bde1991c529c803fd2bb.json b/.sqlx/query-eaf6524ec567e823b3d3dbf90ac1f18ea4a3d84e3d79bde1991c529c803fd2bb.json deleted file mode 100644 index 03fcdd4ad..000000000 --- a/.sqlx/query-eaf6524ec567e823b3d3dbf90ac1f18ea4a3d84e3d79bde1991c529c803fd2bb.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT \"user\".id \"id?\", username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, totp_secret, email_mfa_enabled, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\"\n INNER JOIN \"group_user\" ON \"user\".id = \"group_user\".user_id\n INNER JOIN \"group\" ON \"group_user\".group_id = \"group\".id\n WHERE \"group\".name = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id?", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "password_hash", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "last_name", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "first_name", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 6, - "name": "phone", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "mfa_enabled", - "type_info": "Bool" - }, - { - "ordinal": 8, - "name": "totp_enabled", - "type_info": "Bool" - }, - { - "ordinal": 9, - "name": "totp_secret", - "type_info": "Bytea" - }, - { - "ordinal": 10, - "name": "email_mfa_enabled", - "type_info": "Bool" - }, - { - "ordinal": 11, - "name": "email_mfa_secret", - "type_info": "Bytea" - }, - { - "ordinal": 12, - "name": "mfa_method: _", - "type_info": { - "Custom": { - "name": "mfa_method", - "kind": { - "Enum": [ - "none", - "one_time_password", - "webauthn", - "web3", - "email" - ] - } - } - } - }, - { - "ordinal": 13, - "name": "recovery_codes", - "type_info": "TextArray" - }, - { - "ordinal": 14, - "name": "is_active", - "type_info": "Bool" - }, - { - "ordinal": 15, - "name": "openid_sub", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - true, - false, - false, - false, - true, - false, - false, - true, - false, - true, - false, - false, - false, - true - ] - }, - "hash": "eaf6524ec567e823b3d3dbf90ac1f18ea4a3d84e3d79bde1991c529c803fd2bb" -} diff --git a/.sqlx/query-eb753d506ce15b17b7aca1f5dd3dd03b382a667f0f0bc506e0cbc45118eea293.json b/.sqlx/query-eb753d506ce15b17b7aca1f5dd3dd03b382a667f0f0bc506e0cbc45118eea293.json deleted file mode 100644 index f23df5d70..000000000 --- a/.sqlx/query-eb753d506ce15b17b7aca1f5dd3dd03b382a667f0f0bc506e0cbc45118eea293.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT g.name as name, COALESCE(ARRAY_AGG(DISTINCT u.username) FILTER (WHERE u.username IS NOT NULL), '{}') as \"members!\", COALESCE(ARRAY_AGG(DISTINCT wn.name) FILTER (WHERE wn.name IS NOT NULL), '{}') as \"vpn_locations!\" FROM \"group\" g LEFT JOIN \"group_user\" gu ON gu.group_id = g.id LEFT JOIN \"user\" u ON u.id = gu.user_id LEFT JOIN \"wireguard_network_allowed_group\" wnag ON wnag.group_id = g.id LEFT JOIN \"wireguard_network\" wn ON wn.id = wnag.network_id GROUP BY g.name", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "members!", - "type_info": "TextArray" - }, - { - "ordinal": 2, - "name": "vpn_locations!", - "type_info": "TextArray" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - null, - null - ] - }, - "hash": "eb753d506ce15b17b7aca1f5dd3dd03b382a667f0f0bc506e0cbc45118eea293" -} diff --git a/.sqlx/query-dc21ad55a35e5ea826e64030b4d71a0df499cc1b22f1d8fcffa135307b09c187.json b/.sqlx/query-edb099ee36c93c25aa9f91afdbf0c673f870e9d8c619826f855c7090a7d3cf30.json similarity index 79% rename from .sqlx/query-dc21ad55a35e5ea826e64030b4d71a0df499cc1b22f1d8fcffa135307b09c187.json rename to .sqlx/query-edb099ee36c93c25aa9f91afdbf0c673f870e9d8c619826f855c7090a7d3cf30.json index 9462acb1b..94f69bdb1 100644 --- a/.sqlx/query-dc21ad55a35e5ea826e64030b4d71a0df499cc1b22f1d8fcffa135307b09c187.json +++ b/.sqlx/query-edb099ee36c93c25aa9f91afdbf0c673f870e9d8c619826f855c7090a7d3cf30.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"device_id\",\"collected_at\",\"network\",\"endpoint\",\"upload\",\"download\",\"latest_handshake\",\"allowed_ips\" FROM \"wireguard_peer_stats\" WHERE id = $1", + "query": "SELECT id, \"device_id\",\"collected_at\",\"network\",\"endpoint\",\"upload\",\"download\",\"latest_handshake\",\"allowed_ips\" FROM \"wireguard_peer_stats\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -66,5 +66,5 @@ true ] }, - "hash": "dc21ad55a35e5ea826e64030b4d71a0df499cc1b22f1d8fcffa135307b09c187" + "hash": "edb099ee36c93c25aa9f91afdbf0c673f870e9d8c619826f855c7090a7d3cf30" } diff --git a/.sqlx/query-5f1da7400599669d9591f6dded6c38d6f74286fd5660c1ccae20ce43617bbc8f.json b/.sqlx/query-f323dc6850824f6b51c4b980d330cc93402cd94cdd47d80ada08063d0cb708b7.json similarity index 75% rename from .sqlx/query-5f1da7400599669d9591f6dded6c38d6f74286fd5660c1ccae20ce43617bbc8f.json rename to .sqlx/query-f323dc6850824f6b51c4b980d330cc93402cd94cdd47d80ada08063d0cb708b7.json index fbd018000..aaddaa63c 100644 --- a/.sqlx/query-5f1da7400599669d9591f6dded6c38d6f74286fd5660c1ccae20ce43617bbc8f.json +++ b/.sqlx/query-f323dc6850824f6b51c4b980d330cc93402cd94cdd47d80ada08063d0cb708b7.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "WITH s AS ( SELECT DISTINCT ON (device_id) * FROM wireguard_peer_stats ORDER BY device_id, latest_handshake DESC ) SELECT d.id \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN s ON d.id = s.device_id WHERE s.latest_handshake >= $1 AND s.network = $2", + "query": "WITH s AS ( SELECT DISTINCT ON (device_id) * FROM wireguard_peer_stats ORDER BY device_id, latest_handshake DESC ) SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN s ON d.id = s.device_id WHERE s.latest_handshake >= $1 AND s.network = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -43,5 +43,5 @@ false ] }, - "hash": "5f1da7400599669d9591f6dded6c38d6f74286fd5660c1ccae20ce43617bbc8f" + "hash": "f323dc6850824f6b51c4b980d330cc93402cd94cdd47d80ada08063d0cb708b7" } diff --git a/.sqlx/query-eb6dee5462657ac5ce0ecf31d1477ce7cc874d9ad8b3119977168f601b3e8072.json b/.sqlx/query-f5d791e2cff6d6fec9ad6c18b279b2237153a82d6057bbb59dbf4d8b22f18cae.json similarity index 76% rename from .sqlx/query-eb6dee5462657ac5ce0ecf31d1477ce7cc874d9ad8b3119977168f601b3e8072.json rename to .sqlx/query-f5d791e2cff6d6fec9ad6c18b279b2237153a82d6057bbb59dbf4d8b22f18cae.json index b0e4fe52a..595be5483 100644 --- a/.sqlx/query-eb6dee5462657ac5ce0ecf31d1477ce7cc874d9ad8b3119977168f601b3e8072.json +++ b/.sqlx/query-f5d791e2cff6d6fec9ad6c18b279b2237153a82d6057bbb59dbf4d8b22f18cae.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", name, wireguard_pubkey, user_id, created FROM device WHERE wireguard_pubkey = $1", + "query": "SELECT id, name, wireguard_pubkey, user_id, created FROM device WHERE wireguard_pubkey = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -42,5 +42,5 @@ false ] }, - "hash": "eb6dee5462657ac5ce0ecf31d1477ce7cc874d9ad8b3119977168f601b3e8072" + "hash": "f5d791e2cff6d6fec9ad6c18b279b2237153a82d6057bbb59dbf4d8b22f18cae" } diff --git a/.sqlx/query-c64f247f81e332689e35c224656847b246deb6f92a5790a4fdd0d5733defbb57.json b/.sqlx/query-f74ae18765935516c2d99efe7a5b1257994186057c0f00bc4e2c2a401e7ddaae.json similarity index 75% rename from .sqlx/query-c64f247f81e332689e35c224656847b246deb6f92a5790a4fdd0d5733defbb57.json rename to .sqlx/query-f74ae18765935516c2d99efe7a5b1257994186057c0f00bc4e2c2a401e7ddaae.json index 4a03705f0..881bb89e7 100644 --- a/.sqlx/query-c64f247f81e332689e35c224656847b246deb6f92a5790a4fdd0d5733defbb57.json +++ b/.sqlx/query-f74ae18765935516c2d99efe7a5b1257994186057c0f00bc4e2c2a401e7ddaae.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"wireguard_pubkey\",\"user_id\",\"created\" FROM \"device\" WHERE id = $1", + "query": "SELECT device.id, name, wireguard_pubkey, user_id, created FROM device WHERE user_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -42,5 +42,5 @@ false ] }, - "hash": "c64f247f81e332689e35c224656847b246deb6f92a5790a4fdd0d5733defbb57" + "hash": "f74ae18765935516c2d99efe7a5b1257994186057c0f00bc4e2c2a401e7ddaae" } diff --git a/.sqlx/query-0739dda752a2a5b4bb6483d8fdc87b7065d01d6d5990c7d344be2d0068cac835.json b/.sqlx/query-f76434209cbb430ea2c66f7d97bfca592d7fbd12303935281ff30de901a30870.json similarity index 79% rename from .sqlx/query-0739dda752a2a5b4bb6483d8fdc87b7065d01d6d5990c7d344be2d0068cac835.json rename to .sqlx/query-f76434209cbb430ea2c66f7d97bfca592d7fbd12303935281ff30de901a30870.json index e0722c555..b83ac646f 100644 --- a/.sqlx/query-0739dda752a2a5b4bb6483d8fdc87b7065d01d6d5990c7d344be2d0068cac835.json +++ b/.sqlx/query-f76434209cbb430ea2c66f7d97bfca592d7fbd12303935281ff30de901a30870.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", client_id, client_secret, redirect_uri, scope, name, enabled FROM oauth2client WHERE client_id = $1", + "query": "SELECT id, client_id, client_secret, redirect_uri, scope, name, enabled FROM oauth2client WHERE client_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -54,5 +54,5 @@ false ] }, - "hash": "0739dda752a2a5b4bb6483d8fdc87b7065d01d6d5990c7d344be2d0068cac835" + "hash": "f76434209cbb430ea2c66f7d97bfca592d7fbd12303935281ff30de901a30870" } diff --git a/.sqlx/query-f93c26d4777db959e48d8e33a08f081dafeefcc853b610ac238134ec78043a68.json b/.sqlx/query-f93c26d4777db959e48d8e33a08f081dafeefcc853b610ac238134ec78043a68.json new file mode 100644 index 000000000..526554820 --- /dev/null +++ b/.sqlx/query-f93c26d4777db959e48d8e33a08f081dafeefcc853b610ac238134ec78043a68.json @@ -0,0 +1,46 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT DISTINCT ON (d.id) d.id, d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN \"user\" u ON d.user_id = u.id JOIN group_user gu ON u.id = gu.user_id JOIN \"group\" g ON gu.group_id = g.id WHERE g.\"name\" IN (SELECT * FROM UNNEST($1::text[])) AND u.is_active = true ORDER BY d.id ASC", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "wireguard_pubkey", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "user_id", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "created", + "type_info": "Timestamp" + } + ], + "parameters": { + "Left": [ + "TextArray" + ] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "f93c26d4777db959e48d8e33a08f081dafeefcc853b610ac238134ec78043a68" +} diff --git a/.sqlx/query-29809d24769e1c6cb572c666fd0caeeaeb3b13a524f503b21414d1394173eb24.json b/.sqlx/query-f95b0887794cfac8c96bc32a99c3ea4a8e635965c409d72d678045abf28bd16f.json similarity index 80% rename from .sqlx/query-29809d24769e1c6cb572c666fd0caeeaeb3b13a524f503b21414d1394173eb24.json rename to .sqlx/query-f95b0887794cfac8c96bc32a99c3ea4a8e635965c409d72d678045abf28bd16f.json index ffaf6c210..a31da94fa 100644 --- a/.sqlx/query-29809d24769e1c6cb572c666fd0caeeaeb3b13a524f503b21414d1394173eb24.json +++ b/.sqlx/query-f95b0887794cfac8c96bc32a99c3ea4a8e635965c409d72d678045abf28bd16f.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"address\" \"address: _\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\" \"allowed_ips: _\",\"connected_at\",\"mfa_enabled\",\"keepalive_interval\",\"peer_disconnect_threshold\" FROM \"wireguard_network\" WHERE id = $1", + "query": "SELECT id, \"name\",\"address\" \"address: _\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\" \"allowed_ips: _\",\"connected_at\",\"mfa_enabled\",\"keepalive_interval\",\"peer_disconnect_threshold\" FROM \"wireguard_network\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -90,5 +90,5 @@ false ] }, - "hash": "29809d24769e1c6cb572c666fd0caeeaeb3b13a524f503b21414d1394173eb24" + "hash": "f95b0887794cfac8c96bc32a99c3ea4a8e635965c409d72d678045abf28bd16f" } diff --git a/.sqlx/query-33e91546890789d9341b06c541648149baf908b4f2ba35da8adc07feda6299e3.json b/.sqlx/query-f9617d86a16b2a7fddac1f76089a2b85c6a034d77aa2dcd1855665dce3858566.json similarity index 81% rename from .sqlx/query-33e91546890789d9341b06c541648149baf908b4f2ba35da8adc07feda6299e3.json rename to .sqlx/query-f9617d86a16b2a7fddac1f76089a2b85c6a034d77aa2dcd1855665dce3858566.json index 694ea0750..a7f156e9b 100644 --- a/.sqlx/query-33e91546890789d9341b06c541648149baf908b4f2ba35da8adc07feda6299e3.json +++ b/.sqlx/query-f9617d86a16b2a7fddac1f76089a2b85c6a034d77aa2dcd1855665dce3858566.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", url, description, token, enabled, on_user_created, on_user_deleted, on_user_modified, on_hwkey_provision FROM webhook WHERE url = $1", + "query": "SELECT id, url, description, token, enabled, on_user_created, on_user_deleted, on_user_modified, on_hwkey_provision FROM webhook WHERE url = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -66,5 +66,5 @@ false ] }, - "hash": "33e91546890789d9341b06c541648149baf908b4f2ba35da8adc07feda6299e3" + "hash": "f9617d86a16b2a7fddac1f76089a2b85c6a034d77aa2dcd1855665dce3858566" } diff --git a/.sqlx/query-06e4ad525d72c83281944a311063f8717344fcb0c8ead0e98981439887e8e366.json b/.sqlx/query-fad6990b8d347099568fa0e867a30923a54812064de0b9331b2c71134e6ce29e.json similarity index 81% rename from .sqlx/query-06e4ad525d72c83281944a311063f8717344fcb0c8ead0e98981439887e8e366.json rename to .sqlx/query-fad6990b8d347099568fa0e867a30923a54812064de0b9331b2c71134e6ce29e.json index 8b708fd22..0852c267c 100644 --- a/.sqlx/query-06e4ad525d72c83281944a311063f8717344fcb0c8ead0e98981439887e8e366.json +++ b/.sqlx/query-fad6990b8d347099568fa0e867a30923a54812064de0b9331b2c71134e6ce29e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT device_id, wireguard_network_id, wireguard_ip as \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at FROM wireguard_network_device WHERE device_id = $1", + "query": "SELECT device_id, wireguard_network_id, wireguard_ip \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at FROM wireguard_network_device WHERE device_id = $1", "describe": { "columns": [ { @@ -48,5 +48,5 @@ true ] }, - "hash": "06e4ad525d72c83281944a311063f8717344fcb0c8ead0e98981439887e8e366" + "hash": "fad6990b8d347099568fa0e867a30923a54812064de0b9331b2c71134e6ce29e" } diff --git a/.sqlx/query-c969fef31aa7d70c9d34eee9ecd363094359cc1c1e21516dad2f082d5224b708.json b/.sqlx/query-fc807754b75355939c8b77602eb9a691cc5d4f228326a0ffdb0cfcdedee438a5.json similarity index 85% rename from .sqlx/query-c969fef31aa7d70c9d34eee9ecd363094359cc1c1e21516dad2f082d5224b708.json rename to .sqlx/query-fc807754b75355939c8b77602eb9a691cc5d4f228326a0ffdb0cfcdedee438a5.json index 4dbf61a84..36de06fc5 100644 --- a/.sqlx/query-c969fef31aa7d70c9d34eee9ecd363094359cc1c1e21516dad2f082d5224b708.json +++ b/.sqlx/query-fc807754b75355939c8b77602eb9a691cc5d4f228326a0ffdb0cfcdedee438a5.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" WHERE openid_sub = $1", + "query": "SELECT id, username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" WHERE username = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -121,5 +121,5 @@ true ] }, - "hash": "c969fef31aa7d70c9d34eee9ecd363094359cc1c1e21516dad2f082d5224b708" + "hash": "fc807754b75355939c8b77602eb9a691cc5d4f228326a0ffdb0cfcdedee438a5" } diff --git a/.sqlx/query-8e0dd4e8088a1d51fc94e9e013d6220677464886b055722182a89a2a60605db6.json b/.sqlx/query-fcd1e222d97e81825c3f6f43a731b6cf8647bc5ebb766f1aaa5b69a62cb92759.json similarity index 81% rename from .sqlx/query-8e0dd4e8088a1d51fc94e9e013d6220677464886b055722182a89a2a60605db6.json rename to .sqlx/query-fcd1e222d97e81825c3f6f43a731b6cf8647bc5ebb766f1aaa5b69a62cb92759.json index 35cc853b1..109ad2602 100644 --- a/.sqlx/query-8e0dd4e8088a1d51fc94e9e013d6220677464886b055722182a89a2a60605db6.json +++ b/.sqlx/query-fcd1e222d97e81825c3f6f43a731b6cf8647bc5ebb766f1aaa5b69a62cb92759.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, yubikey_id \"yubikey_id?\", key, name, key_type \"key_type: AuthenticationKeyType\" FROM authentication_key WHERE user_id = $1 AND key_type = $2", + "query": "SELECT id, user_id, yubikey_id \"yubikey_id?\", key, name, key_type \"key_type: AuthenticationKeyType\" FROM authentication_key WHERE user_id = $1 AND key_type = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -69,5 +69,5 @@ false ] }, - "hash": "8e0dd4e8088a1d51fc94e9e013d6220677464886b055722182a89a2a60605db6" + "hash": "fcd1e222d97e81825c3f6f43a731b6cf8647bc5ebb766f1aaa5b69a62cb92759" } diff --git a/Cargo.lock b/Cargo.lock index babb9eda9..0a8671c16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,7 +59,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", "once_cell", "version_check", "zerocopy", @@ -218,9 +217,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -229,24 +228,24 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -266,14 +265,14 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" @@ -298,19 +297,19 @@ dependencies = [ "rustversion", "serde", "sync_wrapper 0.1.2", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", ] [[package]] name = "axum" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" dependencies = [ "async-trait", - "axum-core 0.4.3", + "axum-core 0.4.5", "bytes", "futures-util", "http 1.1.0", @@ -331,7 +330,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", - "tower", + "tower 0.5.1", "tower-layer", "tower-service", "tracing", @@ -343,7 +342,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e7c467bdcd2bd982ce5c8742a1a178aba7b03db399fd18f5d5d438f5aa91cb4" dependencies = [ - "axum 0.7.5", + "axum 0.7.7", "forwarded-header-value", "serde", ] @@ -367,9 +366,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", @@ -380,7 +379,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.1", "tower-layer", "tower-service", "tracing", @@ -388,12 +387,12 @@ dependencies = [ [[package]] name = "axum-extra" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733" +checksum = "73c3220b188aea709cf1b6c5f9b01c3bd936bb08bd2b5184a12b35ac8131b1f9" dependencies = [ - "axum 0.7.5", - "axum-core 0.4.3", + "axum 0.7.7", + "axum-core 0.4.5", "bytes", "cookie 0.18.1", "futures-util", @@ -404,7 +403,7 @@ dependencies = [ "mime", "pin-project-lite", "serde", - "tower", + "tower 0.5.1", "tower-layer", "tower-service", "tracing", @@ -592,9 +591,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" dependencies = [ "serde", ] @@ -620,9 +619,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.19" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d74707dde2ba56f86ae90effb3b43ddd369504387e718014de010cec7959800" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "jobserver", "libc", @@ -712,9 +711,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.17" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -722,9 +721,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -734,14 +733,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -784,11 +783,20 @@ dependencies = [ "uuid", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-hex" -version = "1.12.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" dependencies = [ "cfg-if", "cpufeatures", @@ -1023,7 +1031,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1047,7 +1055,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1058,7 +1066,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1082,7 +1090,7 @@ version = "1.0.0" dependencies = [ "anyhow", "argon2", - "axum 0.7.5", + "axum 0.7.7", "axum-client-ip", "axum-extra", "base32", @@ -1192,7 +1200,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1213,7 +1221,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1223,7 +1231,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" dependencies = [ "derive_builder_core", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1236,7 +1244,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1274,7 +1282,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1524,9 +1532,14 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.3" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] [[package]] name = "fastrand" @@ -1570,9 +1583,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", @@ -1702,7 +1715,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1768,18 +1781,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "getset" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f636605b743120a8d32ed92fc27b6cde1a769f8f936c065151eb66f88ded513c" -dependencies = [ - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "ghash" version = "0.5.1" @@ -1818,8 +1819,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1856,7 +1857,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.5.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -1885,11 +1886,17 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "hashlink" -version = "0.8.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ "hashbrown 0.14.5", ] @@ -1918,15 +1925,6 @@ dependencies = [ "http 1.1.0", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.5.0" @@ -2047,9 +2045,9 @@ checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -2145,7 +2143,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.0", "tower-service", - "webpki-roots 0.26.5", + "webpki-roots 0.26.6", ] [[package]] @@ -2175,9 +2173,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -2188,7 +2186,6 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] @@ -2331,7 +2328,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2391,7 +2388,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.8", "same-file", "walkdir", "winapi-util", @@ -2448,12 +2445,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", "serde", ] @@ -2552,9 +2549,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", @@ -2646,9 +2643,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libgit2-sys" @@ -2670,9 +2667,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libsqlite3-sys" -version = "0.27.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -2810,10 +2807,10 @@ dependencies = [ [[package]] name = "model_derive" -version = "0.1.2" +version = "0.2.0" dependencies = [ "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2958,7 +2955,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3022,9 +3019,12 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "opaque-debug" @@ -3112,7 +3112,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3222,6 +3222,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -3298,9 +3304,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -3309,9 +3315,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -3319,22 +3325,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "pest_meta" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", @@ -3348,7 +3354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.5.0", + "indexmap 2.6.0", ] [[package]] @@ -3470,7 +3476,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3508,9 +3514,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "polyval" @@ -3524,6 +3530,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -3546,7 +3558,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3605,28 +3617,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "proc-macro2" version = "1.0.86" @@ -3648,7 +3638,7 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "unarray", ] @@ -3669,7 +3659,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", - "heck 0.5.0", + "heck", "itertools 0.12.1", "log", "multimap", @@ -3679,7 +3669,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.77", + "syn 2.0.79", "tempfile", ] @@ -3693,7 +3683,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3859,23 +3849,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -3889,13 +3879,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -3906,9 +3896,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -3961,9 +3951,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64 0.22.1", "bytes", @@ -3985,7 +3975,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls 0.23.13", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", @@ -3998,7 +3988,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.26.5", + "webpki-roots 0.26.6", "windows-registry", ] @@ -4099,7 +4089,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.77", + "syn 2.0.79", "walkdir", ] @@ -4220,7 +4210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", "security-framework", @@ -4237,19 +4227,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" @@ -4400,9 +4389,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -4460,7 +4449,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4519,15 +4508,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "9720086b3357bcb44fce40117d769a4d068c70ecfa190850a980a71755f66fcc" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -4537,14 +4526,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "5f1abbfe725f27678f4663bcacb75a83e829fd464c25d78dd038a3a29e307cec" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4553,7 +4542,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "itoa", "ryu", "serde", @@ -4680,6 +4669,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -4722,9 +4714,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" dependencies = [ "sqlx-core", "sqlx-macros", @@ -4735,11 +4727,10 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" dependencies = [ - "ahash", "atoi", "byteorder", "bytes", @@ -4753,9 +4744,10 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", + "hashbrown 0.14.5", "hashlink", "hex", - "indexmap 2.5.0", + "indexmap 2.6.0", "ipnetwork", "log", "memchr", @@ -4778,26 +4770,26 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 1.0.109", + "syn 2.0.79", ] [[package]] name = "sqlx-macros-core" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" dependencies = [ "dotenvy", "either", - "heck 0.4.1", + "heck", "hex", "once_cell", "proc-macro2", @@ -4809,7 +4801,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 1.0.109", + "syn 2.0.79", "tempfile", "tokio", "url", @@ -4817,12 +4809,12 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.6.0", "byteorder", "bytes", @@ -4861,12 +4853,12 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.6.0", "byteorder", "chrono", @@ -4902,9 +4894,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" dependencies = [ "atoi", "chrono", @@ -4918,10 +4910,10 @@ dependencies = [ "log", "percent-encoding", "serde", + "serde_urlencoded", "sqlx-core", "tracing", "url", - "urlencoding", "uuid", ] @@ -5010,22 +5002,22 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "struct-patch" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ab235006ddf5df0867e253576ade01622db0371461838e928ad0922509ec52" +checksum = "82dd71e677fa313d07db38f4c7f9a38f89dfb90be8f35914956919f6ca7b9174" dependencies = [ "struct-patch-derive", ] [[package]] name = "struct-patch-derive" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac9c83bb13d2943d08e980d41c09392b5ade4187f1b11ac1d6a039e9114e82" +checksum = "4596646090f0d724e6c7f3b65d694f99a0daa1a5893a78ef83887025e041405c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5043,11 +5035,11 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5069,9 +5061,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -5113,7 +5105,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5145,9 +5137,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -5180,22 +5172,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5310,7 +5302,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5388,11 +5380,11 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.22.21" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "toml_datetime", "winnow", ] @@ -5418,12 +5410,12 @@ dependencies = [ "pin-project", "prost", "rustls-native-certs", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "rustls-pki-types", "tokio", "tokio-rustls 0.25.0", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -5439,7 +5431,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5487,6 +5479,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-http" version = "0.5.2" @@ -5544,7 +5552,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5623,9 +5631,9 @@ dependencies = [ [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uint" @@ -5706,9 +5714,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -5718,36 +5726,30 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unicode_categories" @@ -5789,12 +5791,6 @@ dependencies = [ "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "utf16_iter" version = "1.0.5" @@ -5819,7 +5815,7 @@ version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_json", "utoipa-gen", @@ -5835,7 +5831,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5844,10 +5840,10 @@ version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943e0ff606c6d57d410fd5663a4d7c074ab2c5f14ab903b9514565e59fa1189e" dependencies = [ - "axum 0.7.5", + "axum 0.7.7", "mime_guess", "regex", - "reqwest 0.12.7", + "reqwest 0.12.8", "rust-embed", "serde", "serde_json", @@ -5880,9 +5876,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "9.0.0" +version = "9.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c32e7318e93a9ac53693b6caccfb05ff22e04a44c7cf8a279051f24c09da286f" +checksum = "349ed9e45296a581f455bc18039878f409992999bc1d5da12a6800eb18c8752f" dependencies = [ "anyhow", "derive_builder", @@ -5893,9 +5889,9 @@ dependencies = [ [[package]] name = "vergen-git2" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62c52cd2b2b8b7ec75fc20111b3022ac3ff83e4fc14b9497cfcfd39c54f9c67" +checksum = "e771aff771c0d7c2f42e434e2766d304d917e29b40f0424e8faaaa936bbc3f29" dependencies = [ "anyhow", "derive_builder", @@ -5908,13 +5904,12 @@ dependencies = [ [[package]] name = "vergen-lib" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e06bee42361e43b60f363bad49d63798d0f42fb1768091812270eca00c784720" +checksum = "229eaddb0050920816cf051e619affaf18caa3dd512de8de5839ccbc8e53abb0" dependencies = [ "anyhow", "derive_builder", - "getset", "rustversion", ] @@ -5977,7 +5972,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -6011,7 +6006,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6024,9 +6019,9 @@ checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" dependencies = [ "futures-util", "js-sys", @@ -6151,9 +6146,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.5" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -6398,9 +6393,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -6486,7 +6481,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "synstructure 0.13.1", ] @@ -6508,7 +6503,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6528,7 +6523,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "synstructure 0.13.1", ] @@ -6549,7 +6544,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6571,7 +6566,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6585,7 +6580,7 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.5.0", + "indexmap 2.6.0", "num_enum", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 4365ced71..7e53e12ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ serde_cbor = { version = "0.12.0-dev", package = "serde_cbor_2" } serde_json = "1.0" serde_urlencoded = "0.7" sha-1 = "0.10" -sqlx = { version = "0.7", features = [ +sqlx = { version = "0.8", features = [ "chrono", "ipnetwork", "runtime-tokio-native-tls", diff --git a/model-derive/Cargo.toml b/model-derive/Cargo.toml index 46013af7f..5c0e90ea9 100644 --- a/model-derive/Cargo.toml +++ b/model-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "model_derive" -version = "0.1.2" +version = "0.2.0" edition = "2021" [lib] diff --git a/model-derive/src/lib.rs b/model-derive/src/lib.rs index acff2d332..abc6764f3 100644 --- a/model-derive/src/lib.rs +++ b/model-derive/src/lib.rs @@ -163,26 +163,46 @@ pub fn derive(input: TokenStream) -> TokenStream { None }); let update_args = insert_args.clone(); + // Struct fields without `id`. It is not possible to use `..self`: mismatched types. + let struct_fields = named.iter().filter_map(|field| { + if let Some(name) = &field.ident { + if name != "id" { + return Some(quote! { #name: self.#name }); + } + } + None + }); // queries - let all_query = format!("SELECT id \"id?\", {cs_aliased_fields} FROM \"{table_name}\""); + let all_query = format!("SELECT id, {cs_aliased_fields} FROM \"{table_name}\""); let find_by_id_query = - format!("SELECT id \"id?\", {cs_aliased_fields} FROM \"{table_name}\" WHERE id = $1"); + format!("SELECT id, {cs_aliased_fields} FROM \"{table_name}\" WHERE id = $1"); let delete_query = format!("DELETE FROM \"{table_name}\" WHERE id = $1"); let insert_query = format!("INSERT INTO \"{table_name}\" ({cs_fields}) VALUES ({cs_values}) RETURNING id"); let update_query = format!("UPDATE \"{table_name}\" SET {cs_setters} WHERE id = $1"); + // TODO: add limit and offset for all(). quote! { - impl #name { - pub async fn find_by_id<'e, E>(executor: E, id: i64) -> Result, sqlx::Error> + impl #name { + pub async fn save<'e, E>(self, executor: E) -> Result<#name, sqlx::Error> + where + E: sqlx::PgExecutor<'e> + { + let id = sqlx::query_scalar!(#insert_query, #(#insert_args,)*).fetch_one(executor).await?; + + Ok(#name { id, #(#struct_fields,)* }) + } + } + + impl #name { + pub async fn find_by_id<'e, E>(executor: E, id: Id) -> Result, sqlx::Error> where E: sqlx::PgExecutor<'e> { sqlx::query_as!(Self, #find_by_id_query, id).fetch_optional(executor).await } - // TODO: add limit and offset pub async fn all<'e, E>(executor: E) -> Result, sqlx::Error> where E: sqlx::PgExecutor<'e> @@ -194,9 +214,8 @@ pub fn derive(input: TokenStream) -> TokenStream { where E: sqlx::PgExecutor<'e> { - if let Some(id) = self.id { - sqlx::query!(#delete_query, id).execute(executor).await?; - } + sqlx::query!(#delete_query, self.id).execute(executor).await?; + Ok(()) } @@ -204,15 +223,8 @@ pub fn derive(input: TokenStream) -> TokenStream { where E: sqlx::PgExecutor<'e> { - match self.id { - None => { - let id = sqlx::query_scalar!(#insert_query, #(#insert_args,)*).fetch_one(executor).await?; - self.id = Some(id); - } - Some(id) => { - sqlx::query!(#update_query, id, #(#update_args,)*).execute(executor).await?; - } - } + sqlx::query!(#update_query, self.id, #(#update_args,)*).execute(executor).await?; + Ok(()) } } diff --git a/src/appstate.rs b/src/appstate.rs index 6a772c47c..78556a756 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -5,6 +5,7 @@ use axum_extra::extract::cookie::Key; use reqwest::Client; use secrecy::ExposeSecret; use serde_json::json; +use sqlx::PgPool; use tokio::{ sync::{ broadcast::Sender, @@ -17,14 +18,14 @@ use webauthn_rs::prelude::*; use crate::{ auth::failed_login::FailedLoginMap, - db::{AppEvent, DbPool, GatewayEvent, WebHook}, + db::{AppEvent, GatewayEvent, WebHook}, mail::Mail, server_config, }; #[derive(Clone)] pub struct AppState { - pub pool: DbPool, + pub pool: PgPool, tx: UnboundedSender, wireguard_tx: Sender, pub mail_tx: UnboundedSender, @@ -44,7 +45,7 @@ impl AppState { } /// Handle webhook events - async fn handle_triggers(pool: DbPool, mut rx: UnboundedReceiver) { + async fn handle_triggers(pool: PgPool, mut rx: UnboundedReceiver) { let reqwest_client = Client::builder().user_agent("reqwest").build().unwrap(); while let Some(msg) = rx.recv().await { debug!("WebHook triggered"); @@ -97,7 +98,7 @@ impl AppState { /// Create application state pub fn new( - pool: DbPool, + pool: PgPool, tx: UnboundedSender, rx: UnboundedReceiver, wireguard_tx: Sender, diff --git a/src/auth/mod.rs b/src/auth/mod.rs index a87d85527..d1d0d0336 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize}; use crate::{ appstate::AppState, - db::{Group, OAuth2AuthorizedApp, OAuth2Token, Session, SessionState, User}, + db::{Group, Id, OAuth2AuthorizedApp, OAuth2Token, Session, SessionState, User}, error::WebError, handlers::SESSION_COOKIE_NAME, server_config, @@ -152,14 +152,14 @@ where // This represents a session for a user who completed the login process (including MFA, if enabled). pub struct SessionInfo { pub session: Session, - pub user: User, + pub user: User, pub is_admin: bool, - groups: Vec, + groups: Vec>, } impl SessionInfo { #[must_use] - pub fn new(session: Session, user: User, is_admin: bool) -> Self { + pub fn new(session: Session, user: User, is_admin: bool) -> Self { Self { session, user, @@ -240,7 +240,7 @@ role!(UserAdminRole, admin_groupname useradmin_groupname); role!(VpnRole, admin_groupname vpn_groupname); // User authenticated by a valid access token -pub struct AccessUserInfo(pub(crate) User); +pub struct AccessUserInfo(pub(crate) User); #[async_trait] impl FromRequestParts for AccessUserInfo diff --git a/src/db/mod.rs b/src/db/mod.rs index 3b79ccd7b..c444023ab 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,11 +1,13 @@ pub mod models; -use sqlx::postgres::PgConnectOptions; +use sqlx::postgres::{PgConnectOptions, PgPool}; -pub type DbPool = sqlx::postgres::PgPool; +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct NoId; +pub type Id = i64; /// Initializes and migrates postgres database. Returns DB pool object. -pub async fn init_db(host: &str, port: u16, name: &str, user: &str, password: &str) -> DbPool { +pub async fn init_db(host: &str, port: u16, name: &str, user: &str, password: &str) -> PgPool { info!("Initializing DB pool"); let opts = PgConnectOptions::new() .host(host) @@ -13,7 +15,7 @@ pub async fn init_db(host: &str, port: u16, name: &str, user: &str, password: &s .username(user) .password(password) .database(name); - let pool = DbPool::connect_with(opts) + let pool = PgPool::connect_with(opts) .await .expect("Database connection failed"); sqlx::migrate!() diff --git a/src/db/models/auth_code.rs b/src/db/models/auth_code.rs index 52ad8bf8d..736ae6092 100644 --- a/src/db/models/auth_code.rs +++ b/src/db/models/auth_code.rs @@ -1,15 +1,17 @@ use chrono::Utc; use model_derive::Model; -use sqlx::{query_as, Error as SqlxError}; +use sqlx::{query_as, Error as SqlxError, PgPool}; -use super::DbPool; -use crate::random::gen_alphanumeric; +use crate::{ + db::{Id, NoId}, + random::gen_alphanumeric, +}; #[derive(Model, Clone)] #[table(authorization_code)] -pub struct AuthCode { - id: Option, - pub user_id: i64, +pub struct AuthCode { + id: I, + pub user_id: Id, pub client_id: String, pub code: String, pub redirect_uri: String, @@ -22,7 +24,7 @@ pub struct AuthCode { impl AuthCode { #[must_use] pub fn new( - user_id: i64, + user_id: Id, client_id: String, redirect_uri: String, scope: String, @@ -31,7 +33,7 @@ impl AuthCode { ) -> Self { let code = gen_alphanumeric(24); Self { - id: None, + id: NoId, user_id, client_id, code, @@ -42,12 +44,14 @@ impl AuthCode { code_challenge, } } +} +impl AuthCode { /// Find by code. - pub async fn find_code(pool: &DbPool, code: &str) -> Result, SqlxError> { + pub async fn find_code(pool: &PgPool, code: &str) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", user_id, client_id, code, redirect_uri, scope, auth_time, nonce, \ + "SELECT id, user_id, client_id, code, redirect_uri, scope, auth_time, nonce, \ code_challenge FROM authorization_code WHERE code = $1", code ) @@ -56,7 +60,7 @@ impl AuthCode { } // Remove a used authorization_code - pub async fn consume(self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn consume(self, pool: &PgPool) -> Result<(), SqlxError> { self.delete(pool).await } } diff --git a/src/db/models/authentication_key.rs b/src/db/models/authentication_key.rs index 4eb95c35f..2bddb9a65 100644 --- a/src/db/models/authentication_key.rs +++ b/src/db/models/authentication_key.rs @@ -1,6 +1,8 @@ use model_derive::Model; use sqlx::{query_as, Error as SqlxError, PgExecutor, Type}; +use crate::db::{Id, NoId}; + #[derive(Clone, Debug, Deserialize, Serialize, Type)] #[sqlx(type_name = "authentication_key_type", rename_all = "lowercase")] #[serde(rename_all = "lowercase")] @@ -11,11 +13,11 @@ pub(crate) enum AuthenticationKeyType { #[derive(Deserialize, Model, Serialize)] #[table(authentication_key)] -pub(crate) struct AuthenticationKey { - id: Option, +pub(crate) struct AuthenticationKey { + id: I, pub yubikey_id: Option, pub name: Option, - pub user_id: i64, + pub user_id: Id, pub key: String, #[model(enum)] key_type: AuthenticationKeyType, @@ -24,14 +26,14 @@ pub(crate) struct AuthenticationKey { impl AuthenticationKey { #[must_use] pub fn new( - user_id: i64, + user_id: Id, key: String, name: Option, key_type: AuthenticationKeyType, yubikey_id: Option, ) -> Self { Self { - id: None, + id: NoId, yubikey_id, user_id, key, @@ -39,10 +41,12 @@ impl AuthenticationKey { key_type, } } +} +impl AuthenticationKey { pub async fn find_by_user_id<'e, E>( executor: E, - user_id: i64, + user_id: Id, key_type: Option, ) -> Result, SqlxError> where @@ -52,7 +56,7 @@ impl AuthenticationKey { Some(key_type) => { query_as!( Self, - "SELECT id \"id?\", user_id, yubikey_id \"yubikey_id?\", key, \ + "SELECT id, user_id, yubikey_id \"yubikey_id?\", key, \ name, key_type \"key_type: AuthenticationKeyType\" \ FROM authentication_key WHERE user_id = $1 AND key_type = $2", user_id, @@ -64,7 +68,7 @@ impl AuthenticationKey { None => { query_as!( Self, - "SELECT id \"id?\", user_id, yubikey_id \"yubikey_id?\", key, \ + "SELECT id, user_id, yubikey_id \"yubikey_id?\", key, \ name, key_type \"key_type: AuthenticationKeyType\" \ FROM authentication_key WHERE user_id = $1", user_id diff --git a/src/db/models/device.rs b/src/db/models/device.rs index 47383b9c1..6dd9028d7 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -1,26 +1,25 @@ -use std::{ - fmt::{Display, Formatter}, - net::IpAddr, -}; +use std::{fmt, net::IpAddr}; use base64::{prelude::BASE64_STANDARD, Engine}; use chrono::{NaiveDateTime, Utc}; use ipnetwork::IpNetwork; use model_derive::Model; -use sqlx::{query, query_as, Error as SqlxError, FromRow, PgConnection, PgExecutor}; +use sqlx::{query, query_as, Error as SqlxError, FromRow, PgConnection, PgExecutor, PgPool}; use thiserror::Error; use utoipa::ToSchema; use super::{ error::ModelError, wireguard::{WireguardNetwork, WIREGUARD_MAX_HANDSHAKE_MINUTES}, - DbPool, }; -use crate::KEY_LENGTH; +use crate::{ + db::{Id, NoId}, + KEY_LENGTH, +}; #[derive(Serialize)] pub struct DeviceConfig { - pub(crate) network_id: i64, + pub(crate) network_id: Id, pub(crate) network_name: String, pub(crate) config: String, pub(crate) address: IpAddr, @@ -33,34 +32,37 @@ pub struct DeviceConfig { } #[derive(Clone, Deserialize, Model, Serialize, Debug, ToSchema)] -pub struct Device { - pub id: Option, +pub struct Device { + pub id: I, pub name: String, pub wireguard_pubkey: String, - pub user_id: i64, + pub user_id: Id, pub created: NaiveDateTime, } -impl Display for Device { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self.id { - Some(device_id) => write!(f, "[ID {device_id}] {}", self.name), - None => write!(f, "{}", self.name), - } +impl fmt::Display for Device { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +impl fmt::Display for Device { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[ID {}] {}", self.id, self.name) } } // helper struct which includes network configurations for a given device -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct DeviceInfo { #[serde(flatten)] - pub device: Device, + pub device: Device, pub network_info: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct DeviceNetworkInfo { - pub network_id: i64, + pub network_id: Id, pub device_wireguard_ip: IpAddr, #[serde(skip_serializing)] pub preshared_key: Option, @@ -68,18 +70,17 @@ pub struct DeviceNetworkInfo { } impl DeviceInfo { - pub async fn from_device<'e, E>(executor: E, device: Device) -> Result + pub async fn from_device<'e, E>(executor: E, device: Device) -> Result where E: PgExecutor<'e>, { debug!("Generating device info for {device}"); - let device_id = device.get_id()?; let network_info = query_as!( DeviceNetworkInfo, - "SELECT wireguard_network_id as network_id, wireguard_ip as \"device_wireguard_ip: IpAddr\", preshared_key, is_authorized \ + "SELECT wireguard_network_id network_id, wireguard_ip \"device_wireguard_ip: IpAddr\", preshared_key, is_authorized \ FROM wireguard_network_device \ WHERE device_id = $1", - device_id + device.id ) .fetch_all(executor) .await?; @@ -93,16 +94,16 @@ impl DeviceInfo { // helper struct which includes full device info // including network activity metadata -#[derive(Serialize, Deserialize, Clone, Debug, ToSchema)] +#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct UserDevice { #[serde(flatten)] - pub device: Device, + pub device: Device, pub networks: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct UserDeviceNetworkInfo { - pub network_id: i64, + pub network_id: Id, pub network_name: String, pub network_gateway_ip: String, pub device_wireguard_ip: String, @@ -113,76 +114,74 @@ pub struct UserDeviceNetworkInfo { } impl UserDevice { - pub async fn from_device(pool: &DbPool, device: Device) -> Result, SqlxError> { - if let Some(device_id) = device.id { - // fetch device config and connection info for all networks - let result = query!( - "WITH stats AS ( \ - SELECT DISTINCT ON (network) network, endpoint, latest_handshake \ - FROM wireguard_peer_stats \ - WHERE device_id = $2 \ - ORDER BY network, collected_at DESC \ - ) \ - SELECT \ - n.id as network_id, n.name as network_name, n.endpoint as gateway_endpoint, \ - wnd.wireguard_ip as \"device_wireguard_ip: IpAddr\", stats.endpoint as device_endpoint, \ - stats.latest_handshake as \"latest_handshake?\", \ - COALESCE (((NOW() - stats.latest_handshake) < $1 * interval '1 minute'), false) as \"is_active!\" \ - FROM wireguard_network_device wnd \ - JOIN wireguard_network n ON n.id = wnd.wireguard_network_id \ - LEFT JOIN stats on n.id = stats.network \ - WHERE wnd.device_id = $2", - WIREGUARD_MAX_HANDSHAKE_MINUTES as f64, - device_id, - ) - .fetch_all(pool) - .await?; - - let networks_info: Vec = result - .into_iter() - .map(|r| { - let device_ip = match r.device_endpoint { - Some(endpoint) => endpoint.split(':').next().map(ToString::to_string), - None => None, - }; - UserDeviceNetworkInfo { - network_id: r.network_id, - network_name: r.network_name, - network_gateway_ip: r.gateway_endpoint, - device_wireguard_ip: r.device_wireguard_ip.to_string(), - last_connected_ip: device_ip, - last_connected_location: None, - last_connected_at: r.latest_handshake, - is_active: r.is_active, - } - }) - .collect(); - return Ok(Some(Self { - device, - networks: networks_info, - })); - } - Ok(None) + pub async fn from_device(pool: &PgPool, device: Device) -> Result, SqlxError> { + // fetch device config and connection info for all networks + let result = query!( + "WITH stats AS ( \ + SELECT DISTINCT ON (network) network, endpoint, latest_handshake \ + FROM wireguard_peer_stats \ + WHERE device_id = $2 \ + ORDER BY network, collected_at DESC \ + ) \ + SELECT \ + n.id network_id, n.name network_name, n.endpoint gateway_endpoint, \ + wnd.wireguard_ip \"device_wireguard_ip: IpAddr\", stats.endpoint device_endpoint, \ + stats.latest_handshake \"latest_handshake?\", \ + COALESCE (((NOW() - stats.latest_handshake) < $1 * interval '1 minute'), false) as \"is_active!\" \ + FROM wireguard_network_device wnd \ + JOIN wireguard_network n ON n.id = wnd.wireguard_network_id \ + LEFT JOIN stats on n.id = stats.network \ + WHERE wnd.device_id = $2", + WIREGUARD_MAX_HANDSHAKE_MINUTES as f64, + device.id, + ) + .fetch_all(pool) + .await?; + + let networks_info: Vec = result + .into_iter() + .map(|r| { + let device_ip = match r.device_endpoint { + Some(endpoint) => endpoint.split(':').next().map(ToString::to_string), + None => None, + }; + UserDeviceNetworkInfo { + network_id: r.network_id, + network_name: r.network_name, + network_gateway_ip: r.gateway_endpoint, + device_wireguard_ip: r.device_wireguard_ip.to_string(), + last_connected_ip: device_ip, + last_connected_location: None, + last_connected_at: r.latest_handshake, + is_active: r.is_active, + } + }) + .collect(); + + Ok(Some(Self { + device, + networks: networks_info, + })) } } -#[derive(Debug, Serialize, Deserialize, Clone, FromRow)] +#[derive(Clone, Debug, Deserialize, FromRow, Serialize)] pub struct WireguardNetworkDevice { - pub wireguard_network_id: i64, + pub wireguard_network_id: Id, pub wireguard_ip: IpAddr, - pub device_id: i64, + pub device_id: Id, pub preshared_key: Option, pub is_authorized: bool, pub authorized_at: Option, } -#[derive(Serialize, Deserialize, Debug, ToSchema)] +#[derive(Debug, Deserialize, Serialize, ToSchema)] pub struct AddDevice { pub name: String, pub wireguard_pubkey: String, } -#[derive(Deserialize, Debug, ToSchema)] +#[derive(Debug, Deserialize, ToSchema)] pub struct ModifyDevice { pub name: String, pub wireguard_pubkey: String, @@ -190,7 +189,7 @@ pub struct ModifyDevice { impl WireguardNetworkDevice { #[must_use] - pub fn new(network_id: i64, device_id: i64, wireguard_ip: IpAddr) -> Self { + pub fn new(network_id: Id, device_id: Id, wireguard_ip: IpAddr) -> Self { Self { wireguard_network_id: network_id, wireguard_ip, @@ -220,6 +219,7 @@ impl WireguardNetworkDevice { ) .execute(executor) .await?; + Ok(()) } @@ -240,6 +240,7 @@ impl WireguardNetworkDevice { ) .execute(executor) .await?; + Ok(()) } @@ -255,20 +256,21 @@ impl WireguardNetworkDevice { ) .execute(executor) .await?; + Ok(()) } pub async fn find<'e, E>( executor: E, - device_id: i64, - network_id: i64, + device_id: Id, + network_id: Id, ) -> Result, SqlxError> where E: PgExecutor<'e>, { let res = query_as!( Self, - "SELECT device_id, wireguard_network_id, wireguard_ip as \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at \ + "SELECT device_id, wireguard_network_id, wireguard_ip \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at \ FROM wireguard_network_device \ WHERE device_id = $1 AND wireguard_network_id = $2", device_id, @@ -276,51 +278,52 @@ impl WireguardNetworkDevice { ) .fetch_optional(executor) .await?; + Ok(res) } pub async fn find_by_device( - pool: &DbPool, - device_id: i64, + pool: &PgPool, + device_id: Id, ) -> Result>, SqlxError> { let result = query_as!( Self, - "SELECT device_id, wireguard_network_id, wireguard_ip as \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at \ + "SELECT device_id, wireguard_network_id, wireguard_ip \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at \ FROM wireguard_network_device WHERE device_id = $1", device_id ) .fetch_all(pool) .await?; - if !result.is_empty() { - return Ok(Some(result)); - } - Ok(None) + + Ok(if result.is_empty() { + None + } else { + Some(result) + }) } - pub async fn all_for_network<'e, E>( - executor: E, - network_id: i64, - ) -> Result, SqlxError> + pub async fn all_for_network<'e, E>(executor: E, network_id: Id) -> Result, SqlxError> where E: PgExecutor<'e>, { let res = query_as!( Self, - "SELECT device_id, wireguard_network_id, wireguard_ip as \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at \ + "SELECT device_id, wireguard_network_id, wireguard_ip \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at \ FROM wireguard_network_device \ WHERE wireguard_network_id = $1", network_id ) .fetch_all(executor) .await?; + Ok(res) } } -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum DeviceError { #[error("Device {0} pubkey is the same as gateway pubkey for network {1}")] - PubkeyConflict(Device, String), + PubkeyConflict(Device, String), #[error("Database error")] DatabaseError(#[from] sqlx::Error), #[error("Model error")] @@ -331,30 +334,28 @@ pub enum DeviceError { impl Device { #[must_use] - pub fn new(name: String, wireguard_pubkey: String, user_id: i64) -> Self { + pub fn new(name: String, wireguard_pubkey: String, user_id: Id) -> Self { Self { - id: None, + id: NoId, name, wireguard_pubkey, user_id, created: Utc::now().naive_utc(), } } +} - pub fn get_id(&self) -> Result { - let id = self.id.ok_or(ModelError::IdNotSet)?; - Ok(id) - } - +impl Device { pub fn update_from(&mut self, other: ModifyDevice) { self.name = other.name; self.wireguard_pubkey = other.wireguard_pubkey; } - /// Create wireguard config for device + + /// Create WireGuard config for device. #[must_use] pub fn create_config( &self, - network: &WireguardNetwork, + network: &WireguardNetwork, wireguard_network_device: &WireguardNetworkDevice, ) -> String { let dns = match &network.dns { @@ -400,14 +401,14 @@ impl Device { pub async fn find_by_ip<'e, E>( executor: E, ip: IpAddr, - network_id: i64, + network_id: Id, ) -> Result, SqlxError> where E: PgExecutor<'e>, { query_as!( Self, - "SELECT d.id \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created \ + "SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created \ FROM device d \ JOIN wireguard_network_device wnd \ ON d.id = wnd.device_id \ @@ -425,7 +426,7 @@ impl Device { { query_as!( Self, - "SELECT id \"id?\", name, wireguard_pubkey, user_id, created \ + "SELECT id, name, wireguard_pubkey, user_id, created \ FROM device WHERE wireguard_pubkey = $1", pubkey ) @@ -434,13 +435,13 @@ impl Device { } pub async fn find_by_id_and_username( - pool: &DbPool, - id: i64, + pool: &PgPool, + id: Id, username: &str, ) -> Result, SqlxError> { query_as!( Self, - "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created \ + "SELECT device.id, name, wireguard_pubkey, user_id, created \ FROM device JOIN \"user\" ON device.user_id = \"user\".id \ WHERE device.id = $1 AND \"user\".username = $2", id, @@ -451,13 +452,13 @@ impl Device { } pub async fn find_by_id_and_user_id( - pool: &DbPool, - id: i64, - user_id: i64, + pool: &PgPool, + id: Id, + user_id: Id, ) -> Result, SqlxError> { query_as!( Self, - "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created \ + "SELECT device.id, name, wireguard_pubkey, user_id, created \ FROM device JOIN \"user\" ON device.user_id = \"user\".id \ WHERE device.id = $1 AND \"user\".id = $2", id, @@ -467,31 +468,24 @@ impl Device { .await } - pub async fn get_ip( - &self, - pool: &DbPool, - network_id: i64, - ) -> Result, SqlxError> { - if let Some(device_id) = self.id { - let result = query!( - "SELECT wireguard_ip \ - FROM wireguard_network_device \ - WHERE device_id = $1 AND wireguard_network_id = $2", - device_id, - network_id - ) - .fetch_one(pool) - .await?; - return Ok(Some(result.wireguard_ip.to_string())); - } + pub async fn get_ip(&self, pool: &PgPool, network_id: Id) -> Result, SqlxError> { + let result = query!( + "SELECT wireguard_ip \ + FROM wireguard_network_device \ + WHERE device_id = $1 AND wireguard_network_id = $2", + self.id, + network_id + ) + .fetch_one(pool) + .await?; - Ok(None) + Ok(Some(result.wireguard_ip.to_string())) } - pub async fn all_for_username(pool: &DbPool, username: &str) -> Result, SqlxError> { + pub async fn all_for_username(pool: &PgPool, username: &str) -> Result, SqlxError> { query_as!( Self, - "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created \ + "SELECT device.id, name, wireguard_pubkey, user_id, created \ FROM device JOIN \"user\" ON device.user_id = \"user\".id \ WHERE \"user\".username = $1", username @@ -519,18 +513,9 @@ impl Device { if network.pubkey == self.wireguard_pubkey { return Err(DeviceError::PubkeyConflict(self.clone(), network.name)); } - - let Some(network_id) = network.id else { - return Err(DeviceError::Unexpected("Network has no ID".to_string())); - }; - - if WireguardNetworkDevice::find( - &mut *transaction, - self.id.expect("Device has no ID"), - network_id, - ) - .await? - .is_some() + if WireguardNetworkDevice::find(&mut *transaction, self.id, network.id) + .await? + .is_some() { debug!("Device {self} already has an IP within network {network}. Skipping...",); continue; @@ -545,7 +530,7 @@ impl Device { wireguard_network_device.wireguard_ip, self.name, self.user_id ); let device_network_info = DeviceNetworkInfo { - network_id, + network_id: network.id, device_wireguard_ip: wireguard_network_device.wireguard_ip, preshared_key: wireguard_network_device.preshared_key.clone(), is_authorized: wireguard_network_device.is_authorized, @@ -554,7 +539,7 @@ impl Device { let config = self.create_config(&network, &wireguard_network_device); configs.push(DeviceConfig { - network_id, + network_id: network.id, network_name: network.name, config, endpoint: format!("{}:{}", network.endpoint, network.port), @@ -574,12 +559,9 @@ impl Device { pub async fn assign_network_ip( &self, transaction: &mut PgConnection, - network: &WireguardNetwork, + network: &WireguardNetwork, reserved_ips: Option<&[IpAddr]>, ) -> Result { - let Some(network_id) = network.id else { - return Err(ModelError::CannotCreate); - }; let net_ip = network.address.ip(); let net_network = network.address.network(); let net_broadcast = network.address.broadcast(); @@ -594,13 +576,12 @@ impl Device { } // Break loop if IP is unassigned and return network device - if Self::find_by_ip(&mut *transaction, ip, network_id) + if Self::find_by_ip(&mut *transaction, ip, network.id) .await? .is_none() { info!("Created IP: {ip} for device: {}", self.name); - let wireguard_network_device = - WireguardNetworkDevice::new(network_id, self.get_id()?, ip); + let wireguard_network_device = WireguardNetworkDevice::new(network.id, self.id, ip); wireguard_network_device.insert(&mut *transaction).await?; return Ok(wireguard_network_device); } @@ -626,18 +607,15 @@ mod test { use super::*; use crate::db::User; - impl Device { + impl Device { /// Create new device and assign IP in a given network pub async fn new_with_ip( - pool: &DbPool, - user_id: i64, + pool: &PgPool, + user_id: Id, name: String, pubkey: String, - network: &WireguardNetwork, + network: &WireguardNetwork, ) -> Result<(Self, WireguardNetworkDevice), ModelError> { - let Some(network_id) = network.id else { - return Err(ModelError::CannotCreate); - }; let net_ip = network.address.ip(); let net_network = network.address.network(); let net_broadcast = network.address.broadcast(); @@ -646,15 +624,19 @@ mod test { continue; } // Break loop if IP is unassigned and return device - if Self::find_by_ip(pool, ip, network_id).await?.is_none() { - let mut device = Self::new(name.clone(), pubkey, user_id); - device.save(pool).await?; + if Device::find_by_ip(pool, ip, network.id).await?.is_none() { + let device = Device::new(name.clone(), pubkey, user_id) + .save(pool) + .await?; info!("Created device: {}", device.name); debug!("For user: {}", device.user_id); let wireguard_network_device = - WireguardNetworkDevice::new(network_id, device.id.unwrap(), ip); + WireguardNetworkDevice::new(network.id, device.id, ip); wireguard_network_device.insert(pool).await?; - info!("Assigned IP: {ip} for device: {name} in network: {network_id}"); + info!( + "Assigned IP: {ip} for device: {name} in network: {}", + network.id + ); return Ok((device, wireguard_network_device)); } } @@ -663,29 +645,26 @@ mod test { } #[sqlx::test] - async fn test_assign_device_ip(pool: DbPool) { + async fn test_assign_device_ip(pool: PgPool) { let mut network = WireguardNetwork::default(); network.try_set_address("10.1.1.1/30").unwrap(); - network.save(&pool).await.unwrap(); + let network = network.save(&pool).await.unwrap(); - let mut user = User::new( + let user = User::new( "testuser", Some("hunter2"), "Tester", "Test", "test@test.com", None, - ); - user.save(&pool).await.unwrap(); - let (_device, wireguard_network_device) = Device::new_with_ip( - &pool, - user.id.unwrap(), - "dev1".into(), - "key1".into(), - &network, ) + .save(&pool) .await .unwrap(); + let (_device, wireguard_network_device) = + Device::new_with_ip(&pool, user.id, "dev1".into(), "key1".into(), &network) + .await + .unwrap(); assert_eq!( wireguard_network_device.wireguard_ip.to_string(), "10.1.1.2" diff --git a/src/db/models/device_login.rs b/src/db/models/device_login.rs index 39ca24433..5cbc371b8 100644 --- a/src/db/models/device_login.rs +++ b/src/db/models/device_login.rs @@ -1,16 +1,16 @@ -use std::fmt::{self, Display, Formatter}; +use std::fmt; use chrono::{NaiveDateTime, Utc}; use model_derive::Model; -use sqlx::{query_as, Error as SqlxError}; +use sqlx::{query_as, Error as SqlxError, PgPool}; -use crate::db::DbPool; +use crate::db::{Id, NoId}; #[derive(Clone, Deserialize, Model, Serialize, Debug)] #[table(device_login_event)] -pub struct DeviceLoginEvent { - id: Option, - pub user_id: i64, +pub struct DeviceLoginEvent { + id: I, + pub user_id: Id, pub ip_address: String, pub model: Option, pub family: String, @@ -21,19 +21,22 @@ pub struct DeviceLoginEvent { pub created: NaiveDateTime, } -impl Display for DeviceLoginEvent { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self.id { - Some(device_id) => write!(f, "[ID {}] {}", device_id, self.family), - None => write!(f, "{}", self.family), - } +impl fmt::Display for DeviceLoginEvent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.family) + } +} + +impl fmt::Display for DeviceLoginEvent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[ID {}] {}", self.id, self.family) } } impl DeviceLoginEvent { #[must_use] pub fn new( - user_id: i64, + user_id: Id, ip_address: String, model: Option, family: String, @@ -43,7 +46,7 @@ impl DeviceLoginEvent { event_type: String, ) -> Self { Self { - id: None, + id: NoId, user_id, ip_address, model, @@ -56,29 +59,31 @@ impl DeviceLoginEvent { } } - pub async fn find_device_login_event(&self, pool: &DbPool) -> Result, SqlxError> { - query_as!( - Self, - "SELECT id \"id?\", user_id, ip_address, model, family, brand, os_family, browser, event_type, created - FROM device_login_event WHERE user_id = $1 AND event_type = $2 AND family = $3 AND \ - brand = $4 AND model = $5 AND browser = $6", - self.user_id, self.event_type, self.family, self.brand, self.model, self.browser - ) - .fetch_optional(pool) - .await - } - - pub async fn check_if_device_already_logged_in( - mut self, - pool: &DbPool, - ) -> Result, anyhow::Error> { + pub(crate) async fn check_if_device_already_logged_in( + self, + pool: &PgPool, + ) -> Result>, anyhow::Error> { let existing_login_event = self.find_device_login_event(pool).await?; if existing_login_event.is_none() { - self.save(pool).await?; - Ok(Some(self)) + Ok(Some(self.save(pool).await?)) } else { Ok(None) } } + + pub async fn find_device_login_event( + &self, + pool: &PgPool, + ) -> Result>, SqlxError> { + query_as!( + DeviceLoginEvent::, + "SELECT id, user_id, ip_address, model, family, brand, os_family, browser, event_type, created \ + FROM device_login_event WHERE user_id = $1 AND event_type = $2 AND family = $3 AND \ + brand = $4 AND model = $5 AND browser = $6", + self.user_id, self.event_type, self.family, self.brand, self.model, self.browser + ) + .fetch_optional(pool) + .await + } } diff --git a/src/db/models/enrollment.rs b/src/db/models/enrollment.rs index 3f2cdc0dd..c4b871adb 100644 --- a/src/db/models/enrollment.rs +++ b/src/db/models/enrollment.rs @@ -1,13 +1,14 @@ use chrono::{Duration, NaiveDateTime, Utc}; use reqwest::Url; -use sqlx::{query, query_as, Error as SqlxError, PgConnection, PgExecutor}; +use sqlx::{query, query_as, Error as SqlxError, PgConnection, PgExecutor, PgPool}; use tera::{Context, Tera}; use thiserror::Error; use tokio::sync::mpsc::UnboundedSender; use tonic::{Code, Status}; -use super::{settings::Settings, DbPool, User}; +use super::{settings::Settings, User}; use crate::{ + db::Id, mail::Mail, random::gen_alphanumeric, server_config, @@ -80,7 +81,7 @@ impl From for Status { #[derive(Clone, Debug)] pub struct Token { pub id: String, - pub user_id: i64, + pub user_id: Id, pub admin_id: Option, pub email: Option, pub created_at: NaiveDateTime, @@ -92,8 +93,8 @@ pub struct Token { impl Token { #[must_use] pub fn new( - user_id: i64, - admin_id: Option, + user_id: Id, + admin_id: Option, email: Option, token_timeout_seconds: u64, token_type: Option, @@ -191,7 +192,7 @@ impl Token { } } - pub async fn find_by_id(pool: &DbPool, id: &str) -> Result { + pub async fn find_by_id(pool: &PgPool, id: &str) -> Result { if let Some(enrollment) = query_as!( Self, "SELECT id, user_id, admin_id, email, created_at, expires_at, used_at, token_type \ @@ -209,7 +210,7 @@ impl Token { } } - pub async fn fetch_all(pool: &DbPool) -> Result, TokenError> { + pub async fn fetch_all(pool: &PgPool) -> Result, TokenError> { let tokens = query_as!( Self, "SELECT id, user_id, admin_id, email, created_at, expires_at, used_at, token_type \ @@ -220,7 +221,7 @@ impl Token { Ok(tokens) } - pub async fn fetch_user<'e, E>(&self, executor: E) -> Result + pub async fn fetch_user<'e, E>(&self, executor: E) -> Result, TokenError> where E: PgExecutor<'e>, { @@ -230,10 +231,11 @@ impl Token { return Err(TokenError::UserNotFound); }; debug!("Fetched user {user:?}."); + Ok(user) } - pub async fn fetch_admin<'e, E>(&self, executor: E) -> Result, TokenError> + pub async fn fetch_admin<'e, E>(&self, executor: E) -> Result>, TokenError> where E: PgExecutor<'e>, { @@ -247,12 +249,13 @@ impl Token { debug!("Trying to find admin using id {admin_id}"); let user = User::find_by_id(executor, admin_id).await?; debug!("Fetched admin {user:?}."); + Ok(user) } pub async fn delete_unused_user_tokens( transaction: &mut PgConnection, - user_id: i64, + user_id: Id, ) -> Result<(), TokenError> { debug!("Deleting unused tokens for the user."); let result = query!( @@ -273,7 +276,7 @@ impl Token { pub async fn delete_unused_user_password_reset_tokens( transaction: &mut PgConnection, - user_id: i64, + user_id: Id, ) -> Result<(), TokenError> { debug!("Deleting unused password reset tokens for user {user_id}"); let result = query!( @@ -374,14 +377,14 @@ impl Token { } } -impl User { +impl User { /// Start user enrollment process /// This creates a new enrollment token valid for 24h /// and optionally sends enrollment email notification to user pub async fn start_enrollment( &self, transaction: &mut PgConnection, - admin: &User, + admin: &User, email: Option, token_timeout_seconds: u64, enrollment_service_url: Url, @@ -414,16 +417,13 @@ impl User { return Err(TokenError::UserDisabled); } - let user_id = self.id.expect("User without ID"); - let admin_id = admin.id.expect("Admin user without ID"); - self.clear_unused_enrollment_tokens(&mut *transaction) .await?; debug!("Create a new enrollment token for user {}.", self.username); let enrollment = Token::new( - user_id, - Some(admin_id), + self.id, + Some(admin.id), email.clone(), token_timeout_seconds, Some(ENROLLMENT_TOKEN_TYPE.to_string()), @@ -484,13 +484,14 @@ impl User { Ok(enrollment.id) } + /// Start user remote desktop configuration process /// This creates a new enrollment token valid for 24h /// and optionally sends email notification to user pub async fn start_remote_desktop_configuration( &self, transaction: &mut PgConnection, - admin: &User, + admin: &User, email: Option, token_timeout_seconds: u64, enrollment_service_url: Url, @@ -506,9 +507,6 @@ impl User { self.username, send_user_notification ); - let user_id = self.id.expect("User without ID"); - let admin_id = admin.id.expect("Admin user without ID"); - debug!("Verify that {} is an active user.", self.username); if !self.is_active { warn!( @@ -527,8 +525,8 @@ impl User { self.username ); let desktop_configuration = Token::new( - user_id, - Some(admin_id), + self.id, + Some(admin.id), email.clone(), token_timeout_seconds, Some(ENROLLMENT_TOKEN_TYPE.to_string()), @@ -595,21 +593,8 @@ impl User { transaction: &mut PgConnection, ) -> Result<(), TokenError> { info!("Removing unused tokens for user {}.", self.username); - Token::delete_unused_user_tokens(transaction, self.id.expect("Missing user ID")).await + Token::delete_unused_user_tokens(transaction, self.id).await } - - // pub async fn request_password_reset( - // &self, - // transaction: &mut PgConnection, - // admin: &User, - // // email: Option, - // token_timeout_seconds: u64, - // // enrollment_service_url: Url, - // // send_user_notification: bool, - // mail_tx: UnboundedSender, - // ) -> Result { - - // } } impl Settings { diff --git a/src/db/models/group.rs b/src/db/models/group.rs index b2e1f6e72..99ece048e 100644 --- a/src/db/models/group.rs +++ b/src/db/models/group.rs @@ -3,13 +3,13 @@ use sqlx::{query, query_as, query_scalar, Error as SqlxError, PgConnection, PgEx use utoipa::ToSchema; use crate::{ - db::{models::error::ModelError, User, WireguardNetwork}, + db::{models::error::ModelError, Id, NoId, User, WireguardNetwork}, server_config, }; -#[derive(Model, Debug, ToSchema)] -pub struct Group { - pub(crate) id: Option, +#[derive(Debug, Model, ToSchema)] +pub struct Group { + pub(crate) id: I, pub name: String, } @@ -17,61 +17,51 @@ impl Group { #[must_use] pub fn new>(name: S) -> Self { Self { - id: None, + id: NoId, name: name.into(), } } +} +impl Group { pub async fn find_by_name<'e, E>(executor: E, name: &str) -> Result, SqlxError> where E: PgExecutor<'e>, { - query_as!( - Self, - "SELECT id \"id?\", name FROM \"group\" WHERE name = $1", - name - ) - .fetch_optional(executor) - .await + query_as!(Self, "SELECT id, name FROM \"group\" WHERE name = $1", name) + .fetch_optional(executor) + .await } pub async fn member_usernames<'e, E>(&self, executor: E) -> Result, SqlxError> where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_scalar!( - "SELECT \"user\".username FROM \"user\" JOIN group_user ON \"user\".id = group_user.user_id \ - WHERE group_user.group_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_scalar!( + "SELECT \"user\".username FROM \"user\" JOIN group_user ON \"user\".id = group_user.user_id \ + WHERE group_user.group_id = $1", + self.id + ) + .fetch_all(executor) + .await } - pub async fn members<'e, E>(&self, executor: E) -> Result, SqlxError> + pub async fn members<'e, E>(&self, executor: E) -> Result>, SqlxError> where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_as!( - User, - "SELECT \"user\".id \"id?\", username, password_hash, last_name, first_name, email, \ - phone, mfa_enabled, totp_enabled, totp_secret, email_mfa_enabled, email_mfa_secret, \ - mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ - FROM \"user\" \ - JOIN group_user ON \"user\".id = group_user.user_id \ - WHERE group_user.group_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_as!( + User, + "SELECT \"user\".id, username, password_hash, last_name, first_name, email, \ + phone, mfa_enabled, totp_enabled, totp_secret, email_mfa_enabled, email_mfa_secret, \ + mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ + FROM \"user\" \ + JOIN group_user ON \"user\".id = group_user.user_id \ + WHERE group_user.group_id = $1", + self.id + ) + .fetch_all(executor) + .await } /// Fetches a list of VPN locations where a given group is explicitly allowed. @@ -81,21 +71,17 @@ impl Group { where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_scalar!( - "SELECT wn.name FROM wireguard_network wn JOIN wireguard_network_allowed_group wnag ON wn.id = wnag.network_id \ - WHERE wnag.group_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_scalar!( + "SELECT wn.name FROM wireguard_network wn JOIN wireguard_network_allowed_group wnag ON wn.id = wnag.network_id \ + WHERE wnag.group_id = $1", + self.id + ) + .fetch_all(executor) + .await } } -impl WireguardNetwork { +impl WireguardNetwork { /// Fetch a list of all allowed groups for a given network from DB pub async fn fetch_allowed_groups<'e, E>(&self, executor: E) -> Result, ModelError> where @@ -109,6 +95,7 @@ impl WireguardNetwork { ) .fetch_all(executor) .await?; + Ok(groups) } @@ -231,12 +218,11 @@ impl WireguardNetwork { #[cfg(test)] mod test { use super::*; - use crate::db::{DbPool, User}; + use crate::db::{PgPool, User}; #[sqlx::test] - async fn test_group(pool: DbPool) { - let mut group = Group::new("worker"); - group.save(&pool).await.unwrap(); + async fn test_group(pool: PgPool) { + let group = Group::new("worker").save(&pool).await.unwrap(); let fetched_group = Group::find_by_name(&pool, "worker").await.unwrap(); assert!(fetched_group.is_some()); @@ -252,19 +238,19 @@ mod test { } #[sqlx::test] - async fn test_group_members(pool: DbPool) { - let mut group = Group::new("worker"); - group.save(&pool).await.unwrap(); - - let mut user = User::new( + async fn test_group_members(pool: PgPool) { + let group = Group::new("worker").save(&pool).await.unwrap(); + let user = User::new( "hpotter", Some("pass123"), "Potter", "Harry", "h.potter@hogwart.edu.uk", None, - ); - user.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); user.add_to_group(&pool, &group).await.unwrap(); let members = group.member_usernames(&pool).await.unwrap(); diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index 6f1897778..d13db81b3 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -22,14 +22,14 @@ pub mod webhook; pub mod wireguard; pub mod yubikey; -use sqlx::{query_as, Error as SqlxError, PgConnection}; +use sqlx::{query_as, Error as SqlxError, PgConnection, PgPool}; use utoipa::ToSchema; use self::{ device::UserDevice, user::{MFAMethod, User}, }; -use super::{DbPool, Group}; +use super::{Group, Id}; #[cfg(feature = "openid")] #[derive(Deserialize, Serialize)] @@ -40,32 +40,32 @@ pub struct NewOpenIDClient { pub enabled: bool, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Debug, Deserialize, Serialize)] pub struct WalletInfo { pub address: String, pub name: String, - pub chain_id: i64, + pub chain_id: Id, pub use_for_mfa: bool, } #[derive(Deserialize, Serialize, Debug, Clone)] pub struct OAuth2AuthorizedAppInfo { - pub oauth2client_id: i64, - pub user_id: i64, + pub oauth2client_id: Id, + pub user_id: Id, pub oauth2client_name: String, } /// Only `id` and `name` from [`WebAuthn`]. -#[derive(Deserialize, Serialize, Debug)] +#[derive(Debug, Deserialize, Serialize)] pub struct SecurityKey { - pub id: i64, + pub id: Id, pub name: String, } // Basic user info used in user list, etc. -#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)] +#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct UserInfo { - pub id: Option, + pub id: Id, pub username: String, pub last_name: String, pub first_name: String, @@ -82,7 +82,7 @@ pub struct UserInfo { } impl UserInfo { - pub async fn from_user(pool: &DbPool, user: &User) -> Result { + pub async fn from_user(pool: &PgPool, user: &User) -> Result { let groups = user.member_of_names(pool).await?; let authorized_apps = user.oauth2authorizedapps(pool).await?; @@ -111,7 +111,7 @@ impl UserInfo { pub(crate) async fn handle_status_change( &self, transaction: &mut PgConnection, - user: &mut User, + user: &mut User, ) -> Result { if self.is_active == user.is_active { Ok(false) @@ -131,7 +131,7 @@ impl UserInfo { pub(crate) async fn handle_user_groups( &mut self, transaction: &mut PgConnection, - user: &mut User, + user: &mut User, ) -> Result { // initialize return value let mut groups_changed = false; @@ -167,14 +167,15 @@ impl UserInfo { } /// Copy fields to [`User`]. This function is safe to call by a non-admin user. - pub fn into_user_safe_fields(self, user: &mut User) -> Result<(), SqlxError> { + pub fn into_user_safe_fields(self, user: &mut User) -> Result<(), SqlxError> { user.phone = self.phone; user.mfa_method = self.mfa_method; + Ok(()) } /// Copy fields to [`User`]. This function should be used by administrators. - pub fn into_user_all_fields(self, user: &mut User) -> Result<(), SqlxError> { + pub fn into_user_all_fields(self, user: &mut User) -> Result<(), SqlxError> { user.phone = self.phone; user.username = self.username; user.last_name = self.last_name; @@ -198,7 +199,7 @@ pub struct UserDetails { } impl UserDetails { - pub async fn from_user(pool: &DbPool, user: &User) -> Result { + pub async fn from_user(pool: &PgPool, user: &User) -> Result { let devices = user.user_devices(pool).await?; let wallets = user.wallets(pool).await?; let security_keys = user.security_keys(pool).await?; @@ -222,19 +223,15 @@ pub struct MFAInfo { } impl MFAInfo { - pub async fn for_user(pool: &DbPool, user: &User) -> Result, SqlxError> { - if let Some(id) = user.id { - query_as!( - Self, - "SELECT mfa_method \"mfa_method: _\", totp_enabled totp_available, email_mfa_enabled email_available, \ - (SELECT count(*) > 0 FROM wallet WHERE user_id = $1 AND wallet.use_for_mfa) \"web3_available!\", \ - (SELECT count(*) > 0 FROM webauthn WHERE user_id = $1) \"webauthn_available!\" \ - FROM \"user\" WHERE \"user\".id = $1", - id - ).fetch_optional(pool).await - } else { - Ok(None) - } + pub async fn for_user(pool: &PgPool, user: &User) -> Result, SqlxError> { + query_as!( + Self, + "SELECT mfa_method \"mfa_method: _\", totp_enabled totp_available, email_mfa_enabled email_available, \ + (SELECT count(*) > 0 FROM wallet WHERE user_id = $1 AND wallet.use_for_mfa) \"web3_available!\", \ + (SELECT count(*) > 0 FROM webauthn WHERE user_id = $1) \"webauthn_available!\" \ + FROM \"user\" WHERE \"user\".id = $1", + user.id + ).fetch_optional(pool).await } #[must_use] @@ -278,25 +275,23 @@ mod test { use super::*; #[sqlx::test] - async fn test_user_info(pool: DbPool) { - let mut user = User::new( + async fn test_user_info(pool: PgPool) { + let user = User::new( "hpotter", Some("pass123"), "Potter", "Harry", "h.potter@hogwart.edu.uk", None, - ); - user.save(&pool).await.unwrap(); - - let mut group1 = Group::new("Gryffindor"); - group1.save(&pool).await.unwrap(); - let mut group2 = Group::new("Hufflepuff"); - group2.save(&pool).await.unwrap(); - let mut group3 = Group::new("Ravenclaw"); - group3.save(&pool).await.unwrap(); - let mut group4 = Group::new("Slytherin"); - group4.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); + + let group1 = Group::new("Gryffindor").save(&pool).await.unwrap(); + let group2 = Group::new("Hufflepuff").save(&pool).await.unwrap(); + let group3 = Group::new("Ravenclaw").save(&pool).await.unwrap(); + let group4 = Group::new("Slytherin").save(&pool).await.unwrap(); user.add_to_group(&pool, &group1).await.unwrap(); user.add_to_group(&pool, &group2).await.unwrap(); diff --git a/src/db/models/oauth2authorizedapp.rs b/src/db/models/oauth2authorizedapp.rs index f4566ce26..094d4738d 100644 --- a/src/db/models/oauth2authorizedapp.rs +++ b/src/db/models/oauth2authorizedapp.rs @@ -1,32 +1,35 @@ use model_derive::Model; -use sqlx::{query_as, Error as SqlxError}; +use sqlx::{query_as, Error as SqlxError, PgPool}; -use super::DbPool; +use crate::db::{Id, NoId}; #[derive(Model)] -pub struct OAuth2AuthorizedApp { - pub id: Option, - pub user_id: i64, - pub oauth2client_id: i64, +pub struct OAuth2AuthorizedApp { + pub id: I, + pub user_id: Id, + pub oauth2client_id: Id, } impl OAuth2AuthorizedApp { #[must_use] - pub fn new(user_id: i64, oauth2client_id: i64) -> Self { + pub fn new(user_id: Id, oauth2client_id: Id) -> Self { Self { - id: None, + id: NoId, user_id, oauth2client_id, } } +} + +impl OAuth2AuthorizedApp { pub async fn find_by_user_and_oauth2client_id( - pool: &DbPool, - user_id: i64, - oauth2client_id: i64, + pool: &PgPool, + user_id: Id, + oauth2client_id: Id, ) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", user_id, oauth2client_id \ + "SELECT id, user_id, oauth2client_id \ FROM oauth2authorizedapp WHERE user_id = $1 AND oauth2client_id = $2", user_id, oauth2client_id diff --git a/src/db/models/oauth2client.rs b/src/db/models/oauth2client.rs index 113d92be6..530e73fff 100644 --- a/src/db/models/oauth2client.rs +++ b/src/db/models/oauth2client.rs @@ -1,12 +1,15 @@ use model_derive::Model; -use sqlx::{query_as, Error as SqlxError}; +use sqlx::{query_as, Error as SqlxError, PgPool}; -use super::{DbPool, NewOpenIDClient}; -use crate::random::gen_alphanumeric; +use super::NewOpenIDClient; +use crate::{ + db::{Id, NoId}, + random::gen_alphanumeric, +}; #[derive(Deserialize, Model, Serialize)] -pub struct OAuth2Client { - pub id: Option, +pub struct OAuth2Client { + pub id: I, pub client_id: String, // unique pub client_secret: String, #[model(ref)] @@ -24,7 +27,7 @@ impl OAuth2Client { let client_id = gen_alphanumeric(16); let client_secret = gen_alphanumeric(32); Self { - id: None, + id: NoId, client_id, client_secret, redirect_uri, @@ -39,7 +42,7 @@ impl OAuth2Client { let client_id = gen_alphanumeric(16); let client_secret = gen_alphanumeric(32); Self { - id: None, + id: NoId, client_id, client_secret, redirect_uri: new.redirect_uri, @@ -48,15 +51,17 @@ impl OAuth2Client { enabled: new.enabled, } } +} +impl OAuth2Client { /// Find client by 'client_id`. pub async fn find_by_client_id( - pool: &DbPool, + pool: &PgPool, client_id: &str, ) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", client_id, client_secret, redirect_uri, scope, name, enabled \ + "SELECT id, client_id, client_secret, redirect_uri, scope, name, enabled \ FROM oauth2client WHERE client_id = $1", client_id ) @@ -66,13 +71,13 @@ impl OAuth2Client { /// Find using `client_id` and `client_secret`; must be `enabled`. pub async fn find_by_auth( - pool: &DbPool, + pool: &PgPool, client_id: &str, client_secret: &str, ) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", client_id, client_secret, redirect_uri, scope, name, enabled \ + "SELECT id, client_id, client_secret, redirect_uri, scope, name, enabled \ FROM oauth2client WHERE client_id = $1 AND client_secret = $2 AND enabled", client_id, client_secret @@ -83,12 +88,12 @@ impl OAuth2Client { /// Find enabled client by `client_id`. pub async fn find_enabled_for_client_id( - pool: &DbPool, + pool: &PgPool, client_id: &str, ) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", client_id, client_secret, redirect_uri, scope, name, enabled \ + "SELECT id, client_id, client_secret, redirect_uri, scope, name, enabled \ FROM oauth2client WHERE client_id = $1 AND enabled", client_id ) @@ -105,8 +110,8 @@ pub struct OAuth2ClientSafe { pub scope: Vec, } -impl From for OAuth2ClientSafe { - fn from(client: OAuth2Client) -> Self { +impl From> for OAuth2ClientSafe { + fn from(client: OAuth2Client) -> Self { OAuth2ClientSafe { client_id: client.client_id, name: client.name, diff --git a/src/db/models/oauth2token.rs b/src/db/models/oauth2token.rs index 94c5e10a6..ad4b9624c 100644 --- a/src/db/models/oauth2token.rs +++ b/src/db/models/oauth2token.rs @@ -1,11 +1,10 @@ use chrono::{Duration, Utc}; -use sqlx::{query, query_as, Error as SqlxError}; +use sqlx::{query, query_as, Error as SqlxError, PgPool}; -use super::DbPool; -use crate::{random::gen_alphanumeric, server_config}; +use crate::{db::Id, random::gen_alphanumeric, server_config}; pub struct OAuth2Token { - pub oauth2authorizedapp_id: i64, + pub oauth2authorizedapp_id: Id, pub access_token: String, pub refresh_token: String, pub redirect_uri: String, @@ -15,7 +14,7 @@ pub struct OAuth2Token { impl OAuth2Token { #[must_use] - pub fn new(oauth2authorizedapp_id: i64, redirect_uri: String, scope: String) -> Self { + pub fn new(oauth2authorizedapp_id: Id, redirect_uri: String, scope: String) -> Self { let timeout = server_config().session_timeout; let expiration = Utc::now() + Duration::seconds(timeout.as_secs() as i64); Self { @@ -29,7 +28,7 @@ impl OAuth2Token { } /// Generate new access token, scratching the old one. Changes are reflected in the database. - pub async fn refresh_and_save(&mut self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn refresh_and_save(&mut self, pool: &PgPool) -> Result<(), SqlxError> { let timeout = server_config().session_timeout; let new_access_token = gen_alphanumeric(24); let new_refresh_token = gen_alphanumeric(24); @@ -56,7 +55,7 @@ impl OAuth2Token { } /// Store data in the database. - pub async fn save(&self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn save(&self, pool: &PgPool) -> Result<(), SqlxError> { query!( "INSERT INTO oauth2token (oauth2authorizedapp_id, access_token, refresh_token, redirect_uri, scope, expires_in) \ VALUES ($1, $2, $3, $4, $5, $6)", @@ -72,7 +71,7 @@ impl OAuth2Token { } /// Delete token from the database. - pub async fn delete(self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn delete(self, pool: &PgPool) -> Result<(), SqlxError> { query!( "DELETE FROM oauth2token WHERE access_token = $1 AND refresh_token = $2", self.access_token, @@ -85,7 +84,7 @@ impl OAuth2Token { /// Find by access token. pub async fn find_access_token( - pool: &DbPool, + pool: &PgPool, access_token: &str, ) -> Result, SqlxError> { match query_as!( @@ -112,7 +111,7 @@ impl OAuth2Token { /// Find by refresh token. pub async fn find_refresh_token( - pool: &DbPool, + pool: &PgPool, refresh_token: &str, ) -> Result, SqlxError> { match query_as!( @@ -138,8 +137,8 @@ impl OAuth2Token { } // Find by authorized app id pub async fn find_by_authorized_app_id( - pool: &DbPool, - oauth2authorizedapp_id: i64, + pool: &PgPool, + oauth2authorizedapp_id: Id, ) -> Result, SqlxError> { match query_as!( Self, diff --git a/src/db/models/polling_token.rs b/src/db/models/polling_token.rs index d782afc89..cd20e62ee 100644 --- a/src/db/models/polling_token.rs +++ b/src/db/models/polling_token.rs @@ -1,34 +1,38 @@ use chrono::{NaiveDateTime, Utc}; use model_derive::Model; -use sqlx::{query_as, Error as SqlxError, PgExecutor}; +use sqlx::{query_as, Error as SqlxError, PgExecutor, PgPool}; -use super::DbPool; -use crate::random::gen_alphanumeric; +use crate::{ + db::{Id, NoId}, + random::gen_alphanumeric, +}; // Token used for polling requests. #[derive(Clone, Debug, Model)] -pub struct PollingToken { - pub id: Option, +pub struct PollingToken { + pub id: I, pub token: String, - pub device_id: i64, + pub device_id: Id, pub created_at: NaiveDateTime, } impl PollingToken { #[must_use] - pub fn new(device_id: i64) -> Self { + pub fn new(device_id: Id) -> Self { Self { - id: None, + id: NoId, device_id, token: gen_alphanumeric(32), created_at: Utc::now().naive_utc(), } } +} - pub async fn find(pool: &DbPool, token: &str) -> Result, SqlxError> { +impl PollingToken { + pub async fn find(pool: &PgPool, token: &str) -> Result, SqlxError> { query_as!( Self, - "SELECT id, token, device_id, created_at + "SELECT id, token, device_id, created_at \ FROM pollingtoken WHERE token = $1", token ) @@ -36,7 +40,7 @@ impl PollingToken { .await } - pub async fn delete_for_device_id<'e, E>(executor: E, device_id: i64) -> Result<(), SqlxError> + pub async fn delete_for_device_id<'e, E>(executor: E, device_id: Id) -> Result<(), SqlxError> where E: PgExecutor<'e>, { diff --git a/src/db/models/session.rs b/src/db/models/session.rs index db80fcbb6..4b11068f7 100644 --- a/src/db/models/session.rs +++ b/src/db/models/session.rs @@ -1,9 +1,8 @@ use chrono::{Duration, NaiveDateTime, Utc}; -use sqlx::{query, query_as, Error as SqlxError, PgExecutor, Type}; +use sqlx::{query, query_as, Error as SqlxError, PgExecutor, PgPool, Type}; use webauthn_rs::prelude::{PasskeyAuthentication, PasskeyRegistration}; -use super::DbPool; -use crate::{random::gen_alphanumeric, server_config}; +use crate::{db::Id, random::gen_alphanumeric, server_config}; #[derive(Clone, PartialEq, Type)] #[repr(i16)] @@ -18,7 +17,7 @@ pub enum SessionState { #[derive(Clone)] pub struct Session { pub id: String, - pub user_id: i64, + pub user_id: Id, pub state: SessionState, pub created: NaiveDateTime, pub expires: NaiveDateTime, @@ -31,7 +30,7 @@ pub struct Session { impl Session { #[must_use] pub fn new( - user_id: i64, + user_id: Id, state: SessionState, ip_address: String, device_info: Option, @@ -56,7 +55,7 @@ impl Session { self.expires < Utc::now().naive_utc() } - pub async fn find_by_id(pool: &DbPool, id: &str) -> Result, SqlxError> { + pub async fn find_by_id(pool: &PgPool, id: &str) -> Result, SqlxError> { query_as!( Self, "SELECT id, user_id, state \"state: SessionState\", created, expires, webauthn_challenge, \ @@ -67,7 +66,7 @@ impl Session { .await } - pub async fn save(&self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn save(&self, pool: &PgPool) -> Result<(), SqlxError> { query!( "INSERT INTO session (id, user_id, state, created, expires, webauthn_challenge, web3_challenge, ip_address, device_info) \ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", @@ -83,10 +82,11 @@ impl Session { ) .execute(pool) .await?; + Ok(()) } - pub async fn set_state(&mut self, pool: &DbPool, state: SessionState) -> Result<(), SqlxError> { + pub async fn set_state(&mut self, pool: &PgPool, state: SessionState) -> Result<(), SqlxError> { query!( "UPDATE session SET state = $1 WHERE id = $2", state.clone() as i16, @@ -95,6 +95,7 @@ impl Session { .execute(pool) .await?; self.state = state; + Ok(()) } @@ -130,6 +131,7 @@ impl Session { .await?; self.webauthn_challenge = Some(webauthn_challenge); } + Ok(()) } @@ -151,6 +153,7 @@ impl Session { .await?; self.webauthn_challenge = Some(webauthn_challenge); } + Ok(()) } @@ -170,6 +173,7 @@ impl Session { .execute(executor) .await?; self.web3_challenge = Some(web3_challenge); + Ok(()) } @@ -180,6 +184,7 @@ impl Session { query!("DELETE FROM session WHERE id = $1", self.id) .execute(executor) .await?; + Ok(()) } @@ -190,6 +195,7 @@ impl Session { query!("DELETE FROM session WHERE expires < now()",) .execute(executor) .await?; + Ok(()) } @@ -200,6 +206,7 @@ impl Session { query!("DELETE FROM session WHERE user_id = $1", user_id) .execute(executor) .await?; + Ok(()) } } diff --git a/src/db/models/settings.rs b/src/db/models/settings.rs index 569db58ef..8d4c6dca5 100644 --- a/src/db/models/settings.rs +++ b/src/db/models/settings.rs @@ -1,10 +1,8 @@ use std::collections::HashMap; -use model_derive::Model; -use sqlx::{query, query_as, Error as SqlxError, PgExecutor, Type}; +use sqlx::{query, query_as, PgExecutor, PgPool, Type}; use struct_patch::Patch; -use super::DbPool; use crate::secret::SecretString; #[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Type, Debug)] @@ -15,11 +13,9 @@ pub enum SmtpEncryption { ImplicitTls, } -#[derive(Debug, Clone, Model, Serialize, Deserialize, PartialEq, Patch)] -#[patch(attribute(derive(Serialize, Deserialize)))] +#[derive(Clone, Debug, Deserialize, PartialEq, Patch, Serialize)] +#[patch(attribute(derive(Deserialize, Serialize)))] pub struct Settings { - #[serde(skip)] - pub id: Option, // Modules pub openid_enabled: bool, pub wireguard_enabled: bool, @@ -34,10 +30,8 @@ pub struct Settings { // SMTP pub smtp_server: Option, pub smtp_port: Option, - #[model(enum)] pub smtp_encryption: SmtpEncryption, pub smtp_user: Option, - #[model(secret)] pub smtp_password: Option, pub smtp_sender: Option, // Enrollment @@ -52,7 +46,6 @@ pub struct Settings { // LDAP pub ldap_url: Option, pub ldap_bind_username: Option, - #[model(secret)] pub ldap_bind_password: Option, pub ldap_group_search_base: Option, pub ldap_user_search_base: Option, @@ -68,11 +61,129 @@ pub struct Settings { } impl Settings { - pub async fn get_settings<'e, E>(executor: E) -> Result + pub async fn get<'e, E>(executor: E) -> Result, sqlx::Error> + where + E: PgExecutor<'e>, + { + query_as!( + Self, + "SELECT openid_enabled, wireguard_enabled, webhooks_enabled, \ + worker_enabled, challenge_template, instance_name, main_logo_url, nav_logo_url, \ + smtp_server, smtp_port, smtp_encryption \"smtp_encryption: _\", smtp_user, \ + smtp_password \"smtp_password?: SecretString\", smtp_sender, \ + enrollment_vpn_step_optional, enrollment_welcome_message, \ + enrollment_welcome_email, enrollment_welcome_email_subject, \ + enrollment_use_welcome_message_as_email, uuid, ldap_url, ldap_bind_username, \ + ldap_bind_password \"ldap_bind_password?: SecretString\", \ + ldap_group_search_base, ldap_user_search_base, ldap_user_obj_class, \ + ldap_group_obj_class, ldap_username_attr, ldap_groupname_attr, \ + ldap_group_member_attr, ldap_member_attr, openid_create_account, \ + license \ + FROM \"settings\" WHERE id = 1", + ) + .fetch_optional(executor) + .await + } + + pub async fn save<'e, E>(&self, executor: E) -> Result<(), sqlx::Error> + where + E: PgExecutor<'e>, + { + query!( + "UPDATE \"settings\" SET \ + openid_enabled = $1, \ + wireguard_enabled = $2, \ + webhooks_enabled = $3, \ + worker_enabled = $4, \ + challenge_template = $5, \ + instance_name = $6, \ + main_logo_url = $7, \ + nav_logo_url = $8, \ + smtp_server = $9, \ + smtp_port = $10, \ + smtp_encryption = $11, \ + smtp_user = $12, \ + smtp_password = $13, \ + smtp_sender = $14, \ + enrollment_vpn_step_optional = $15, \ + enrollment_welcome_message = $16, \ + enrollment_welcome_email = $17, \ + enrollment_welcome_email_subject = $18, \ + enrollment_use_welcome_message_as_email = $19, \ + uuid = $20, \ + ldap_url = $21, \ + ldap_bind_username = $22, \ + ldap_bind_password = $23, \ + ldap_group_search_base = $24, \ + ldap_user_search_base = $25, \ + ldap_user_obj_class = $26, \ + ldap_group_obj_class = $27, \ + ldap_username_attr = $28, \ + ldap_groupname_attr = $29, \ + ldap_group_member_attr = $30, \ + ldap_member_attr = $31, \ + openid_create_account = $32, \ + license = $33 \ + WHERE id = 1", + self.openid_enabled, + self.wireguard_enabled, + self.webhooks_enabled, + self.worker_enabled, + self.challenge_template, + self.instance_name, + self.main_logo_url, + self.nav_logo_url, + self.smtp_server, + self.smtp_port, + &self.smtp_encryption as &SmtpEncryption, + self.smtp_user, + &self.smtp_password as &Option, + self.smtp_sender, + self.enrollment_vpn_step_optional, + self.enrollment_welcome_message, + self.enrollment_welcome_email, + self.enrollment_welcome_email_subject, + self.enrollment_use_welcome_message_as_email, + self.uuid, + self.ldap_url, + self.ldap_bind_username, + &self.ldap_bind_password as &Option, + self.ldap_group_search_base, + self.ldap_user_search_base, + self.ldap_user_obj_class, + self.ldap_group_obj_class, + self.ldap_username_attr, + self.ldap_groupname_attr, + self.ldap_group_member_attr, + self.ldap_member_attr, + self.openid_create_account, + self.license + ) + .execute(executor) + .await?; + + Ok(()) + } + + pub(crate) async fn save_license<'e, E>(&self, executor: E) -> Result<(), sqlx::Error> + where + E: PgExecutor<'e>, + { + query!( + "UPDATE \"settings\" SET license = $1 WHERE id = 1", + self.license, + ) + .execute(executor) + .await?; + + Ok(()) + } + + pub async fn get_settings<'e, E>(executor: E) -> Result where E: PgExecutor<'e>, { - let settings = Settings::find_by_id(executor, 1).await?; + let settings = Settings::get(executor).await?; Ok(settings.expect("Settings not found")) } @@ -80,7 +191,7 @@ impl Settings { // Set default values for settings if not set yet. // This is only relevant to a subset of settings which are nullable // and we want to initialize their values. - pub async fn init_defaults(pool: &DbPool) -> Result<(), SqlxError> { + pub async fn init_defaults(pool: &PgPool) -> Result<(), sqlx::Error> { info!("Initializing default settings"); let default_settings = HashMap::from([ @@ -125,7 +236,7 @@ pub struct SettingsEssentials { } impl SettingsEssentials { - pub(crate) async fn get_settings_essentials<'e, E>(executor: E) -> Result + pub(crate) async fn get_settings_essentials<'e, E>(executor: E) -> Result where E: PgExecutor<'e>, { diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 99ec98034..21fee1ac1 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -9,7 +9,7 @@ use argon2::{ }; use axum::http::StatusCode; use model_derive::Model; -use sqlx::{query, query_as, query_scalar, Error as SqlxError, PgExecutor, Type}; +use sqlx::{query, query_as, query_scalar, Error as SqlxError, PgExecutor, PgPool, Type}; use totp_lite::{totp_custom, Sha1}; use super::{ @@ -17,11 +17,11 @@ use super::{ group::Group, wallet::Wallet, webauthn::WebAuthn, - DbPool, MFAInfo, OAuth2AuthorizedAppInfo, SecurityKey, WalletInfo, + MFAInfo, OAuth2AuthorizedAppInfo, SecurityKey, WalletInfo, }; use crate::{ auth::{EMAIL_CODE_DIGITS, TOTP_CODE_DIGITS, TOTP_CODE_VALIDITY_PERIOD}, - db::Session, + db::{Id, NoId, Session}, error::WebError, random::{gen_alphanumeric, gen_totp_secret}, server_config, @@ -58,7 +58,7 @@ impl fmt::Display for MFAMethod { // User information ready to be sent as part of diagnostic data. #[derive(Serialize)] pub struct UserDiagnostic { - pub id: i64, + pub id: Id, pub mfa_enabled: bool, pub totp_enabled: bool, pub email_mfa_enabled: bool, @@ -67,9 +67,9 @@ pub struct UserDiagnostic { pub enrolled: bool, } -#[derive(Model, PartialEq, Serialize, Clone, Debug)] -pub struct User { - pub id: Option, +#[derive(Clone, Debug, Model, PartialEq, Serialize)] +pub struct User { + pub id: I, pub username: String, pub(crate) password_hash: Option, pub last_name: String, @@ -92,14 +92,14 @@ pub struct User { pub(crate) recovery_codes: Vec, } -impl User { - fn hash_password(password: &str) -> Result { - let salt = SaltString::generate(&mut OsRng); - Ok(Argon2::default() - .hash_password(password.as_bytes(), &salt)? - .to_string()) - } +fn hash_password(password: &str) -> Result { + let salt = SaltString::generate(&mut OsRng); + Ok(Argon2::default() + .hash_password(password.as_bytes(), &salt)? + .to_string()) +} +impl User { #[must_use] pub fn new>( username: S, @@ -109,10 +109,9 @@ impl User { email: S, phone: Option, ) -> Self { - let password_hash = - password.and_then(|password_hash| Self::hash_password(password_hash).ok()); + let password_hash = password.and_then(|password_hash| hash_password(password_hash).ok()); Self { - id: None, + id: NoId, username: username.into(), password_hash, last_name: last_name.into(), @@ -130,9 +129,11 @@ impl User { openid_sub: None, } } +} +impl User { pub fn set_password(&mut self, password: &str) { - self.password_hash = Self::hash_password(password).ok(); + self.password_hash = hash_password(password).ok(); } pub fn verify_password(&self, password: &str) -> Result<(), HashError> { @@ -165,22 +166,23 @@ impl User { pub fn is_enrolled(&self) -> bool { self.password_hash.is_some() || self.openid_sub.is_some() } +} +impl User { /// Generate new TOTP secret, save it, then return it as RFC 4648 base32-encoded string. pub async fn new_totp_secret<'e, E>(&mut self, executor: E) -> Result where E: PgExecutor<'e>, { let secret = gen_totp_secret(); - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET totp_secret = $1 WHERE id = $2", - secret, - id - ) - .execute(executor) - .await?; - } + query!( + "UPDATE \"user\" SET totp_secret = $1 WHERE id = $2", + secret, + self.id + ) + .execute(executor) + .await?; + let secret_base32 = base32::encode(base32::Alphabet::Rfc4648 { padding: false }, &secret); self.totp_secret = Some(secret); Ok(secret_base32) @@ -192,16 +194,16 @@ impl User { E: PgExecutor<'e>, { let email_secret = gen_totp_secret(); - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET email_mfa_secret = $1 WHERE id = $2", - email_secret, - id - ) - .execute(executor) - .await?; - } + query!( + "UPDATE \"user\" SET email_mfa_secret = $1 WHERE id = $2", + email_secret, + self.id + ) + .execute(executor) + .await?; + self.email_mfa_secret = Some(email_secret); + Ok(()) } @@ -217,15 +219,13 @@ impl User { "Setting MFA method for user {} to {mfa_method:?}", self.username ); - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET mfa_method = $2 WHERE id = $1", - id, - &mfa_method as &MFAMethod - ) - .execute(executor) - .await?; - } + query!( + "UPDATE \"user\" SET mfa_method = $2 WHERE id = $1", + self.id, + &mfa_method as &MFAMethod + ) + .execute(executor) + .await?; self.mfa_method = mfa_method; Ok(()) @@ -244,26 +244,22 @@ impl User { return Ok(true); } - if let Some(id) = self.id { - query_scalar!( - "SELECT totp_enabled OR email_mfa_enabled OR coalesce(bool_or(wallet.use_for_mfa), FALSE) \ - OR count(webauthn.id) > 0 \"bool!\" FROM \"user\" \ - LEFT JOIN wallet ON wallet.user_id = \"user\".id \ - LEFT JOIN webauthn ON webauthn.user_id = \"user\".id \ - WHERE \"user\".id = $1 GROUP BY totp_enabled, email_mfa_enabled;", - id - ) - .fetch_one(executor) - .await - } else { - Ok(false) - } + query_scalar!( + "SELECT totp_enabled OR email_mfa_enabled OR coalesce(bool_or(wallet.use_for_mfa), FALSE) \ + OR count(webauthn.id) > 0 \"bool!\" FROM \"user\" \ + LEFT JOIN wallet ON wallet.user_id = \"user\".id \ + LEFT JOIN webauthn ON webauthn.user_id = \"user\".id \ + WHERE \"user\".id = $1 GROUP BY totp_enabled, email_mfa_enabled;", + self.id + ) + .fetch_one(executor) + .await } /// Verify the state of mfa flags are correct. /// Recovers from invalid mfa_method /// Use this function after removing any of the authentication factors. - pub async fn verify_mfa_state(&mut self, pool: &DbPool) -> Result<(), WebError> { + pub async fn verify_mfa_state(&mut self, pool: &PgPool) -> Result<(), WebError> { if let Some(info) = MFAInfo::for_user(pool, self).await? { let factors_present = info.mfa_available(); if self.mfa_enabled != factors_present { @@ -273,15 +269,13 @@ impl User { self.disable_mfa(pool).await?; } else { // first factor was added so MFA needs to be enabled - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET mfa_enabled = $2 WHERE id = $1", - id, - factors_present - ) - .execute(pool) - .await?; - } + query!( + "UPDATE \"user\" SET mfa_enabled = $2 WHERE id = $1", + self.id, + factors_present + ) + .execute(pool) + .await?; }; if !factors_present && self.mfa_method != MFAMethod::None { @@ -321,7 +315,7 @@ impl User { } /// Enable MFA. At least one of the authenticator factors must be configured. - pub async fn enable_mfa(&mut self, pool: &DbPool) -> Result<(), WebError> { + pub async fn enable_mfa(&mut self, pool: &PgPool) -> Result<(), WebError> { if !self.mfa_enabled { self.verify_mfa_state(pool).await?; } @@ -345,38 +339,36 @@ impl User { let code = gen_alphanumeric(16); self.recovery_codes.push(code); } - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET recovery_codes = $2 WHERE id = $1", - id, - &self.recovery_codes - ) - .execute(executor) - .await?; - } + query!( + "UPDATE \"user\" SET recovery_codes = $2 WHERE id = $1", + self.id, + &self.recovery_codes + ) + .execute(executor) + .await?; Ok(Some(self.recovery_codes.clone())) } /// Disable MFA; discard recovery codes, TOTP secret, and security keys. - pub async fn disable_mfa(&mut self, pool: &DbPool) -> Result<(), SqlxError> { - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET mfa_enabled = FALSE, mfa_method = 'none', totp_enabled = FALSE, email_mfa_enabled = FALSE, \ - totp_secret = NULL, email_mfa_secret = NULL, recovery_codes = '{}' WHERE id = $1", - id - ) - .execute(pool) - .await?; - Wallet::disable_mfa_for_user(pool, id).await?; - WebAuthn::delete_all_for_user(pool, id).await?; - } + pub async fn disable_mfa(&mut self, pool: &PgPool) -> Result<(), SqlxError> { + query!( + "UPDATE \"user\" SET mfa_enabled = FALSE, mfa_method = 'none', totp_enabled = FALSE, email_mfa_enabled = FALSE, \ + totp_secret = NULL, email_mfa_secret = NULL, recovery_codes = '{}' WHERE id = $1", + self.id + ) + .execute(pool) + .await?; + Wallet::disable_mfa_for_user(pool, self.id).await?; + WebAuthn::delete_all_for_user(pool, self.id).await?; + self.totp_secret = None; self.email_mfa_secret = None; self.totp_enabled = false; self.email_mfa_enabled = false; self.mfa_method = MFAMethod::None; self.recovery_codes.clear(); + Ok(()) } @@ -386,36 +378,38 @@ impl User { E: PgExecutor<'e>, { if !self.totp_enabled { - if let Some(id) = self.id { - query!("UPDATE \"user\" SET totp_enabled = TRUE WHERE id = $1", id) - .execute(executor) - .await?; - } + query!( + "UPDATE \"user\" SET totp_enabled = TRUE WHERE id = $1", + self.id + ) + .execute(executor) + .await?; self.totp_enabled = true; } + Ok(()) } /// Disable TOTP; discard the secret. - pub async fn disable_totp(&mut self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn disable_totp(&mut self, pool: &PgPool) -> Result<(), SqlxError> { if self.totp_enabled { // FIXME: check if this flag is set correctly when TOTP is the only method self.mfa_enabled = self.check_mfa_enabled(pool).await?; self.totp_enabled = false; self.totp_secret = None; - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET mfa_enabled = $2, totp_enabled = $3 AND totp_secret = $4 \ - WHERE id = $1", - id, - self.mfa_enabled, - self.totp_enabled, - self.totp_secret, - ) - .execute(pool) - .await?; - } + + query!( + "UPDATE \"user\" SET mfa_enabled = $2, totp_enabled = $3 AND totp_secret = $4 \ + WHERE id = $1", + self.id, + self.mfa_enabled, + self.totp_enabled, + self.totp_secret, + ) + .execute(pool) + .await?; } + Ok(()) } @@ -425,49 +419,49 @@ impl User { E: PgExecutor<'e>, { if !self.email_mfa_enabled { - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET email_mfa_enabled = TRUE WHERE id = $1", - id - ) - .execute(executor) - .await?; - } + query!( + "UPDATE \"user\" SET email_mfa_enabled = TRUE WHERE id = $1", + self.id + ) + .execute(executor) + .await?; + self.email_mfa_enabled = true; } + Ok(()) } /// Disable email MFA; discard the secret. - pub async fn disable_email_mfa(&mut self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn disable_email_mfa(&mut self, pool: &PgPool) -> Result<(), SqlxError> { if self.email_mfa_enabled { self.mfa_enabled = self.check_mfa_enabled(pool).await?; self.email_mfa_enabled = false; self.email_mfa_secret = None; - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET mfa_enabled = $2, email_mfa_enabled = $3 AND email_mfa_secret = $4 \ - WHERE id = $1", - id, - self.mfa_enabled, - self.email_mfa_enabled, - self.email_mfa_secret, - ) - .execute(pool) - .await?; - } + + query!( + "UPDATE \"user\" SET mfa_enabled = $2, email_mfa_enabled = $3 AND email_mfa_secret = $4 \ + WHERE id = $1", + self.id, + self.mfa_enabled, + self.email_mfa_enabled, + self.email_mfa_secret, + ) + .execute(pool) + .await?; } + Ok(()) } /// Select all users without sensitive data. // FIXME: Remove it when Model macro will support SecretString pub async fn all_without_sensitive_data( - pool: &DbPool, + pool: &PgPool, ) -> Result, SqlxError> { let users = query!( "SELECT id, mfa_enabled, totp_enabled, email_mfa_enabled, \ - mfa_method as \"mfa_method: MFAMethod\", password_hash, is_active, openid_sub \ + mfa_method \"mfa_method: MFAMethod\", password_hash, is_active, openid_sub \ FROM \"user\"" ) .fetch_all(pool) @@ -484,28 +478,30 @@ impl User { enrolled: u.password_hash.is_some() || u.openid_sub.is_some(), }) .collect(); + Ok(res) } /// Return all members of group pub async fn find_by_group_name( - pool: &DbPool, + pool: &PgPool, group_name: &str, - ) -> Result, SqlxError> { + ) -> Result>, SqlxError> { let users = query_as!( - User, - "SELECT \"user\".id \"id?\", username, password_hash, last_name, first_name, email, \ + Self, + "SELECT \"user\".id, username, password_hash, last_name, first_name, email, \ phone, mfa_enabled, totp_enabled, totp_secret, \ email_mfa_enabled, email_mfa_secret, \ mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ - FROM \"user\" - INNER JOIN \"group_user\" ON \"user\".id = \"group_user\".user_id - INNER JOIN \"group\" ON \"group_user\".group_id = \"group\".id + FROM \"user\" \ + INNER JOIN \"group_user\" ON \"user\".id = \"group_user\".user_id \ + INNER JOIN \"group\" ON \"group_user\".group_id = \"group\".id \ WHERE \"group\".name = $1", group_name ) .fetch_all(pool) .await?; + Ok(users) } @@ -520,10 +516,10 @@ impl User { totp_secret, timestamp.as_secs(), ); - eprintln!("{expected_code} ?? {code}"); return code == expected_code; } } + false } @@ -593,21 +589,21 @@ impl User { /// Verify recovery code. If it is valid, consume it, so it can't be used again. pub async fn verify_recovery_code( &mut self, - pool: &DbPool, + pool: &PgPool, code: &str, ) -> Result { if let Some(index) = self.recovery_codes.iter().position(|c| c == code) { // Note: swap_remove() should be faster than remove(). self.recovery_codes.swap_remove(index); - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET recovery_codes = $2 WHERE id = $1", - id, - &self.recovery_codes - ) - .execute(pool) - .await?; - } + + query!( + "UPDATE \"user\" SET recovery_codes = $2 WHERE id = $1", + self.id, + &self.recovery_codes + ) + .execute(pool) + .await?; + Ok(true) } else { Ok(false) @@ -623,7 +619,7 @@ impl User { { query_as!( Self, - "SELECT id \"id?\", username, password_hash, last_name, first_name, email, \ + "SELECT id, username, password_hash, last_name, first_name, email, \ phone, mfa_enabled, totp_enabled, email_mfa_enabled, \ totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ FROM \"user\" WHERE username = $1", @@ -639,7 +635,7 @@ impl User { { query_as!( Self, - "SELECT id \"id?\", username, password_hash, last_name, first_name, email, \ + "SELECT id, username, password_hash, last_name, first_name, email, \ phone, mfa_enabled, totp_enabled, email_mfa_enabled, \ totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ FROM \"user\" WHERE email = $1", @@ -655,7 +651,7 @@ impl User { { query_as!( Self, - "SELECT id \"id?\", username, password_hash, last_name, first_name, email, \ + "SELECT id, username, password_hash, last_name, first_name, email, \ phone, mfa_enabled, totp_enabled, email_mfa_enabled, \ totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ FROM \"user\" WHERE openid_sub = $1", @@ -669,41 +665,33 @@ impl User { where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_scalar!( - "SELECT \"group\".name FROM \"group\" JOIN group_user ON \"group\".id = group_user.group_id \ - WHERE group_user.user_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_scalar!( + "SELECT \"group\".name FROM \"group\" JOIN group_user ON \"group\".id = group_user.group_id \ + WHERE group_user.user_id = $1", + self.id + ) + .fetch_all(executor) + .await } - pub async fn member_of<'e, E>(&self, executor: E) -> Result, SqlxError> + pub async fn member_of<'e, E>(&self, executor: E) -> Result>, SqlxError> where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_as!( - Group, - "SELECT id \"id?\", name FROM \"group\" JOIN group_user ON \"group\".id = group_user.group_id \ - WHERE group_user.user_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_as!( + Group, + "SELECT id, name FROM \"group\" JOIN group_user ON \"group\".id = group_user.group_id \ + WHERE group_user.user_id = $1", + self.id + ) + .fetch_all(executor) + .await } /// Returns a vector of [`UserDevice`]s (hence the name). /// [`UserDevice`] is a struct containing additional network info about a device. /// If you only need [`Device`]s, use [`User::devices()`] instead. - pub async fn user_devices(&self, pool: &DbPool) -> Result, SqlxError> { + pub async fn user_devices(&self, pool: &PgPool) -> Result, SqlxError> { let devices = self.devices(pool).await?; let mut user_devices = Vec::new(); for device in devices { @@ -711,45 +699,38 @@ impl User { user_devices.push(user_device); } } + Ok(user_devices) } /// Returns a vector of [`Device`]s related to a user. If you want to get [`UserDevice`]s (which contain additional network info), /// use [`User::user_devices()`] instead. - pub async fn devices<'e, E>(&self, executor: E) -> Result, SqlxError> + pub async fn devices<'e, E>(&self, executor: E) -> Result>, SqlxError> where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_as!( - Device, - "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created \ - FROM device WHERE user_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_as!( + Device, + "SELECT device.id, name, wireguard_pubkey, user_id, created \ + FROM device WHERE user_id = $1", + self.id + ) + .fetch_all(executor) + .await } pub async fn wallets<'e, E>(&self, executor: E) -> Result, SqlxError> where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_as!( - WalletInfo, - "SELECT address \"address!\", name, chain_id, use_for_mfa \ - FROM wallet WHERE user_id = $1 AND validation_timestamp IS NOT NULL", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_as!( + WalletInfo, + "SELECT address \"address!\", name, chain_id, use_for_mfa \ + FROM wallet WHERE user_id = $1 AND validation_timestamp IS NOT NULL", + self.id + ) + .fetch_all(executor) + .await } pub async fn oauth2authorizedapps<'e, E>( @@ -759,71 +740,61 @@ impl User { where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_as!( - OAuth2AuthorizedAppInfo, - "SELECT oauth2client.id \"oauth2client_id!\", oauth2client.name \"oauth2client_name\", \ - oauth2authorizedapp.user_id \"user_id\" \ - FROM oauth2authorizedapp \ - JOIN oauth2client ON oauth2client.id = oauth2authorizedapp.oauth2client_id \ - WHERE oauth2authorizedapp.user_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_as!( + OAuth2AuthorizedAppInfo, + "SELECT oauth2client.id \"oauth2client_id!\", oauth2client.name \"oauth2client_name\", \ + oauth2authorizedapp.user_id \"user_id\" \ + FROM oauth2authorizedapp \ + JOIN oauth2client ON oauth2client.id = oauth2authorizedapp.oauth2client_id \ + WHERE oauth2authorizedapp.user_id = $1", + self.id + ) + .fetch_all(executor) + .await } - pub async fn security_keys(&self, pool: &DbPool) -> Result, SqlxError> { - if let Some(id) = self.id { - query_as!( - SecurityKey, - "SELECT id \"id!\", name FROM webauthn WHERE user_id = $1", - id - ) - .fetch_all(pool) - .await - } else { - Ok(Vec::new()) - } + pub async fn security_keys(&self, pool: &PgPool) -> Result, SqlxError> { + query_as!( + SecurityKey, + "SELECT id \"id!\", name FROM webauthn WHERE user_id = $1", + self.id + ) + .fetch_all(pool) + .await } - pub async fn add_to_group<'e, E>(&self, executor: E, group: &Group) -> Result<(), SqlxError> + pub async fn add_to_group<'e, E>(&self, executor: E, group: &Group) -> Result<(), SqlxError> where E: PgExecutor<'e>, { - if let (Some(id), Some(group_id)) = (self.id, group.id) { - query!( - "INSERT INTO group_user (group_id, user_id) VALUES ($1, $2) \ - ON CONFLICT DO NOTHING", - group_id, - id - ) - .execute(executor) - .await?; - } + query!( + "INSERT INTO group_user (group_id, user_id) VALUES ($1, $2) \ + ON CONFLICT DO NOTHING", + group.id, + self.id + ) + .execute(executor) + .await?; + Ok(()) } pub async fn remove_from_group<'e, E>( &self, executor: E, - group: &Group, + group: &Group, ) -> Result<(), SqlxError> where E: PgExecutor<'e>, { - if let (Some(id), Some(group_id)) = (self.id, group.id) { - query!( - "DELETE FROM group_user WHERE group_id = $1 AND user_id = $2", - group_id, - id - ) - .execute(executor) - .await?; - } + query!( + "DELETE FROM group_user WHERE group_id = $1 AND user_id = $2", + group.id, + self.id + ) + .execute(executor) + .await?; + Ok(()) } @@ -836,25 +807,24 @@ impl User { where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query!( - "DELETE FROM oauth2authorizedapp WHERE user_id = $1 AND oauth2client_id = ANY($2)", - id, - app_client_ids - ) - .execute(executor) - .await?; - } + query!( + "DELETE FROM oauth2authorizedapp WHERE user_id = $1 AND oauth2client_id = ANY($2)", + self.id, + app_client_ids + ) + .execute(executor) + .await?; + Ok(()) } /// Create admin user if one doesn't exist yet pub async fn init_admin_user( - pool: &DbPool, + pool: &PgPool, default_admin_pass: &str, ) -> Result<(), anyhow::Error> { info!("Initializing admin user"); - let password_hash = Self::hash_password(default_admin_pass)?; + let password_hash = hash_password(default_admin_pass)?; // create admin user let result = query_scalar!( @@ -883,26 +853,25 @@ impl User { where E: PgExecutor<'e>, { - if let Some(id) = self.id { - Session::delete_all_for_user(executor, id).await?; - } + Session::delete_all_for_user(executor, self.id).await?; + Ok(()) } pub async fn find_by_device_id<'e, E>( executor: E, - device_id: i64, + device_id: Id, ) -> Result, SqlxError> where E: PgExecutor<'e>, { query_as!( Self, - "SELECT u.id \"id?\", u.username, u.password_hash, u.last_name, u.first_name, u.email, \ + "SELECT u.id, u.username, u.password_hash, u.last_name, u.first_name, u.email, \ u.phone, u.mfa_enabled, u.totp_enabled, u.email_mfa_enabled, \ u.totp_secret, u.email_mfa_secret, u.mfa_method \"mfa_method: _\", u.recovery_codes, u.is_active, u.openid_sub \ - FROM \"user\" as u \ - JOIN \"device\" as d ON u.id = d.user_id \ + FROM \"user\" u \ + JOIN \"device\" d ON u.id = d.user_id \ WHERE d.id = $1", device_id ) @@ -917,7 +886,7 @@ mod test { use crate::{config::DefGuardConfig, SERVER_CONFIG}; #[sqlx::test] - async fn test_mfa_code(pool: DbPool) { + async fn test_mfa_code(pool: PgPool) { let config = DefGuardConfig::new_test_config(); let _ = SERVER_CONFIG.set(config.clone()); @@ -928,7 +897,10 @@ mod test { "Harry", "h.potter@hogwart.edu.uk", None, - ); + ) + .save(&pool) + .await + .unwrap(); user.new_email_secret(&pool).await.unwrap(); assert!(user.email_mfa_secret.is_some()); let code = user.generate_email_mfa_code().unwrap(); @@ -940,7 +912,7 @@ mod test { } #[sqlx::test] - async fn test_user(pool: DbPool) { + async fn test_user(pool: PgPool) { let mut user = User::new( "hpotter", Some("pass123"), @@ -948,8 +920,10 @@ mod test { "Harry", "h.potter@hogwart.edu.uk", None, - ); - user.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); let fetched_user = User::find_by_username(&pool, "hpotter").await.unwrap(); assert!(fetched_user.is_some()); @@ -969,26 +943,30 @@ mod test { } #[sqlx::test] - async fn test_all_users(pool: DbPool) { - let mut harry = User::new( + async fn test_all_users(pool: PgPool) { + User::new( "hpotter", Some("pass123"), "Potter", "Harry", "h.potter@hogwart.edu.uk", None, - ); - harry.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); - let mut albus = User::new( + let albus = User::new( "adumbledore", Some("magic!"), "Dumbledore", "Albus", "a.dumbledore@hogwart.edu.uk", None, - ); - albus.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); let users = User::all(&pool).await.unwrap(); assert_eq!(users.len(), 2); @@ -1000,7 +978,7 @@ mod test { } #[sqlx::test] - async fn test_recovery_codes(pool: DbPool) { + async fn test_recovery_codes(pool: PgPool) { let mut harry = User::new( "hpotter", Some("pass123"), @@ -1008,10 +986,12 @@ mod test { "Harry", "h.potter@hogwart.edu.uk", None, - ); + ) + .save(&pool) + .await + .unwrap(); harry.get_recovery_codes(&pool).await.unwrap(); assert_eq!(harry.recovery_codes.len(), RECOVERY_CODES_COUNT); - harry.save(&pool).await.unwrap(); let fetched_user = User::find_by_username(&pool, "hpotter").await.unwrap(); assert!(fetched_user.is_some()); diff --git a/src/db/models/wallet.rs b/src/db/models/wallet.rs index e5de96e6f..557395ccd 100644 --- a/src/db/models/wallet.rs +++ b/src/db/models/wallet.rs @@ -1,7 +1,4 @@ -use std::{ - error::Error, - fmt::{Display, Formatter, Result as FmtResult}, -}; +use std::{error::Error, fmt}; use chrono::{NaiveDateTime, Utc}; use ethers_core::types::transaction::eip712::{Eip712, TypedData}; @@ -11,11 +8,13 @@ use secp256k1::{ ecdsa::{RecoverableSignature, RecoveryId}, Message, Secp256k1, }; -use sqlx::{query, query_as, Error as SqlxError, PgExecutor}; +use sqlx::{query, query_as, Error as SqlxError, PgExecutor, PgPool}; use tiny_keccak::{Hasher, Keccak}; -use super::DbPool; -use crate::hex::hex_decode; +use crate::{ + db::{Id, NoId}, + hex::hex_decode, +}; #[derive(Debug)] pub enum Web3Error { @@ -30,8 +29,8 @@ pub enum Web3Error { impl Error for Web3Error {} -impl Display for Web3Error { - fn fmt(&self, f: &mut Formatter) -> FmtResult { +impl fmt::Display for Web3Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Decode => write!(f, "hex decoding error"), Self::InvalidMessage => write!(f, "invalid message"), @@ -62,12 +61,12 @@ pub fn hash_message>(message: S) -> [u8; 32] { } #[derive(Model)] -pub struct Wallet { - pub(crate) id: Option, - pub(crate) user_id: i64, +pub struct Wallet { + pub(crate) id: I, + pub(crate) user_id: Id, pub address: String, pub name: String, - pub chain_id: i64, + pub chain_id: Id, pub challenge_message: String, pub challenge_signature: Option, pub creation_timestamp: NaiveDateTime, @@ -78,14 +77,14 @@ pub struct Wallet { impl Wallet { #[must_use] pub fn new_for_user>( - user_id: i64, + user_id: Id, address: S, name: S, - chain_id: i64, + chain_id: Id, challenge_message: S, ) -> Self { Self { - id: None, + id: NoId, user_id, address: address.into(), name: name.into(), @@ -98,6 +97,43 @@ impl Wallet { } } + /// Prepare challenge message using EIP-712 format + #[must_use] + pub fn format_challenge(address: &str, challenge_message: &str) -> String { + let nonce = Nonce::new_random(); + + format!( + r#"{{ + "domain": {{ "name": "Defguard", "version": "1" }}, + "types": {{ + "EIP712Domain": [ + {{ "name": "name", "type": "string" }}, + {{ "name": "version", "type": "string" }} + ], + "ProofOfOwnership": [ + {{ "name": "wallet", "type": "address" }}, + {{ "name": "content", "type": "string" }}, + {{ "name": "nonce", "type": "string" }} + ] + }}, + "primaryType": "ProofOfOwnership", + "message": {{ + "wallet": "{}", + "content": "{}", + "nonce": "{}" + }}}} + "#, + address, + challenge_message, + nonce.secret() + ) + .chars() + .filter(|c| c != &'\r' && c != &'\n' && c != &'\t') + .collect() + } +} + +impl Wallet { pub fn verify_address(&self, message: &str, signature: &str) -> Result { let address_array = hex_decode(&self.address).map_err(|_| Web3Error::Decode)?; let signature_array = hex_decode(signature).map_err(|_| Web3Error::Decode)?; @@ -134,59 +170,27 @@ impl Wallet { Err(Web3Error::VerifyAddress) } } +} - pub async fn set_signature(&mut self, pool: &DbPool, signature: &str) -> Result<(), SqlxError> { +impl Wallet { + pub async fn set_signature(&mut self, pool: &PgPool, signature: &str) -> Result<(), SqlxError> { self.challenge_signature = Some(signature.into()); self.validation_timestamp = Some(Utc::now().naive_utc()); - if let Some(id) = self.id { - query!( - "UPDATE wallet SET challenge_signature = $1, validation_timestamp = $2 WHERE id = $3", - self.challenge_signature, self.validation_timestamp, id - ) - .execute(pool) - .await?; - } - Ok(()) - } - - /// Prepare challenge message using EIP-712 format - #[must_use] - pub fn format_challenge(address: &str, challenge_message: &str) -> String { - let nonce = Nonce::new_random(); - - format!( - r#"{{ - "domain": {{ "name": "Defguard", "version": "1" }}, - "types": {{ - "EIP712Domain": [ - {{ "name": "name", "type": "string" }}, - {{ "name": "version", "type": "string" }} - ], - "ProofOfOwnership": [ - {{ "name": "wallet", "type": "address" }}, - {{ "name": "content", "type": "string" }}, - {{ "name": "nonce", "type": "string" }} - ] - }}, - "primaryType": "ProofOfOwnership", - "message": {{ - "wallet": "{}", - "content": "{}", - "nonce": "{}" - }}}} - "#, - address, - challenge_message, - nonce.secret() + query!( + "UPDATE wallet SET challenge_signature = $1, validation_timestamp = $2 WHERE id = $3", + self.challenge_signature, + self.validation_timestamp, + self.id ) - .chars() - .filter(|c| c != &'\r' && c != &'\n' && c != &'\t') - .collect() + .execute(pool) + .await?; + + Ok(()) } pub async fn find_by_user_and_address<'e, E>( executor: E, - user_id: i64, + user_id: Id, address: &str, ) -> Result, SqlxError> where @@ -194,7 +198,7 @@ impl Wallet { { query_as!( Self, - "SELECT id \"id?\", user_id, address, name, chain_id, challenge_message, challenge_signature, \ + "SELECT id, user_id, address, name, chain_id, challenge_message, challenge_signature, \ creation_timestamp, validation_timestamp, use_for_mfa FROM wallet \ WHERE user_id = $1 AND address = $2", user_id, @@ -204,7 +208,7 @@ impl Wallet { .await } - pub async fn disable_mfa_for_user<'e, E>(executor: E, user_id: i64) -> Result<(), SqlxError> + pub async fn disable_mfa_for_user<'e, E>(executor: E, user_id: Id) -> Result<(), SqlxError> where E: PgExecutor<'e>, { @@ -214,6 +218,7 @@ impl Wallet { ) .execute(executor) .await?; + Ok(()) } } diff --git a/src/db/models/webauthn.rs b/src/db/models/webauthn.rs index 2a7bbfe62..29fbe4695 100644 --- a/src/db/models/webauthn.rs +++ b/src/db/models/webauthn.rs @@ -1,38 +1,44 @@ use model_derive::Model; -use sqlx::{query, query_as, query_scalar, Error as SqlxError, PgExecutor}; +use sqlx::{query, query_as, query_scalar, Error as SqlxError, PgExecutor, PgPool}; use webauthn_rs::prelude::Passkey; -use super::{error::ModelError, DbPool}; +use super::error::ModelError; +use crate::db::{Id, NoId}; #[derive(Model)] -pub struct WebAuthn { - id: Option, - pub(crate) user_id: i64, +pub struct WebAuthn { + id: I, + pub(crate) user_id: Id, name: String, // serialize from/to [`Passkey`] pub passkey: Vec, } impl WebAuthn { - pub fn new(user_id: i64, name: String, passkey: &Passkey) -> Result { + pub fn new(user_id: Id, name: String, passkey: &Passkey) -> Result { let passkey = serde_cbor::to_vec(passkey).map_err(|_| ModelError::CannotCreate)?; Ok(Self { - id: None, + id: NoId, user_id, name, passkey, }) } +} +impl WebAuthn { /// Serialize [`Passkey`] from binary data. pub(crate) fn passkey(&self) -> Result { let passkey = serde_cbor::from_slice(&self.passkey).map_err(|_| ModelError::CannotCreate)?; + Ok(passkey) } +} +impl WebAuthn { /// Fetch all [`Passkey`]s for a given user. - pub async fn passkeys_for_user(pool: &DbPool, user_id: i64) -> Result, SqlxError> { + pub async fn passkeys_for_user(pool: &PgPool, user_id: Id) -> Result, SqlxError> { query_scalar!("SELECT passkey FROM webauthn WHERE user_id = $1", user_id) .fetch_all(pool) .await @@ -45,10 +51,10 @@ impl WebAuthn { } /// Fetch all for a given user. - pub async fn all_for_user(pool: &DbPool, user_id: i64) -> Result, SqlxError> { + pub async fn all_for_user(pool: &PgPool, user_id: Id) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", user_id, name, passkey FROM webauthn WHERE user_id = $1", + "SELECT id, user_id, name, passkey FROM webauthn WHERE user_id = $1", user_id ) .fetch_all(pool) @@ -56,7 +62,7 @@ impl WebAuthn { } /// Delete all for a given user. - pub async fn delete_all_for_user<'e, E>(executor: E, user_id: i64) -> Result<(), SqlxError> + pub async fn delete_all_for_user<'e, E>(executor: E, user_id: Id) -> Result<(), SqlxError> where E: PgExecutor<'e>, { diff --git a/src/db/models/webhook.rs b/src/db/models/webhook.rs index ed8f31d17..c9eda286c 100644 --- a/src/db/models/webhook.rs +++ b/src/db/models/webhook.rs @@ -1,8 +1,8 @@ use model_derive::Model; -use sqlx::{query_as, Error as SqlxError, FromRow}; +use sqlx::{query_as, Error as SqlxError, FromRow, PgPool}; use super::UserInfo; -use crate::DbPool; +use crate::db::{Id, NoId}; /// App events which triggers webhook action #[derive(Debug)] @@ -13,7 +13,7 @@ pub enum AppEvent { HWKeyProvision(HWKeyUserData), } /// User data send on HWKeyProvision AppEvent -#[derive(Serialize, Debug)] +#[derive(Debug, Serialize)] pub struct HWKeyUserData { pub username: String, pub email: String, @@ -46,10 +46,9 @@ impl AppEvent { } } -#[derive(Deserialize, Model, Serialize, FromRow, Debug)] -pub struct WebHook { - #[serde(default)] - pub id: Option, +#[derive(Debug, Deserialize, FromRow, Model, Serialize)] +pub struct WebHook { + pub id: I, pub url: String, pub description: String, pub token: String, @@ -60,9 +59,9 @@ pub struct WebHook { pub on_hwkey_provision: bool, } -impl WebHook { +impl WebHook { /// Fetch all enabled webhooks. - pub async fn all_enabled(pool: &DbPool, trigger: &AppEvent) -> Result, SqlxError> { + pub async fn all_enabled(pool: &PgPool, trigger: &AppEvent) -> Result, SqlxError> { let column_name = trigger.column_name(); let query = format!( "SELECT id, url, description, token, enabled, on_user_created, \ @@ -73,10 +72,10 @@ impl WebHook { } /// Find [`WebHook`] by URL. - pub async fn find_by_url(pool: &DbPool, url: &str) -> Result, SqlxError> { + pub async fn find_by_url(pool: &PgPool, url: &str) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", url, description, token, enabled, on_user_created, \ + "SELECT id, url, description, token, enabled, on_user_created, \ on_user_deleted, on_user_modified, on_hwkey_provision FROM webhook WHERE url = $1", url ) diff --git a/src/db/models/wireguard.rs b/src/db/models/wireguard.rs index 913894179..5bc14aa9e 100644 --- a/src/db/models/wireguard.rs +++ b/src/db/models/wireguard.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - fmt::{Debug, Display, Formatter}, + fmt, net::{IpAddr, Ipv4Addr}, str::FromStr, }; @@ -10,7 +10,7 @@ use chrono::{Duration, NaiveDateTime, Utc}; use ipnetwork::{IpNetwork, IpNetworkError, NetworkSize}; use model_derive::Model; use rand_core::OsRng; -use sqlx::{query_as, query_scalar, Error as SqlxError, FromRow, PgConnection, PgExecutor}; +use sqlx::{query_as, query_scalar, Error as SqlxError, FromRow, PgConnection, PgExecutor, PgPool}; use thiserror::Error; use utoipa::ToSchema; use x25519_dalek::{PublicKey, StaticSecret}; @@ -18,10 +18,11 @@ use x25519_dalek::{PublicKey, StaticSecret}; use super::{ device::{Device, DeviceError, DeviceInfo, DeviceNetworkInfo, WireguardNetworkDevice}, error::ModelError, - DbPool, User, UserInfo, + User, UserInfo, }; use crate::{ appstate::AppState, + db::{Id, NoId}, grpc::{gateway::Peer, GatewayState}, wg_config::ImportedDevice, }; @@ -30,9 +31,9 @@ pub const DEFAULT_KEEPALIVE_INTERVAL: i32 = 25; pub const DEFAULT_DISCONNECT_THRESHOLD: i32 = 180; // Used in process of importing network from wireguard config -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct MappedDevice { - pub user_id: i64, + pub user_id: Id, pub name: String, pub wireguard_pubkey: String, pub wireguard_ip: IpAddr, @@ -59,19 +60,19 @@ impl DateTimeAggregation { #[derive(Clone, Debug)] pub enum GatewayEvent { - NetworkCreated(i64, WireguardNetwork), - NetworkModified(i64, WireguardNetwork, Vec), - NetworkDeleted(i64, String), + NetworkCreated(Id, WireguardNetwork), + NetworkModified(Id, WireguardNetwork, Vec), + NetworkDeleted(Id, String), DeviceCreated(DeviceInfo), DeviceModified(DeviceInfo), DeviceDeleted(DeviceInfo), } /// Stores configuration required to setup a WireGuard network -#[derive(Clone, Debug, Model, Deserialize, Serialize, PartialEq, ToSchema)] +#[derive(Clone, Debug, Deserialize, Model, PartialEq, Serialize, ToSchema)] #[table(wireguard_network)] -pub struct WireguardNetwork { - pub id: Option, +pub struct WireguardNetwork { + pub id: I, pub name: String, #[model(enum)] pub address: IpNetwork, @@ -94,12 +95,15 @@ pub struct WireguardKey { pub public: String, } -impl Display for WireguardNetwork { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self.id { - Some(network_id) => write!(f, "[ID {}] {}", network_id, self.name), - None => write!(f, "{}", self.name), - } +impl fmt::Display for WireguardNetwork { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +impl fmt::Display for WireguardNetwork { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[ID {}] {}", self.id, self.name) } } @@ -138,7 +142,7 @@ impl WireguardNetwork { let prvkey = StaticSecret::random_from_rng(OsRng); let pubkey = PublicKey::from(&prvkey); Ok(Self { - id: None, + id: NoId, name, address, port, @@ -154,11 +158,15 @@ impl WireguardNetwork { }) } - pub fn get_id(&self) -> Result { - let id = self.id.ok_or(ModelError::IdNotSet)?; - Ok(id) + /// Try to set `address` from `&str`. + pub fn try_set_address(&mut self, address: &str) -> Result { + IpNetwork::from_str(address).inspect(|&network| { + self.address = network; + }) } +} +impl WireguardNetwork { pub async fn find_by_name<'e, E>( executor: E, name: &str, @@ -169,7 +177,7 @@ impl WireguardNetwork { let networks = query_as!( WireguardNetwork, "SELECT \ - id as \"id?\", name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ + id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ connected_at, mfa_enabled, keepalive_interval, peer_disconnect_threshold \ FROM wireguard_network WHERE name = $1", name @@ -205,6 +213,7 @@ impl WireguardNetwork { let count = query_scalar!("SELECT count(*) \"count!\" FROM wireguard_network_device WHERE wireguard_network_id = $1", self.id) .fetch_one(transaction) .await?; + Ok(count) } @@ -223,7 +232,8 @@ impl WireguardNetwork { return Err(WireguardNetworkError::NetworkTooSmall); } } - }; + } + Ok(()) } @@ -238,13 +248,6 @@ impl WireguardNetwork { } } - /// Try to set `address` from `&str`. - pub fn try_set_address(&mut self, address: &str) -> Result { - IpNetwork::from_str(address).inspect(|&network| { - self.address = network; - }) - } - /// Try to change network address, changing device addresses if necessary. pub async fn change_address( &mut self, @@ -255,7 +258,6 @@ impl WireguardNetwork { "Changing network address for {self} from {} to {new_address}", self.address ); - let network_id = self.get_id()?; let old_address = self.address; // check if new network size will fit all existing devices @@ -292,11 +294,8 @@ impl WireguardNetwork { } match devices_iter.next() { Some(device) => { - let Some(device_id) = device.id else { - return Err(WireguardNetworkError::from(ModelError::CannotModify)); - }; let wireguard_network_device = - WireguardNetworkDevice::new(network_id, device_id, ip); + WireguardNetworkDevice::new(self.id, device.id, ip); wireguard_network_device.update(&mut *transaction).await?; } None => break, @@ -305,6 +304,7 @@ impl WireguardNetwork { } self.address = new_address; + Ok(()) } @@ -313,38 +313,38 @@ impl WireguardNetwork { async fn get_allowed_devices( &self, transaction: &mut PgConnection, - ) -> Result, ModelError> { + ) -> Result>, ModelError> { debug!("Fetching all allowed devices for network {}", self); - let devices = match self - .get_allowed_groups(&mut *transaction) - .await? { + let devices = match self.get_allowed_groups(&mut *transaction).await? { // devices need to be filtered by allowed group Some(allowed_groups) => { query_as!( - Device, - "SELECT DISTINCT ON (d.id) d.id as \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created \ - FROM device d \ - JOIN \"user\" u ON d.user_id = u.id \ - JOIN group_user gu ON u.id = gu.user_id \ - JOIN \"group\" g ON gu.group_id = g.id \ - WHERE g.\"name\" IN (SELECT * FROM UNNEST($1::text[])) - AND u.is_active = true - ORDER BY d.id ASC", - &allowed_groups - ) + Device, + "SELECT DISTINCT ON (d.id) d.id, d.name, d.wireguard_pubkey, d.user_id, d.created \ + FROM device d \ + JOIN \"user\" u ON d.user_id = u.id \ + JOIN group_user gu ON u.id = gu.user_id \ + JOIN \"group\" g ON gu.group_id = g.id \ + WHERE g.\"name\" IN (SELECT * FROM UNNEST($1::text[])) \ + AND u.is_active = true \ + ORDER BY d.id ASC", + &allowed_groups + ) .fetch_all(&mut *transaction) .await? - }, + } // all devices of enabled users are allowed None => { query_as!( Device, - "SELECT d.id as \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created \ + "SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created \ FROM device d \ JOIN \"user\" u ON d.user_id = u.id \ WHERE u.is_active = true \ ORDER BY d.id ASC" - ).fetch_all(&mut *transaction).await? + ) + .fetch_all(&mut *transaction) + .await? } }; @@ -374,14 +374,13 @@ impl WireguardNetwork { pub async fn add_device_to_network( &self, transaction: &mut PgConnection, - device: &Device, + device: &Device, reserved_ips: Option<&[IpAddr]>, ) -> Result { info!("Assigning IP in network {self} for {device}"); let allowed_devices = self.get_allowed_devices(&mut *transaction).await?; - let allowed_device_ids: Vec = - allowed_devices.iter().filter_map(|dev| dev.id).collect(); - if allowed_device_ids.contains(&device.get_id()?) { + let allowed_device_ids: Vec = allowed_devices.iter().map(|dev| dev.id).collect(); + if allowed_device_ids.contains(&device.id) { let wireguard_network_device = device .assign_network_ip(&mut *transaction, self, reserved_ips) .await?; @@ -404,9 +403,9 @@ impl WireguardNetwork { // list all allowed devices let allowed_devices = self.get_allowed_devices(&mut *transaction).await?; // convert to a map for easier processing - let mut allowed_devices: HashMap = allowed_devices + let mut allowed_devices: HashMap> = allowed_devices .into_iter() - .filter_map(|dev| dev.id.map(|id| (id, dev))) + .map(|dev| (dev.id, dev)) .collect(); // check if all devices can fit within network @@ -416,11 +415,10 @@ impl WireguardNetwork { // list all assigned IPs let assigned_ips = - WireguardNetworkDevice::all_for_network(&mut *transaction, self.get_id()?).await?; + WireguardNetworkDevice::all_for_network(&mut *transaction, self.id).await?; // loop through assigned IPs; remove no longer allowed, readdress when necessary; remove processed entry from all devices list // initial list should now contain only devices to be added - let network_id = self.get_id()?; let mut events = Vec::new(); for device_network_config in assigned_ips { // device is allowed and an IP was already assigned @@ -433,7 +431,7 @@ impl WireguardNetwork { events.push(GatewayEvent::DeviceModified(DeviceInfo { device, network_info: vec![DeviceNetworkInfo { - network_id, + network_id: self.id, device_wireguard_ip: wireguard_network_device.wireguard_ip, preshared_key: wireguard_network_device.preshared_key, is_authorized: wireguard_network_device.is_authorized, @@ -453,7 +451,7 @@ impl WireguardNetwork { events.push(GatewayEvent::DeviceDeleted(DeviceInfo { device, network_info: vec![DeviceNetworkInfo { - network_id, + network_id: self.id, device_wireguard_ip: device_network_config.wireguard_ip, preshared_key: device_network_config.preshared_key, is_authorized: device_network_config.is_authorized, @@ -475,7 +473,7 @@ impl WireguardNetwork { events.push(GatewayEvent::DeviceCreated(DeviceInfo { device, network_info: vec![DeviceNetworkInfo { - network_id, + network_id: self.id, device_wireguard_ip: wireguard_network_device.wireguard_ip, preshared_key: wireguard_network_device.preshared_key, is_authorized: wireguard_network_device.is_authorized, @@ -495,12 +493,11 @@ impl WireguardNetwork { transaction: &mut PgConnection, imported_devices: Vec, ) -> Result<(Vec, Vec), WireguardNetworkError> { - let network_id = self.get_id()?; let allowed_devices = self.get_allowed_devices(&mut *transaction).await?; // convert to a map for easier processing - let allowed_devices: HashMap = allowed_devices + let allowed_devices: HashMap> = allowed_devices .into_iter() - .filter_map(|dev| dev.id.map(|id| (id, dev))) + .map(|dev| (dev.id, dev)) .collect(); let mut devices_to_map = Vec::new(); @@ -513,16 +510,15 @@ impl WireguardNetwork { { Some(existing_device) => { // check if device is allowed in network - let device_id = existing_device.get_id()?; - match allowed_devices.get(&device_id) { + match allowed_devices.get(&existing_device.id) { Some(_) => { info!( "Device with pubkey {} exists already, assigning IP {} for new network: {self}", existing_device.wireguard_pubkey, imported_device.wireguard_ip ); let wireguard_network_device = WireguardNetworkDevice::new( - network_id, - existing_device.id.expect("Device ID is missing"), + self.id, + existing_device.id, imported_device.wireguard_ip, ); wireguard_network_device.insert(&mut *transaction).await?; @@ -532,7 +528,7 @@ impl WireguardNetwork { events.push(GatewayEvent::DeviceModified(DeviceInfo { device: existing_device, network_info: vec![DeviceNetworkInfo { - network_id, + network_id: self.id, device_wireguard_ip: wireguard_network_device.wireguard_ip, preshared_key: wireguard_network_device.preshared_key, is_authorized: wireguard_network_device.is_authorized, @@ -550,6 +546,7 @@ impl WireguardNetwork { None => devices_to_map.push(imported_device), } } + Ok((devices_to_map, events)) } @@ -560,7 +557,6 @@ impl WireguardNetwork { mapped_devices: Vec, ) -> Result, WireguardNetworkError> { info!("Mapping user devices for network {}", self); - let network_id = self.get_id()?; // get allowed groups for network let allowed_groups = self.get_allowed_groups(&mut *transaction).await?; @@ -574,12 +570,13 @@ impl WireguardNetwork { WireguardNetworkError::InvalidDevicePubkey(mapped_device.wireguard_pubkey.clone()) })?; // save a new device - let mut device = Device::new( + let device = Device::new( mapped_device.name.clone(), mapped_device.wireguard_pubkey.clone(), mapped_device.user_id, - ); - device.save(&mut *transaction).await?; + ) + .save(&mut *transaction) + .await?; debug!("Saved new device {device}"); // get a list of groups user is assigned to @@ -601,14 +598,11 @@ impl WireguardNetwork { let mut network_info = Vec::new(); match &allowed_groups { None => { - let wireguard_network_device = WireguardNetworkDevice::new( - network_id, - device.id.expect("Device ID is missing"), - mapped_device.wireguard_ip, - ); + let wireguard_network_device = + WireguardNetworkDevice::new(self.id, device.id, mapped_device.wireguard_ip); wireguard_network_device.insert(&mut *transaction).await?; network_info.push(DeviceNetworkInfo { - network_id, + network_id: self.id, device_wireguard_ip: wireguard_network_device.wireguard_ip, preshared_key: wireguard_network_device.preshared_key, is_authorized: wireguard_network_device.is_authorized, @@ -619,13 +613,13 @@ impl WireguardNetwork { if allowed.iter().any(|group| groups.contains(group)) { // assign specified IP in imported network let wireguard_network_device = WireguardNetworkDevice::new( - network_id, - device.id.expect("Device ID is missing"), + self.id, + device.id, mapped_device.wireguard_ip, ); wireguard_network_device.insert(&mut *transaction).await?; network_info.push(DeviceNetworkInfo { - network_id, + network_id: self.id, device_wireguard_ip: wireguard_network_device.wireguard_ip, preshared_key: wireguard_network_device.preshared_key, is_authorized: wireguard_network_device.is_authorized, @@ -648,17 +642,18 @@ impl WireguardNetwork { })); } } + Ok(events) } async fn fetch_latest_stats( &self, - conn: &DbPool, - device_id: i64, - ) -> Result, SqlxError> { + conn: &PgPool, + device_id: Id, + ) -> Result>, SqlxError> { let stats = query_as!( WireguardPeerStats, - "SELECT id \"id?\", device_id \"device_id!\", collected_at \"collected_at!\", network \"network!\", \ + "SELECT id, device_id \"device_id!\", collected_at \"collected_at!\", network \"network!\", \ endpoint, upload \"upload!\", download \"download!\", latest_handshake \"latest_handshake!\", allowed_ips \ FROM wireguard_peer_stats \ WHERE device_id = $1 AND network = $2 \ @@ -669,11 +664,12 @@ impl WireguardNetwork { ) .fetch_optional(conn) .await?; + Ok(stats) } /// Parse WireGuard IP address - fn parse_wireguard_ip(stats: &WireguardPeerStats) -> Option { + fn parse_wireguard_ip(stats: &WireguardPeerStats) -> Option { stats .allowed_ips .as_ref() @@ -681,7 +677,7 @@ impl WireguardNetwork { } /// Parse public IP address - fn parse_public_ip(stats: &WireguardPeerStats) -> Option { + fn parse_public_ip(stats: &WireguardPeerStats) -> Option { stats .endpoint .as_ref() @@ -691,8 +687,8 @@ impl WireguardNetwork { /// Finds when the device connected based on handshake timestamps async fn connected_at( &self, - conn: &DbPool, - device_id: i64, + conn: &PgPool, + device_id: Id, ) -> Result, SqlxError> { let connected_at = query_scalar!( "SELECT \ @@ -710,14 +706,15 @@ impl WireguardNetwork { ) .fetch_optional(conn) .await?; + Ok(connected_at.flatten()) } /// Retrieves stats for specified devices async fn device_stats( &self, - conn: &DbPool, - devices: &[Device], + conn: &PgPool, + devices: &[Device], from: &NaiveDateTime, aggregation: &DateTimeAggregation, ) -> Result, SqlxError> { @@ -730,15 +727,15 @@ impl WireguardNetwork { // https://github.com/launchbadge/sqlx/issues/656 let device_ids = devices .iter() - .filter_map(|d| d.id.map(|id| id.to_string())) + .map(|d| d.id.to_string()) .collect::>() .join(","); let query = format!( "SELECT \ device_id, \ - date_trunc($1, collected_at) as collected_at, \ - cast(sum(download) as bigint) as download, \ - cast(sum(upload) as bigint) as upload \ + date_trunc($1, collected_at) collected_at, \ + cast(sum(download) as bigint) download, \ + cast(sum(upload) as bigint) upload \ FROM wireguard_peer_stats_view \ WHERE device_id IN ({device_ids}) \ AND collected_at >= $2 \ @@ -754,19 +751,18 @@ impl WireguardNetwork { .await?; let mut result = Vec::new(); for device in devices { - let Some(device_id) = device.id else { continue }; - let latest_stats = self.fetch_latest_stats(conn, device_id).await?; + let latest_stats = self.fetch_latest_stats(conn, device.id).await?; result.push(WireguardDeviceStatsRow { - id: device_id, + id: device.id, user_id: device.user_id, name: device.name.clone(), wireguard_ip: latest_stats.as_ref().and_then(Self::parse_wireguard_ip), public_ip: latest_stats.as_ref().and_then(Self::parse_public_ip), - connected_at: self.connected_at(conn, device_id).await?, + connected_at: self.connected_at(conn, device.id).await?, // Filter stats for this device stats: stats .iter() - .filter(|s| Some(s.device_id) == device.id) + .filter(|s| s.device_id == device.id) .cloned() .collect(), }); @@ -777,11 +773,11 @@ impl WireguardNetwork { /// Retrieves network stats grouped by currently active users since `from` timestamp pub async fn user_stats( &self, - conn: &DbPool, + conn: &PgPool, from: &NaiveDateTime, aggregation: &DateTimeAggregation, ) -> Result, SqlxError> { - let mut user_map: HashMap> = HashMap::new(); + let mut user_map: HashMap> = HashMap::new(); let oldest_handshake = (Utc::now() - Duration::minutes(WIREGUARD_MAX_HANDSHAKE_MINUTES)).naive_utc(); // Retrieve connected devices from database @@ -793,7 +789,7 @@ impl WireguardNetwork { ORDER BY device_id, latest_handshake DESC \ ) \ SELECT \ - d.id \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created \ + d.id, d.name, d.wireguard_pubkey, d.user_id, d.created \ FROM device d \ JOIN s ON d.id = s.device_id \ WHERE s.latest_handshake >= $1 AND s.network = $2", @@ -818,20 +814,21 @@ impl WireguardNetwork { devices: u.1.clone(), }); } + Ok(stats) } /// Retrieves total active users/devices since `from` timestamp async fn total_activity( &self, - conn: &DbPool, + conn: &PgPool, from: &NaiveDateTime, ) -> Result { let activity_stats = query_as!( WireguardNetworkActivityStats, "SELECT \ - COALESCE(COUNT(DISTINCT(u.id)), 0) as \"active_users!\", \ - COALESCE(COUNT(DISTINCT(s.device_id)), 0) as \"active_devices!\" \ + COALESCE(COUNT(DISTINCT(u.id)), 0) \"active_users!\", \ + COALESCE(COUNT(DISTINCT(s.device_id)), 0) \"active_devices!\" \ FROM \"user\" u \ JOIN device d ON d.user_id = u.id \ JOIN wireguard_peer_stats s ON s.device_id = d.id \ @@ -841,20 +838,21 @@ impl WireguardNetwork { ) .fetch_one(conn) .await?; + Ok(activity_stats) } /// Retrieves currently connected users async fn current_activity( &self, - conn: &DbPool, + conn: &PgPool, ) -> Result { let from = (Utc::now() - Duration::minutes(WIREGUARD_MAX_HANDSHAKE_MINUTES)).naive_utc(); let activity_stats = query_as!( WireguardNetworkActivityStats, "SELECT \ - COALESCE(COUNT(DISTINCT(u.id)), 0) as \"active_users!\", \ - COALESCE(COUNT(DISTINCT(s.device_id)), 0) as \"active_devices!\" \ + COALESCE(COUNT(DISTINCT(u.id)), 0) \"active_users!\", \ + COALESCE(COUNT(DISTINCT(s.device_id)), 0) \"active_devices!\" \ FROM \"user\" u \ JOIN device d ON d.user_id = u.id \ JOIN wireguard_peer_stats s ON s.device_id = d.id \ @@ -864,6 +862,7 @@ impl WireguardNetwork { ) .fetch_one(conn) .await?; + Ok(activity_stats) } @@ -871,7 +870,7 @@ impl WireguardNetwork { /// using `aggregation` (hour/minute) aggregation level async fn transfer_series( &self, - conn: &DbPool, + conn: &PgPool, from: &NaiveDateTime, aggregation: &DateTimeAggregation, ) -> Result, SqlxError> { @@ -892,13 +891,14 @@ impl WireguardNetwork { ) .fetch_all(conn) .await?; + Ok(stats) } /// Retrieves network stats pub async fn network_stats( &self, - conn: &DbPool, + conn: &PgPool, from: &NaiveDateTime, aggregation: &DateTimeAggregation, ) -> Result { @@ -921,7 +921,7 @@ impl WireguardNetwork { impl Default for WireguardNetwork { fn default() -> Self { Self { - id: Option::default(), + id: NoId, name: String::default(), address: IpNetwork::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0).unwrap(), port: i32::default(), @@ -941,7 +941,7 @@ impl Default for WireguardNetwork { #[derive(Serialize, Clone, Debug)] pub struct WireguardNetworkInfo { #[serde(flatten)] - pub network: WireguardNetwork, + pub network: WireguardNetwork, pub connected: bool, pub gateways: Vec, pub allowed_groups: Vec, @@ -956,34 +956,34 @@ pub struct WireguardStatsRow { #[derive(FromRow, Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct WireguardDeviceTransferRow { - pub device_id: i64, + pub device_id: Id, pub collected_at: Option, pub upload: i64, pub download: i64, } -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Clone, Default, Deserialize, Serialize)] pub struct WireguardDeviceStatsRow { - pub id: i64, + pub id: Id, pub stats: Vec, - pub user_id: i64, + pub user_id: Id, pub name: String, pub wireguard_ip: Option, pub public_ip: Option, pub connected_at: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Deserialize, Serialize)] pub struct WireguardUserStatsRow { pub user: UserInfo, pub devices: Vec, } -#[derive(Model, Serialize, Deserialize, Debug)] +#[derive(Debug, Deserialize, Model, Serialize)] #[table(wireguard_peer_stats)] -pub struct WireguardPeerStats { - pub id: Option, - pub device_id: i64, +pub struct WireguardPeerStats { + pub id: I, + pub device_id: Id, pub collected_at: NaiveDateTime, pub network: i64, pub endpoint: Option, @@ -1004,7 +1004,7 @@ pub struct WireguardNetworkTransferStats { pub download: i64, } -#[derive(Serialize, Deserialize)] +#[derive(Deserialize, Serialize)] pub struct WireguardNetworkStats { pub current_active_users: i64, pub current_active_devices: i64, @@ -1022,34 +1022,30 @@ mod test { use super::*; use crate::db::models::device::WireguardNetworkDevice; - async fn add_devices(pool: &DbPool, network: &WireguardNetwork, count: usize) { - let mut user = User::new( + async fn add_devices(pool: &PgPool, network: &WireguardNetwork, count: usize) { + let user = User::new( "testuser", Some("hunter2"), "Tester", "Test", "test@test.com", None, - ); - user.save(pool).await.unwrap(); + ) + .save(pool) + .await + .unwrap(); for i in 0..count { - Device::new_with_ip( - pool, - user.id.unwrap(), - format!("dev{i}"), - format!("key{i}"), - network, - ) - .await - .unwrap(); + Device::new_with_ip(pool, user.id, format!("dev{i}"), format!("key{i}"), network) + .await + .unwrap(); } } #[sqlx::test] - async fn test_change_address(pool: DbPool) { + async fn test_change_address(pool: PgPool) { let mut network = WireguardNetwork::default(); network.try_set_address("10.1.1.1/24").unwrap(); - network.save(&pool).await.unwrap(); + let mut network = network.save(&pool).await.unwrap(); add_devices(&pool, &network, 3).await; @@ -1068,7 +1064,7 @@ mod test { .unwrap() .unwrap(); let wireguard_network_device = - WireguardNetworkDevice::find(&pool, device.id.unwrap(), network.id.unwrap()) + WireguardNetworkDevice::find(&pool, device.id, network.id) .await .unwrap() .unwrap(); @@ -1080,10 +1076,10 @@ mod test { } #[sqlx::test] - async fn test_change_address_wont_fit(pool: DbPool) { + async fn test_change_address_wont_fit(pool: PgPool) { let mut network = WireguardNetwork::default(); network.try_set_address("10.1.1.1/29").unwrap(); - network.save(&pool).await.unwrap(); + let mut network = network.save(&pool).await.unwrap(); add_devices(&pool, &network, 3).await; @@ -1099,22 +1095,26 @@ mod test { } #[sqlx::test] - async fn test_connected_at_reconnection(pool: DbPool) { + async fn test_connected_at_reconnection(pool: PgPool) { let mut network = WireguardNetwork::default(); network.try_set_address("10.1.1.1/29").unwrap(); - network.save(&pool).await.unwrap(); + let network = network.save(&pool).await.unwrap(); - let mut user = User::new( + let user = User::new( "testuser", Some("hunter2"), "Tester", "Test", "test@test.com", None, - ); - user.save(&pool).await.unwrap(); - let mut device = Device::new(String::new(), String::new(), user.id.unwrap()); - device.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); + let device = Device::new(String::new(), String::new(), user.id) + .save(&pool) + .await + .unwrap(); // insert stats let samples = 60; // 1 hour of samples @@ -1122,22 +1122,24 @@ mod test { for i in 0..=samples { // simulate connection 30 minutes ago let handshake_minutes = i * if i < 31 { 1 } else { 10 }; - let mut wps = WireguardPeerStats { - id: None, - device_id: device.id.unwrap(), + WireguardPeerStats { + id: NoId, + device_id: device.id, collected_at: now - Duration::minutes(i), - network: network.id.unwrap(), + network: network.id, endpoint: Some("11.22.33.44".into()), upload: (samples - i) * 10, download: (samples - i) * 20, latest_handshake: now - Duration::minutes(handshake_minutes), allowed_ips: Some("10.1.1.0/24".into()), - }; - wps.save(&pool).await.unwrap(); + } + .save(&pool) + .await + .unwrap(); } let connected_at = network - .connected_at(&pool, device.id.unwrap()) + .connected_at(&pool, device.id) .await .unwrap() .unwrap(); @@ -1149,43 +1151,49 @@ mod test { } #[sqlx::test] - async fn test_connected_at_always_connected(pool: DbPool) { + async fn test_connected_at_always_connected(pool: PgPool) { let mut network = WireguardNetwork::default(); network.try_set_address("10.1.1.1/29").unwrap(); - network.save(&pool).await.unwrap(); + let network = network.save(&pool).await.unwrap(); - let mut user = User::new( + let user = User::new( "testuser", Some("hunter2"), "Tester", "Test", "test@test.com", None, - ); - user.save(&pool).await.unwrap(); - let mut device = Device::new(String::new(), String::new(), user.id.unwrap()); - device.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); + let device = Device::new(String::new(), String::new(), user.id) + .save(&pool) + .await + .unwrap(); // insert stats let samples = 60; // 1 hour of samples let now = Utc::now().naive_utc(); for i in 0..=samples { - let mut wps = WireguardPeerStats { - id: None, - device_id: device.id.unwrap(), + WireguardPeerStats { + id: NoId, + device_id: device.id, collected_at: now - Duration::minutes(i), - network: network.id.unwrap(), + network: network.id, endpoint: Some("11.22.33.44".into()), upload: (samples - i) * 10, download: (samples - i) * 20, latest_handshake: now - Duration::minutes(i), // handshake every minute allowed_ips: Some("10.1.1.0/24".into()), - }; - wps.save(&pool).await.unwrap(); + } + .save(&pool) + .await + .unwrap(); } let connected_at = network - .connected_at(&pool, device.id.unwrap()) + .connected_at(&pool, device.id) .await .unwrap() .unwrap(); diff --git a/src/db/models/yubikey.rs b/src/db/models/yubikey.rs index b5f2a3a9c..ca3d90382 100644 --- a/src/db/models/yubikey.rs +++ b/src/db/models/yubikey.rs @@ -1,39 +1,43 @@ use model_derive::Model; -use sqlx::{query, query_as, Error as SqlxError, PgExecutor}; +use sqlx::{query, query_as, PgExecutor}; + +use crate::db::{Id, NoId}; #[derive(Deserialize, Model, Serialize)] -pub struct YubiKey { - pub id: Option, +pub struct YubiKey { + pub id: I, pub name: String, pub serial: String, - pub user_id: i64, + pub user_id: Id, } impl YubiKey { #[must_use] - pub fn new(name: String, serial: String, user_id: i64) -> Self { + pub fn new(name: String, serial: String, user_id: Id) -> Self { Self { - id: None, + id: NoId, name, serial, user_id, } } +} - pub async fn find_by_user_id<'e, E>(executor: E, user_id: i64) -> Result, SqlxError> +impl YubiKey { + pub async fn find_by_user_id<'e, E>(executor: E, user_id: Id) -> Result, sqlx::Error> where E: PgExecutor<'e>, { query_as!( Self, - "SELECT * FROM \"yubikey\" WHERE user_id = $1", + "SELECT id, name, serial, user_id FROM \"yubikey\" WHERE user_id = $1", user_id ) .fetch_all(executor) .await } - pub async fn delete_by_id<'e, E>(executor: E, id: i64) -> Result<(), SqlxError> + pub async fn delete_by_id<'e, E>(executor: E, id: Id) -> Result<(), sqlx::Error> where E: PgExecutor<'e>, { diff --git a/src/enterprise/db/models/enterprise_settings.rs b/src/enterprise/db/models/enterprise_settings.rs index 91b9be641..1d14b761c 100644 --- a/src/enterprise/db/models/enterprise_settings.rs +++ b/src/enterprise/db/models/enterprise_settings.rs @@ -1,14 +1,11 @@ -use model_derive::Model; -use sqlx::PgExecutor; +use sqlx::{query, query_as, PgExecutor}; use struct_patch::Patch; use crate::enterprise::license::{get_cached_license, validate_license}; -#[derive(Debug, Model, Deserialize, Serialize, Patch)] -#[patch(attribute(derive(Serialize, Deserialize)))] +#[derive(Debug, Deserialize, Patch, Serialize)] +#[patch(attribute(derive(Deserialize, Serialize)))] pub struct EnterpriseSettings { - #[serde(skip)] - pub id: Option, // If true, only admins can manage devices pub admin_device_management: bool, // If true, the option to route all traffic through the vpn is disabled in the client @@ -18,11 +15,9 @@ pub struct EnterpriseSettings { } // We want to be conscious of what the defaults are here -#[allow(clippy::derivable_impls)] impl Default for EnterpriseSettings { fn default() -> Self { Self { - id: None, admin_device_management: false, disable_all_traffic: false, only_client_activation: false, @@ -44,10 +39,37 @@ impl EnterpriseSettings { validate_license(license.as_ref()).is_ok() }; if is_valid { - let settings = Self::find_by_id(executor, 1).await?; + let settings = query_as!( + Self, + "SELECT admin_device_management, \ + disable_all_traffic, only_client_activation \ + FROM \"enterprisesettings\" WHERE id = 1", + ) + .fetch_optional(executor) + .await?; Ok(settings.expect("EnterpriseSettings not found")) } else { Ok(EnterpriseSettings::default()) } } + + pub(crate) async fn save<'e, E>(&self, executor: E) -> Result<(), sqlx::Error> + where + E: PgExecutor<'e>, + { + query!( + "UPDATE \"enterprisesettings\" SET \ + admin_device_management = $1, \ + disable_all_traffic = $2, \ + only_client_activation = $3 \ + WHERE id = 1", + self.admin_device_management, + self.disable_all_traffic, + self.only_client_activation, + ) + .execute(executor) + .await?; + + Ok(()) + } } diff --git a/src/enterprise/db/models/openid_provider.rs b/src/enterprise/db/models/openid_provider.rs index 1d6321495..099fd0f22 100644 --- a/src/enterprise/db/models/openid_provider.rs +++ b/src/enterprise/db/models/openid_provider.rs @@ -1,11 +1,11 @@ use model_derive::Model; -use sqlx::{query, query_as, Error as SqlxError}; +use sqlx::{query, query_as, Error as SqlxError, PgPool}; -use crate::db::DbPool; +use crate::db::{Id, NoId}; #[derive(Deserialize, Model, Serialize)] -pub struct OpenIdProvider { - pub id: Option, +pub struct OpenIdProvider { + pub id: I, pub name: String, pub base_url: String, pub client_id: String, @@ -16,7 +16,7 @@ impl OpenIdProvider { #[must_use] pub fn new>(name: S, base_url: S, client_id: S, client_secret: S) -> Self { Self { - id: None, + id: NoId, name: name.into(), base_url: base_url.into(), client_id: client_id.into(), @@ -24,18 +24,8 @@ impl OpenIdProvider { } } - pub async fn find_by_name(pool: &DbPool, name: &str) -> Result, SqlxError> { - query_as!( - OpenIdProvider, - "SELECT id \"id?\", name, base_url, client_id, client_secret FROM openidprovider WHERE name = $1", - name - ) - .fetch_optional(pool) - .await - } - - pub async fn upsert(&mut self, pool: &DbPool) -> Result<(), SqlxError> { - if let Some(provider) = OpenIdProvider::get_current(pool).await? { + pub async fn upsert(self, pool: &PgPool) -> Result, SqlxError> { + if let Some(provider) = OpenIdProvider::::get_current(pool).await? { query!( "UPDATE openidprovider SET name = $1, base_url = $2, client_id = $3, client_secret = $4 WHERE id = $5", self.name, @@ -46,17 +36,29 @@ impl OpenIdProvider { ) .execute(pool) .await?; + + Ok(provider) } else { - self.save(pool).await?; + self.save(pool).await } + } +} - Ok(()) +impl OpenIdProvider { + pub async fn find_by_name(pool: &PgPool, name: &str) -> Result, SqlxError> { + query_as!( + OpenIdProvider, + "SELECT id, name, base_url, client_id, client_secret FROM openidprovider WHERE name = $1", + name + ) + .fetch_optional(pool) + .await } - pub async fn get_current(pool: &DbPool) -> Result, SqlxError> { + pub async fn get_current(pool: &PgPool) -> Result, SqlxError> { query_as!( OpenIdProvider, - "SELECT id \"id?\", name, base_url, client_id, client_secret FROM openidprovider" + "SELECT id, name, base_url, client_id, client_secret FROM openidprovider" ) .fetch_optional(pool) .await diff --git a/src/enterprise/grpc/polling.rs b/src/enterprise/grpc/polling.rs index 9780a9045..1bf322566 100644 --- a/src/enterprise/grpc/polling.rs +++ b/src/enterprise/grpc/polling.rs @@ -1,7 +1,8 @@ +use sqlx::PgPool; use tonic::Status; use crate::{ - db::{models::polling_token::PollingToken, DbPool, Device, User}, + db::{models::polling_token::PollingToken, Device, Id, User}, enterprise::license::{get_cached_license, validate_license}, grpc::{ proto::{InstanceInfoRequest, InstanceInfoResponse}, @@ -10,17 +11,17 @@ use crate::{ }; pub struct PollingServer { - pool: DbPool, + pool: PgPool, } impl PollingServer { #[must_use] - pub fn new(pool: DbPool) -> Self { + pub fn new(pool: PgPool) -> Self { Self { pool } } /// Checks validity of polling session - async fn validate_session(&self, token: &str) -> Result { + async fn validate_session(&self, token: &str) -> Result, Status> { debug!("Validating polling token. Token: {token}"); // Polling service is enterprise-only, check the lincense @@ -41,6 +42,7 @@ impl PollingServer { // Polling tokens are valid indefinitely info!("Token validation successful {token:?}."); + Ok(token) } @@ -61,15 +63,14 @@ impl PollingServer { debug!("Polling info for device: {}", device.wireguard_pubkey); // Ensure user is active - let device_id = device.id.expect("missing device id"); - let Some(user) = User::find_by_device_id(&self.pool, device_id) + let Some(user) = User::find_by_device_id(&self.pool, device.id) .await .map_err(|err| { - error!("Failed to retrieve user for device id {device_id}: {err}"); + error!("Failed to retrieve user for device id {}: {err}", device.id); Status::internal("failed to retrieve user") })? else { - error!("User for device id {device_id} not found"); + error!("User for device id {} not found", device.id); return Err(Status::internal("user not found")); }; if !user.is_active { @@ -83,6 +84,7 @@ impl PollingServer { // Build & return polling info let device_config = build_device_config_response(&self.pool, &device.wireguard_pubkey, false).await?; + Ok(InstanceInfoResponse { device_config: Some(device_config), }) diff --git a/src/enterprise/handlers/openid_login.rs b/src/enterprise/handlers/openid_login.rs index 42b6cce65..954ab2bed 100644 --- a/src/enterprise/handlers/openid_login.rs +++ b/src/enterprise/handlers/openid_login.rs @@ -18,12 +18,13 @@ use openidconnect::{ IssuerUrl, Nonce, ProviderMetadata, RedirectUrl, Scope, }; use serde_json::json; +use sqlx::PgPool; use time::Duration; use super::LicenseInfo; use crate::{ appstate::AppState, - db::{DbPool, MFAInfo, Session, SessionState, Settings, User, UserInfo}, + db::{MFAInfo, Session, SessionState, Settings, User, UserInfo}, enterprise::db::models::openid_provider::OpenIdProvider, error::WebError, handlers::{ @@ -69,7 +70,7 @@ async fn get_provider_metadata(url: &str) -> Result { Ok(provider_metadata) } -async fn make_oidc_client(pool: &DbPool) -> Result { +async fn make_oidc_client(pool: &PgPool) -> Result { let Some(provider) = OpenIdProvider::get_current(pool).await? else { return Err(WebError::ObjectNotFound( "OpenID provider not set".to_string(), @@ -318,8 +319,7 @@ pub async fn auth_callback( phone.map(|v| v.to_string()), ); user.openid_sub = Some(sub); - user.save(&appstate.pool).await?; - user + user.save(&appstate.pool).await? } else { warn!( "User with email address {} is trying to log in through OpenID Connect for the first time, but the account creation is disabled. They should perform an enrollment first.", @@ -346,7 +346,7 @@ pub async fn auth_callback( let device_info = agent.clone().map(|v| get_user_agent_device(&v)); Session::delete_expired(&appstate.pool).await?; let session = Session::new( - user.id.unwrap(), + user.id, SessionState::PasswordVerified, ip_address.clone(), device_info, diff --git a/src/enterprise/handlers/openid_providers.rs b/src/enterprise/handlers/openid_providers.rs index 7289bfb07..d7d170fbb 100644 --- a/src/enterprise/handlers/openid_providers.rs +++ b/src/enterprise/handlers/openid_providers.rs @@ -45,22 +45,24 @@ pub async fn add_openid_provider( State(appstate): State, Json(provider_data): Json, ) -> ApiResult { - let mut new_provider = OpenIdProvider::new( + // Currently, we only support one OpenID provider at a time + let new_provider = OpenIdProvider::new( provider_data.name, provider_data.base_url, provider_data.client_id, provider_data.client_secret, - ); + ) + .upsert(&appstate.pool) + .await?; debug!( "User {} adding OpenID provider {}", session.user.username, new_provider.name ); - // Currently, we only support one OpenID provider at a time - new_provider.upsert(&appstate.pool).await?; info!( "User {} added OpenID client {}", session.user.username, new_provider.name ); + Ok(ApiResponse { json: json!({}), status: StatusCode::CREATED, diff --git a/src/enterprise/license.rs b/src/enterprise/license.rs index 68772854d..74b23f28a 100644 --- a/src/enterprise/license.rs +++ b/src/enterprise/license.rs @@ -9,14 +9,11 @@ use chrono::{DateTime, TimeDelta, Utc}; use humantime::format_duration; use pgp::{types::KeyTrait, Deserializable, SignedPublicKey, StandaloneSignature}; use prost::Message; -use sqlx::error::Error as SqlxError; +use sqlx::{error::Error as SqlxError, PgPool}; use thiserror::Error; use tokio::time::sleep; -use crate::{ - db::{DbPool, Settings}, - VERSION, -}; +use crate::{db::Settings, VERSION}; const LICENSE_SERVER_URL: &str = "https://pkgs.defguard.net/api/license/renew"; @@ -315,7 +312,7 @@ impl License { } /// Get the key from the database - async fn get_key(pool: &DbPool) -> Result, LicenseError> { + async fn get_key(pool: &PgPool) -> Result, LicenseError> { let settings = Settings::get_settings(pool).await?; match settings.license { Some(key) => { @@ -331,7 +328,7 @@ impl License { /// Create the license object based on the license key stored in the database. /// Automatically decodes and deserializes the keys and verifies the signature. - pub async fn load(pool: &DbPool) -> Result, LicenseError> { + pub async fn load(pool: &PgPool) -> Result, LicenseError> { if let Some(key) = Self::get_key(pool).await? { Ok(Some(Self::from_base64(&key)?)) } else { @@ -342,7 +339,7 @@ impl License { /// Try to load the license from the database, if the license requires a renewal, try to renew it. /// If the renewal fails, it will return the old license for the renewal service to renew it later. - pub async fn load_or_renew(pool: &DbPool) -> Result, LicenseError> { + pub async fn load_or_renew(pool: &PgPool) -> Result, LicenseError> { match Self::load(pool).await? { Some(license) => { if license.requires_renewal() { @@ -436,7 +433,7 @@ impl License { /// Exchange the currently stored key for a new one from the license server. /// /// Doesn't update the cached license, nor does it save the new key in the database. -async fn renew_license(db_pool: &DbPool) -> Result { +async fn renew_license(db_pool: &PgPool) -> Result { debug!("Exchanging license for a new one..."); let Some(old_license_key) = Settings::get_settings(db_pool).await?.license else { return Err(LicenseError::LicenseNotFound); @@ -503,12 +500,14 @@ pub fn validate_license(license: Option<&License>) -> Result<(), LicenseError> { } /// Helper function to save the license key string in the database -async fn save_license_key(pool: &DbPool, key: &str) -> Result<(), LicenseError> { +async fn save_license_key(pool: &PgPool, key: &str) -> Result<(), LicenseError> { debug!("Saving the license key to the database..."); let mut settings = Settings::get_settings(pool).await?; settings.license = Some(key.to_string()); - settings.save(pool).await?; - info!("Successfully saved the license key to the database."); + settings.save_license(pool).await?; + + info!("Successfully saved license key to the database."); + Ok(()) } @@ -528,7 +527,9 @@ pub fn update_cached_license(key: Option<&str>) -> Result<(), LicenseError> { None }; set_cached_license(license); + info!("Successfully updated the cached license information."); + Ok(()) } @@ -547,7 +548,7 @@ const CHECK_PERIOD_NO_LICENSE: Duration = Duration::from_secs(24 * 60 * 60); /// Periodic license check task for the case when the license is about to expire const CHECK_PERIOD_RENEWAL_WINDOW: Duration = Duration::from_secs(60 * 60); -pub async fn run_periodic_license_check(pool: DbPool) -> Result<(), LicenseError> { +pub async fn run_periodic_license_check(pool: PgPool) -> Result<(), LicenseError> { let mut check_period: Duration = CHECK_PERIOD; info!( "Starting periodic license renewal check every {}", diff --git a/src/error.rs b/src/error.rs index 9a7e99187..bc13ad5da 100644 --- a/src/error.rs +++ b/src/error.rs @@ -75,7 +75,8 @@ impl From for WebError { match error { LdapError::ObjectNotFound(msg) => Self::ObjectNotFound(msg), LdapError::Ldap(msg) => Self::Ldap(msg), - LdapError::MissingSettings => Self::Ldap("LDAP settings are missing".to_string()), + LdapError::MissingSettings => Self::Ldap("LDAP settings are missing".into()), + LdapError::Database => Self::Ldap("Database problem".into()), } } } diff --git a/src/grpc/auth.rs b/src/grpc/auth.rs index afa8a9e11..fced88ff4 100644 --- a/src/grpc/auth.rs +++ b/src/grpc/auth.rs @@ -1,6 +1,7 @@ use std::sync::{Arc, Mutex}; use jsonwebtoken::errors::Error as JWTError; +use sqlx::PgPool; use tonic::{Request, Response, Status}; use crate::{ @@ -8,20 +9,20 @@ use crate::{ failed_login::{check_username, log_failed_login_attempt, FailedLoginMap}, Claims, ClaimsType, }, - db::{DbPool, User}, + db::User, server_config, }; tonic::include_proto!("auth"); pub struct AuthServer { - pool: DbPool, + pool: PgPool, failed_logins: Arc>, } impl AuthServer { #[must_use] - pub fn new(pool: DbPool, failed_logins: Arc>) -> Self { + pub fn new(pool: PgPool, failed_logins: Arc>) -> Self { Self { pool, failed_logins, diff --git a/src/grpc/desktop_client_mfa.rs b/src/grpc/desktop_client_mfa.rs index 4fb5e5d32..0dfcb2a56 100644 --- a/src/grpc/desktop_client_mfa.rs +++ b/src/grpc/desktop_client_mfa.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use chrono::Utc; +use sqlx::PgPool; use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender}; use tonic::Status; @@ -12,7 +13,7 @@ use crate::{ auth::{Claims, ClaimsType}, db::{ models::device::{DeviceInfo, DeviceNetworkInfo, WireguardNetworkDevice}, - DbPool, Device, GatewayEvent, User, UserInfo, WireguardNetwork, + Device, GatewayEvent, Id, User, UserInfo, WireguardNetwork, }, handlers::mail::send_email_mfa_code_email, mail::Mail, @@ -22,13 +23,13 @@ const CLIENT_SESSION_TIMEOUT: u64 = 60 * 5; // 10 minutes struct ClientLoginSession { method: MfaMethod, - location: WireguardNetwork, - device: Device, - user: User, + location: WireguardNetwork, + device: Device, + user: User, } pub(super) struct ClientMfaServer { - pool: DbPool, + pool: PgPool, mail_tx: UnboundedSender, wireguard_tx: Sender, sessions: HashMap, @@ -37,7 +38,7 @@ pub(super) struct ClientMfaServer { impl ClientMfaServer { #[must_use] pub fn new( - pool: DbPool, + pool: PgPool, mail_tx: UnboundedSender, wireguard_tx: Sender, ) -> Self { @@ -224,12 +225,8 @@ impl ClientMfaServer { })?; // fetch device config for the location - let Ok(Some(mut network_device)) = WireguardNetworkDevice::find( - &mut *transaction, - device.id.expect("Missing device ID"), - location.id.expect("Missing location ID"), - ) - .await + let Ok(Some(mut network_device)) = + WireguardNetworkDevice::find(&mut *transaction, device.id, location.id).await else { error!("Failed to fetch network config for device {device} and location {location}"); return Err(Status::internal("unexpected error")); @@ -257,7 +254,7 @@ impl ClientMfaServer { let device_info = DeviceInfo { device: device.clone(), network_info: vec![DeviceNetworkInfo { - network_id: location.id.expect("Missing location ID"), + network_id: location.id, device_wireguard_ip: network_device.wireguard_ip, preshared_key: network_device.preshared_key, is_authorized: network_device.is_authorized, diff --git a/src/grpc/enrollment.rs b/src/grpc/enrollment.rs index 9ea99b880..c308d7523 100644 --- a/src/grpc/enrollment.rs +++ b/src/grpc/enrollment.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use ipnetwork::IpNetwork; -use sqlx::Transaction; +use sqlx::{PgPool, Transaction}; use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender}; use tonic::Status; use uaparser::UserAgentParser; @@ -21,7 +21,7 @@ use crate::{ enrollment::{Token, TokenError, ENROLLMENT_TOKEN_TYPE}, polling_token::PollingToken, }, - DbPool, Device, GatewayEvent, Settings, User, + Device, GatewayEvent, Id, Settings, User, }, enterprise::db::models::enterprise_settings::EnterpriseSettings, grpc::utils::build_device_config_response, @@ -34,7 +34,7 @@ use crate::{ }; pub(super) struct EnrollmentServer { - pool: DbPool, + pool: PgPool, wireguard_tx: Sender, mail_tx: UnboundedSender, user_agent_parser: Arc, @@ -44,7 +44,7 @@ pub(super) struct EnrollmentServer { impl EnrollmentServer { #[must_use] pub fn new( - pool: DbPool, + pool: PgPool, wireguard_tx: Sender, mail_tx: UnboundedSender, user_agent_parser: Arc, @@ -452,7 +452,11 @@ impl EnrollmentServer { request.pubkey, user.username, user.id ); - let mut device = Device::new(request.name, request.pubkey, enrollment.user_id); + let device = Device::new( + request.name.clone(), + request.pubkey.clone(), + enrollment.user_id, + ); debug!( "Creating new device for user {}({:?}) {device:?}.", user.username, user.id, @@ -462,10 +466,10 @@ impl EnrollmentServer { error!("Failed to begin transaction: {err}"); Status::internal("unexpected error") })?; - device.save(&mut *transaction).await.map_err(|err| { + let device = device.save(&mut *transaction).await.map_err(|err| { error!( "Failed to save device {}, pubkey {} for user {}({:?}): {err}", - device.name, device.wireguard_pubkey, user.username, user.id, + request.name, request.pubkey, user.username, user.id, ); Status::internal("unexpected error") })?; @@ -539,17 +543,16 @@ impl EnrollmentServer { "Creating polling token for further client communication for device {}, user {}({:?})", device.wireguard_pubkey, user.username, user.id, ); - let mut token = PollingToken::new(device.id.ok_or_else(|| { - error!("No device id"); - Status::internal("unexpected error") - })?); - token.save(&mut *transaction).await.map_err(|err| { - error!( - "Failed to save PollingToken for device {}, user {}({:?}): {err}", - device.wireguard_pubkey, user.username, user.id - ); - Status::internal("failed to save polling token") - })?; + let token = PollingToken::new(device.id) + .save(&mut *transaction) + .await + .map_err(|err| { + error!( + "Failed to save PollingToken for device {}, user {}({:?}): {err}", + device.wireguard_pubkey, user.username, user.id + ); + Status::internal("failed to save polling token") + })?; info!( "Created polling token for further client communication for device: {}, user {}({:?})", device.wireguard_pubkey, user.username, user.id, @@ -616,8 +619,8 @@ impl EnrollmentServer { } } -impl From for AdminInfo { - fn from(admin: User) -> Self { +impl From> for AdminInfo { + fn from(admin: User) -> Self { Self { name: format!("{} {}", admin.first_name, admin.last_name), phone_number: admin.phone, @@ -627,7 +630,7 @@ impl From for AdminInfo { } impl InitialUserInfo { - async fn from_user(pool: &DbPool, user: User) -> Result { + async fn from_user(pool: &PgPool, user: User) -> Result { let enrolled = user.is_enrolled(); let devices = user.user_devices(pool).await?; let device_names = devices.into_iter().map(|dev| dev.device.name).collect(); @@ -667,10 +670,10 @@ impl From for ProtoDeviceConfig { } } -impl From for ProtoDevice { - fn from(device: Device) -> Self { +impl From> for ProtoDevice { + fn from(device: Device) -> Self { Self { - id: device.get_id().expect("Failed to get device ID"), + id: device.id, name: device.name, pubkey: device.wireguard_pubkey, user_id: device.user_id, @@ -685,7 +688,7 @@ impl Token { &self, transaction: &mut Transaction<'_, sqlx::Postgres>, mail_tx: &UnboundedSender, - user: &User, + user: &User, settings: &Settings, ip_address: &str, device_info: Option<&str>, @@ -715,8 +718,8 @@ impl Token { // Notify admin that a user has completed enrollment fn send_admin_notification( mail_tx: &UnboundedSender, - admin: &User, - user: &User, + admin: &User, + user: &User, ip_address: &str, device_info: Option<&str>, ) -> Result<(), TokenError> { diff --git a/src/grpc/gateway.rs b/src/grpc/gateway.rs index 9e637e41c..64125f34a 100644 --- a/src/grpc/gateway.rs +++ b/src/grpc/gateway.rs @@ -5,7 +5,7 @@ use std::{ }; use chrono::{DateTime, Utc}; -use sqlx::{query, Error as SqlxError, PgExecutor}; +use sqlx::{query, Error as SqlxError, PgExecutor, PgPool}; use tokio::{ sync::{ broadcast::{Receiver as BroadcastReceiver, Sender}, @@ -20,7 +20,7 @@ use super::GatewayMap; use crate::{ db::{ models::wireguard::{WireguardNetwork, WireguardPeerStats}, - DbPool, Device, GatewayEvent, + Device, GatewayEvent, Id, NoId, }, mail::Mail, }; @@ -28,13 +28,13 @@ use crate::{ tonic::include_proto!("gateway"); pub struct GatewayServer { - pool: DbPool, + pool: PgPool, state: Arc>, wireguard_tx: Sender, mail_tx: UnboundedSender, } -impl WireguardNetwork { +impl WireguardNetwork { /// Get a list of all allowed peers /// /// Each device is marked as allowed or not allowed in a given network, @@ -43,10 +43,10 @@ impl WireguardNetwork { where E: PgExecutor<'e>, { - debug!("Fetching all peers for network {}", self.id.unwrap()); + debug!("Fetching all peers for network {}", self.id); let rows = query!( - "SELECT d.wireguard_pubkey as pubkey, preshared_key, \ - array[host(wnd.wireguard_ip)] as \"allowed_ips!: Vec\" \ + "SELECT d.wireguard_pubkey pubkey, preshared_key, \ + array[host(wnd.wireguard_ip)] \"allowed_ips!: Vec\" \ FROM wireguard_network_device wnd \ JOIN device d ON wnd.device_id = d.id \ JOIN \"user\" u ON d.user_id = u.id \ @@ -79,7 +79,7 @@ impl GatewayServer { /// Create new gateway server instance #[must_use] pub fn new( - pool: DbPool, + pool: PgPool, state: Arc>, wireguard_tx: Sender, mail_tx: UnboundedSender, @@ -134,7 +134,7 @@ impl GatewayServer { } } -fn gen_config(network: &WireguardNetwork, peers: Vec) -> Configuration { +fn gen_config(network: &WireguardNetwork, peers: Vec) -> Configuration { Configuration { name: network.name.clone(), port: network.port as u32, @@ -145,13 +145,13 @@ fn gen_config(network: &WireguardNetwork, peers: Vec) -> Configuration { } impl WireguardPeerStats { - fn from_peer_stats(stats: PeerStats, network_id: i64) -> Self { + fn from_peer_stats(stats: PeerStats, network_id: Id) -> Self { let endpoint = match stats.endpoint { endpoint if endpoint.is_empty() => None, _ => Some(stats.endpoint), }; Self { - id: None, + id: NoId, network: network_id, endpoint, device_id: -1, @@ -168,8 +168,8 @@ impl WireguardPeerStats { /// Helper struct for handling gateway events struct GatewayUpdatesHandler { - network_id: i64, - network: WireguardNetwork, + network_id: Id, + network: WireguardNetwork, gateway_hostname: String, events_rx: BroadcastReceiver, tx: mpsc::Sender>, @@ -177,8 +177,8 @@ struct GatewayUpdatesHandler { impl GatewayUpdatesHandler { pub fn new( - network_id: i64, - network: WireguardNetwork, + network_id: Id, + network: WireguardNetwork, gateway_hostname: String, events_rx: BroadcastReceiver, tx: mpsc::Sender>, @@ -313,7 +313,7 @@ impl GatewayUpdatesHandler { /// Sends updated network configuration async fn send_network_update( &self, - network: &WireguardNetwork, + network: &WireguardNetwork, peers: Vec, update_type: i32, ) -> Result<(), Status> { @@ -436,10 +436,10 @@ impl GatewayUpdatesHandler { pub struct GatewayUpdatesStream { task_handle: JoinHandle<()>, rx: Receiver>, - network_id: i64, + network_id: Id, gateway_hostname: String, gateway_state: Arc>, - pool: DbPool, + pool: PgPool, } impl GatewayUpdatesStream { @@ -447,10 +447,10 @@ impl GatewayUpdatesStream { pub fn new( task_handle: JoinHandle<()>, rx: Receiver>, - network_id: i64, + network_id: Id, gateway_hostname: String, gateway_state: Arc>, - pool: DbPool, + pool: PgPool, ) -> Self { Self { task_handle, @@ -507,15 +507,7 @@ impl gateway_service_server::GatewayService for GatewayServer { // Get device by public key and fill in stats.device_id // FIXME: keep an in-memory device map to avoid repeated DB requests stats.device_id = match Device::find_by_pubkey(&self.pool, &public_key).await { - Ok(Some(device)) => device.id.ok_or_else(|| { - Status::new( - Code::Internal, - format!( - "Device {} (public key: {public_key}) has no ID", - device.name - ), - ) - })?, + Ok(Some(device)) => device.id, Ok(None) => { error!("Device with public key {public_key} not found"); return Err(Status::new( @@ -532,16 +524,20 @@ impl gateway_service_server::GatewayService for GatewayServer { } }; // Save stats to db - if let Err(err) = stats.save(&self.pool).await { - error!("Saving WireGuard peer stats to db failed: {err}"); - return Err(Status::new( - Code::Internal, - format!("Saving WireGuard peer stats to db failed: {err}"), - )); - } + let stats = match stats.save(&self.pool).await { + Ok(stats) => stats, + Err(err) => { + error!("Saving WireGuard peer stats to db failed: {err}"); + return Err(Status::new( + Code::Internal, + format!("Saving WireGuard peer stats to db failed: {err}"), + )); + } + }; info!("Saved WireGuard peer stats to db."); debug!("WireGuard peer stats: {stats:?}"); } + Ok(Response::new(())) } diff --git a/src/grpc/interceptor.rs b/src/grpc/interceptor.rs index 75d254459..190d0f09e 100644 --- a/src/grpc/interceptor.rs +++ b/src/grpc/interceptor.rs @@ -2,8 +2,8 @@ use tonic::{service::Interceptor, Status}; use crate::auth::{Claims, ClaimsType}; -/// Auth interceptor used by GRPC services. Verifies JWT token sent -/// in GRPC metadata under "authorization" key. +/// Auth interceptor used by gRPC services. Verifies JWT token sent +/// in gRPC metadata under "authorization" key. #[derive(Clone)] pub struct JwtInterceptor { claims_type: ClaimsType, diff --git a/src/grpc/mod.rs b/src/grpc/mod.rs index e1cf70f88..9f2fbcf1b 100644 --- a/src/grpc/mod.rs +++ b/src/grpc/mod.rs @@ -12,6 +12,8 @@ use std::{ use chrono::{Duration as ChronoDuration, NaiveDateTime, Utc}; use reqwest::Url; use serde::Serialize; +#[cfg(feature = "worker")] +use sqlx::PgPool; use thiserror::Error; use tokio::{ sync::{ @@ -44,7 +46,7 @@ use self::{ }; use crate::{ auth::failed_login::FailedLoginMap, - db::{AppEvent, Settings}, + db::{AppEvent, Id, Settings}, enterprise::{ db::models::enterprise_settings::EnterpriseSettings, grpc::polling::PollingServer, @@ -55,10 +57,7 @@ use crate::{ server_config, }; #[cfg(feature = "worker")] -use crate::{ - auth::ClaimsType, - db::{DbPool, GatewayEvent}, -}; +use crate::{auth::ClaimsType, db::GatewayEvent}; mod auth; mod desktop_client_mfa; @@ -80,10 +79,9 @@ use proto::{core_request, proxy_client::ProxyClient, CoreError, CoreResponse}; // Helper struct used to handle gateway state // gateways are grouped by network -type NetworkId = i64; type GatewayHostname = String; #[derive(Debug)] -pub struct GatewayMap(HashMap>); +pub struct GatewayMap(HashMap>); #[derive(Error, Debug)] pub enum GatewayMapError { @@ -110,7 +108,7 @@ impl GatewayMap { // as a sort of "registration" pub fn add_gateway( &mut self, - network_id: i64, + network_id: Id, network_name: &str, hostname: String, name: Option, @@ -130,7 +128,7 @@ impl GatewayMap { } // remove gateway from map - pub fn remove_gateway(&mut self, network_id: i64, uid: Uuid) -> Result<(), GatewayMapError> { + pub fn remove_gateway(&mut self, network_id: Id, uid: Uuid) -> Result<(), GatewayMapError> { debug!("Removing gateway from network {network_id}"); if let Some(network_gateway_map) = self.0.get_mut(&network_id) { // find gateway by uuid @@ -165,7 +163,7 @@ impl GatewayMap { // we assume that the gateway is already present in hashmap pub fn connect_gateway( &mut self, - network_id: i64, + network_id: Id, hostname: &str, ) -> Result<(), GatewayMapError> { debug!("Connecting gateway {hostname} in network {network_id}"); @@ -194,9 +192,9 @@ impl GatewayMap { // change gateway status to disconnected pub fn disconnect_gateway( &mut self, - network_id: i64, + network_id: Id, hostname: String, - pool: &DbPool, + pool: &PgPool, ) -> Result<(), GatewayMapError> { debug!("Disconnecting gateway {hostname} in network {network_id}"); if let Some(network_gateway_map) = self.0.get_mut(&network_id) { @@ -216,7 +214,7 @@ impl GatewayMap { // return `true` if at least one gateway in a given network is connected #[must_use] - pub fn connected(&self, network_id: i64) -> bool { + pub fn connected(&self, network_id: Id) -> bool { match self.0.get(&network_id) { Some(network_gateway_map) => network_gateway_map .values() @@ -227,7 +225,7 @@ impl GatewayMap { // return a list af aff statuses af all gateways in a given network #[must_use] - pub fn get_network_gateway_status(&self, network_id: i64) -> Vec { + pub fn get_network_gateway_status(&self, network_id: Id) -> Vec { match self.0.get(&network_id) { Some(network_gateway_map) => network_gateway_map.clone().into_values().collect(), None => Vec::new(), @@ -236,7 +234,7 @@ impl GatewayMap { // return gateway name #[must_use] - pub fn get_network_gateway_name(&self, network_id: i64, hostname: &str) -> Option { + pub fn get_network_gateway_name(&self, network_id: Id, hostname: &str) -> Option { match self.0.get(&network_id) { Some(network_gateway_map) => { if let Some(state) = network_gateway_map.get(hostname) { @@ -260,7 +258,7 @@ impl Default for GatewayMap { pub struct GatewayState { pub uid: Uuid, pub connected: bool, - pub network_id: i64, + pub network_id: Id, pub network_name: String, pub name: Option, pub hostname: String, @@ -275,7 +273,7 @@ pub struct GatewayState { impl GatewayState { #[must_use] pub fn new>( - network_id: i64, + network_id: Id, network_name: S, hostname: S, name: Option, @@ -297,7 +295,7 @@ impl GatewayState { /// Send gateway disconnected notification /// Sends notification only if last notification time is bigger than specified in config - fn send_disconnect_notification(&mut self, pool: &DbPool) { + fn send_disconnect_notification(&mut self, pool: &PgPool) { debug!("Sending gateway disconnect email notification"); // Clone here because self doesn't live long enough let name = self.name.clone(); @@ -350,7 +348,7 @@ impl From for CoreError { /// Bi-directional gRPC stream for comminication with Defguard proxy. pub async fn run_grpc_bidi_stream( - pool: DbPool, + pool: PgPool, wireguard_tx: Sender, mail_tx: UnboundedSender, user_agent_parser: Arc, @@ -555,7 +553,7 @@ pub async fn run_grpc_bidi_stream( /// Runs gRPC server with core services. pub async fn run_grpc_server( worker_state: Arc>, - pool: DbPool, + pool: PgPool, gateway_state: Arc>, wireguard_tx: Sender, mail_tx: UnboundedSender, @@ -676,7 +674,7 @@ impl InstanceInfo { } } -impl From for crate::grpc::proto::InstanceInfo { +impl From for proto::InstanceInfo { fn from(instance: InstanceInfo) -> Self { Self { name: instance.name, diff --git a/src/grpc/password_reset.rs b/src/grpc/password_reset.rs index bea8fc54b..6a5516c16 100644 --- a/src/grpc/password_reset.rs +++ b/src/grpc/password_reset.rs @@ -1,14 +1,15 @@ +use sqlx::PgPool; use tokio::sync::mpsc::UnboundedSender; use tonic::Status; use super::proto::{ - PasswordResetInitializeRequest, PasswordResetRequest, PasswordResetStartRequest, + DeviceInfo, PasswordResetInitializeRequest, PasswordResetRequest, PasswordResetStartRequest, PasswordResetStartResponse, }; use crate::{ db::{ models::enrollment::{Token, PASSWORD_RESET_TOKEN_TYPE}, - DbPool, User, + User, }, handlers::{ mail::{send_password_reset_email, send_password_reset_success_email}, @@ -20,14 +21,14 @@ use crate::{ }; pub(super) struct PasswordResetServer { - pool: DbPool, + pool: PgPool, mail_tx: UnboundedSender, // ldap_feature_active: bool, } impl PasswordResetServer { #[must_use] - pub fn new(pool: DbPool, mail_tx: UnboundedSender) -> Self { + pub fn new(pool: PgPool, mail_tx: UnboundedSender) -> Self { // FIXME: check if LDAP feature is enabled // let ldap_feature_active = true; Self { @@ -70,7 +71,7 @@ impl PasswordResetServer { pub async fn request_password_reset( &self, request: PasswordResetInitializeRequest, - req_device_info: Option, + req_device_info: Option, ) -> Result<(), Status> { let config = server_config(); debug!("Starting password reset request"); @@ -114,14 +115,10 @@ impl PasswordResetServer { Status::internal("unexpected error") })?; - Token::delete_unused_user_password_reset_tokens( - &mut transaction, - user.id.expect("Missing user ID"), - ) - .await?; + Token::delete_unused_user_password_reset_tokens(&mut transaction, user.id).await?; let enrollment = Token::new( - user.id.expect("Missing user ID"), + user.id, None, Some(email.clone()), config.password_reset_token_timeout.as_secs(), @@ -211,7 +208,7 @@ impl PasswordResetServer { pub async fn reset_password( &self, request: PasswordResetRequest, - req_device_info: Option, + req_device_info: Option, ) -> Result<(), Status> { debug!("Starting password reset: {request:?}"); let enrollment = self.validate_session(&request.token).await?; diff --git a/src/grpc/utils.rs b/src/grpc/utils.rs index 664b576a0..8078075cf 100644 --- a/src/grpc/utils.rs +++ b/src/grpc/utils.rs @@ -1,4 +1,5 @@ use ipnetwork::IpNetwork; +use sqlx::PgPool; use tonic::Status; use super::{ @@ -11,13 +12,13 @@ use crate::{ device::WireguardNetworkDevice, polling_token::PollingToken, wireguard::WireguardNetwork, }, - DbPool, Device, Settings, User, + Device, Settings, User, }, enterprise::db::models::enterprise_settings::EnterpriseSettings, }; pub(crate) async fn build_device_config_response( - pool: &DbPool, + pool: &PgPool, pubkey: &str, // Whether to make a new polling token for the device new_token: bool, @@ -61,15 +62,12 @@ pub(crate) async fn build_device_config_response( Status::internal("unexpected error") })?; for network in networks { - let (Some(device_id), Some(network_id)) = (device.id, network.id) else { - continue; - }; - let wireguard_network_device = WireguardNetworkDevice::find(pool, device_id, network_id) + let wireguard_network_device = WireguardNetworkDevice::find(pool, device.id, network.id) .await .map_err(|err| { error!( "Failed to fetch wireguard network device for device {} and network {}: {err}", - device_id, network_id + device.id, network.id ); Status::internal(format!("unexpected error: {err}")) })?; @@ -82,7 +80,7 @@ pub(crate) async fn build_device_config_response( .join(","); let config = ProtoDeviceConfig { config: device.create_config(&network, &wireguard_network_device), - network_id, + network_id: network.id, network_name: network.name, assigned_ip: wireguard_network_device.wireguard_ip.to_string(), endpoint: format!("{}:{}", network.endpoint, network.port), @@ -108,32 +106,19 @@ pub(crate) async fn build_device_config_response( // 1. Delete existing polling token for the device, if it exists // 2. Create a new polling token for the device - PollingToken::delete_for_device_id( - &mut *transaction, - device.id.ok_or_else(|| { - error!( - "Device {} has no id, can't delete polling token", - device.wireguard_pubkey - ); - Status::internal("unexpected error") - })?, - ) - .await - .map_err(|err| { - error!("Failed to delete polling token: {err}"); - Status::internal(format!("unexpected error: {err}")) - })?; - let mut new_token = PollingToken::new(device.id.ok_or_else(|| { - error!( - "Device {} has no id, can't create a polling token", - device.wireguard_pubkey - ); - Status::internal("unexpected error") - })?); - new_token.save(&mut *transaction).await.map_err(|err| { - error!("Failed to save new polling token: {err}"); - Status::internal(format!("unexpected error: {err}")) - })?; + PollingToken::delete_for_device_id(&mut *transaction, device.id) + .await + .map_err(|err| { + error!("Failed to delete polling token: {err}"); + Status::internal(format!("unexpected error: {err}")) + })?; + let new_token = PollingToken::new(device.id) + .save(&mut *transaction) + .await + .map_err(|err| { + error!("Failed to save new polling token: {err}"); + Status::internal(format!("unexpected error: {err}")) + })?; transaction.commit().await.map_err(|err| { error!("Failed to commit transaction while making a new polling token: {err}"); diff --git a/src/grpc/worker.rs b/src/grpc/worker.rs index 4636cc85e..a81636278 100644 --- a/src/grpc/worker.rs +++ b/src/grpc/worker.rs @@ -6,14 +6,14 @@ use std::{ time::Instant, }; -use sqlx::query; +use sqlx::{query, PgPool}; use tokio::sync::mpsc::UnboundedSender; use tonic::{Request, Response, Status}; use super::{Job, JobResponse, WorkerDetail, WorkerInfo, WorkerState}; use crate::db::{ models::authentication_key::{AuthenticationKey, AuthenticationKeyType}, - AppEvent, DbPool, HWKeyUserData, User, YubiKey, + AppEvent, HWKeyUserData, User, YubiKey, }; tonic::include_proto!("worker"); @@ -179,13 +179,13 @@ impl WorkerState { } pub struct WorkerServer { - pool: DbPool, + pool: PgPool, state: Arc>, } impl WorkerServer { #[must_use] - pub fn new(pool: DbPool, state: Arc>) -> Self { + pub fn new(pool: PgPool, state: Arc>) -> Self { Self { pool, state } } } @@ -260,54 +260,46 @@ impl worker_service_server::WorkerService for WorkerServer { Ok(Some(user)) => { // create yubikey // FIXME: pass name from user input this is temporary solution - if let Some(user_id) = user.id { - let yubi_count_res = query!( - "SELECT COUNT(*) FROM \"yubikey\" WHERE user_id = $1", - user.id - ) - .fetch_one(&self.pool) - .await - .map_err(|_| Status::internal("Failed to count keys"))?; - // FIXME: names may collide - let name = match yubi_count_res.count { - Some(count) => { - let name = format!("YubiKey {}", count + 1); - name - } - None => "YubiKey".to_string(), - }; - let mut new_yubi = YubiKey::new(name, message.yubikey_serial, user_id); - new_yubi - .save(&self.pool) - .await - .map_err(|_| Status::internal("Failed to save yubikey"))?; - if let Some(key_id) = new_yubi.id { - let mut ssh = AuthenticationKey::new( - user_id, - message.ssh_key, - None, - AuthenticationKeyType::Ssh, - Some(key_id), - ); - let mut gpg = AuthenticationKey::new( - user_id, - message.public_key, - None, - AuthenticationKeyType::Gpg, - Some(key_id), - ); - ssh.save(&self.pool) - .await - .map_err(|_| Status::internal("Failed to save auth key"))?; - gpg.save(&self.pool) - .await - .map_err(|_| Status::internal("Failed to save auth key"))?; - } else { - return Err(Status::internal("Yubikey did not get an id")); + let yubi_count_res = query!( + "SELECT COUNT(*) FROM \"yubikey\" WHERE user_id = $1", + user.id + ) + .fetch_one(&self.pool) + .await + .map_err(|_| Status::internal("Failed to count keys"))?; + // FIXME: names may collide + let name = match yubi_count_res.count { + Some(count) => { + let name = format!("YubiKey {}", count + 1); + name } - } else { - return Err(Status::internal("User has no ID")); - } + None => "YubiKey".to_string(), + }; + let new_yubi = YubiKey::new(name, message.yubikey_serial, user.id) + .save(&self.pool) + .await + .map_err(|_| Status::internal("Failed to save YubiKey"))?; + let key_id = new_yubi.id; + let ssh = AuthenticationKey::new( + user.id, + message.ssh_key, + None, + AuthenticationKeyType::Ssh, + Some(key_id), + ); + let gpg = AuthenticationKey::new( + user.id, + message.public_key, + None, + AuthenticationKeyType::Gpg, + Some(key_id), + ); + ssh.save(&self.pool) + .await + .map_err(|_| Status::internal("Failed to save auth key"))?; + gpg.save(&self.pool) + .await + .map_err(|_| Status::internal("Failed to save auth key"))?; } Ok(None) => info!("User {username} not found"), Err(err) => error!("Error {err}"), diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs index 1c4860638..953ed2f34 100644 --- a/src/handlers/auth.rs +++ b/src/handlers/auth.rs @@ -57,7 +57,7 @@ pub async fn authenticate( // check if user can proceed with login check_username(&appstate.failed_logins, &username)?; - let user: User = match User::find_by_username(&appstate.pool, &username).await { + let user = match User::find_by_username(&appstate.pool, &username).await { Ok(Some(user)) => match user.verify_password(&data.password) { Ok(()) => { if user.is_active { @@ -129,7 +129,7 @@ pub async fn authenticate( debug!("Creating new session for user {username}"); let session = Session::new( - user.id.unwrap(), + user.id, SessionState::PasswordVerified, ip_address.clone(), device_info, @@ -283,8 +283,7 @@ pub async fn webauthn_init( user.username ); // passkeys to exclude - let passkeys = - WebAuthn::passkeys_for_user(&appstate.pool, user.id.expect("User ID missing")).await?; + let passkeys = WebAuthn::passkeys_for_user(&appstate.pool, user.id).await?; match appstate.webauthn.start_passkey_registration( Uuid::new_v4(), &user.username, @@ -355,7 +354,7 @@ pub async fn webauthn_finish( .await? .ok_or(WebError::WebauthnRegistration("User not found".into()))?; let recovery_codes = RecoveryCodes::new(user.get_recovery_codes(&appstate.pool).await?); - let mut webauthn = WebAuthn::new(session.session.user_id, webauth_reg.name, &passkey)?; + let webauthn = WebAuthn::new(session.session.user_id, webauth_reg.name, &passkey)?; webauthn.save(&appstate.pool).await?; if user.mfa_method == MFAMethod::None { send_mfa_configured_email( @@ -700,7 +699,7 @@ pub async fn web3auth_start( Json(data): Json, ) -> ApiResult { debug!("Starting web3 authentication for wallet {}", data.address); - match Settings::find_by_id(&appstate.pool, 1).await? { + match Settings::get(&appstate.pool).await? { Some(settings) => { let challenge = Wallet::format_challenge(&data.address, &settings.challenge_template); session diff --git a/src/handlers/group.rs b/src/handlers/group.rs index ee5c7e3ff..97f45dd46 100644 --- a/src/handlers/group.rs +++ b/src/handlers/group.rs @@ -61,7 +61,7 @@ pub(crate) async fn bulk_assign_to_groups( debug!("Assigning groups to users."); let users = query_as!( User, - "SELECT id \"id?\", username, password_hash, last_name, first_name, email, \ + "SELECT id, username, password_hash, last_name, first_name, email, \ phone, mfa_enabled, totp_enabled, email_mfa_enabled, \ totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ FROM \"user\" WHERE id = ANY($1)", @@ -99,6 +99,7 @@ pub(crate) async fn bulk_assign_to_groups( transaction.commit().await?; WireguardNetwork::sync_all_networks(&appstate).await?; info!("Assigned {} groups to {} users.", groups.len(), users.len()); + Ok(ApiResponse { json: json!({}), status: StatusCode::OK, @@ -137,9 +138,9 @@ pub(crate) async fn list_groups_info( debug!("Listing groups info"); let q_result = query_as!( GroupInfo, - "SELECT g.name as name, \ - COALESCE(ARRAY_AGG(DISTINCT u.username) FILTER (WHERE u.username IS NOT NULL), '{}') as \"members!\", \ - COALESCE(ARRAY_AGG(DISTINCT wn.name) FILTER (WHERE wn.name IS NOT NULL), '{}') as \"vpn_locations!\" \ + "SELECT g.name name, \ + COALESCE(ARRAY_AGG(DISTINCT u.username) FILTER (WHERE u.username IS NOT NULL), '{}') \"members!\", \ + COALESCE(ARRAY_AGG(DISTINCT wn.name) FILTER (WHERE wn.name IS NOT NULL), '{}') \"vpn_locations!\" \ FROM \"group\" g \ LEFT JOIN \"group_user\" gu ON gu.group_id = g.id \ LEFT JOIN \"user\" u ON u.id = gu.user_id \ @@ -262,9 +263,8 @@ pub(crate) async fn create_group( // FIXME: LDAP operations are not reverted. let mut transaction = appstate.pool.begin().await?; - let mut group = Group::new(&group_info.name); // FIXME: conflicts must not return internal server error (500). - group.save(&appstate.pool).await?; + let group = Group::new(&group_info.name).save(&appstate.pool).await?; // TODO: create group in LDAP for username in &group_info.members { @@ -282,6 +282,7 @@ pub(crate) async fn create_group( WireguardNetwork::sync_all_networks(&appstate).await?; info!("Created group {}", group_info.name); + Ok(ApiResponse { json: json!(group_info), status: StatusCode::CREATED, diff --git a/src/handlers/mail.rs b/src/handlers/mail.rs index b759cedef..f2cc35f90 100644 --- a/src/handlers/mail.rs +++ b/src/handlers/mail.rs @@ -17,13 +17,13 @@ use super::{ApiResponse, ApiResult}; use crate::{ appstate::AppState, auth::{AdminRole, SessionInfo}, - db::{models::enrollment::TokenError, MFAMethod, Session, User}, + db::{models::enrollment::TokenError, Id, MFAMethod, Session, User}, error::WebError, mail::{Attachment, Mail}, server_config, support::dump_config, templates::{self, support_data_mail, TemplateError, TemplateLocation}, - DbPool, + PgPool, }; static TEST_MAIL_SUBJECT: &str = "Defguard email test"; @@ -213,7 +213,7 @@ pub async fn send_gateway_disconnected_email( network_name: String, gateway_adress: &str, mail_tx: &UnboundedSender, - pool: &DbPool, + pool: &PgPool, ) -> Result<(), WebError> { debug!("Sending gateway disconnected mail to all admin users"); let admin_users = User::find_by_group_name(pool, &server_config().admin_groupname).await?; @@ -310,7 +310,7 @@ pub async fn send_new_device_ocid_login_email( pub fn send_mfa_configured_email( session: Option<&Session>, - user: &User, + user: &User, mfa_method: &MFAMethod, mail_tx: &UnboundedSender, ) -> Result<(), TemplateError> { @@ -341,7 +341,7 @@ pub fn send_mfa_configured_email( } pub fn send_email_mfa_activation_email( - user: &User, + user: &User, mail_tx: &UnboundedSender, session: &Session, ) -> Result<(), TemplateError> { @@ -376,7 +376,7 @@ pub fn send_email_mfa_activation_email( } pub fn send_email_mfa_code_email( - user: &User, + user: &User, mail_tx: &UnboundedSender, session: Option<&Session>, ) -> Result<(), TemplateError> { @@ -411,7 +411,7 @@ pub fn send_email_mfa_code_email( } pub fn send_password_reset_email( - user: &User, + user: &User, mail_tx: &UnboundedSender, service_url: Url, token: &str, @@ -443,7 +443,7 @@ pub fn send_password_reset_email( } pub fn send_password_reset_success_email( - user: &User, + user: &User, mail_tx: &UnboundedSender, ip_address: Option<&str>, device_info: Option<&str>, diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index bef60c8c9..55edbf4da 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -4,6 +4,7 @@ use axum::{ Json, }; use serde_json::{json, Value}; +use sqlx::PgPool; use utoipa::ToSchema; use webauthn_rs::prelude::RegisterPublicKeyCredential; @@ -11,7 +12,7 @@ use webauthn_rs::prelude::RegisterPublicKeyCredential; use crate::db::Device; use crate::{ auth::SessionInfo, - db::{DbPool, User, UserInfo}, + db::{Id, NoId, User, UserInfo, WebHook}, enterprise::license::LicenseError, error::WebError, VERSION, @@ -267,7 +268,7 @@ pub struct WalletSignature { #[derive(Deserialize, Serialize, ToSchema)] pub struct WalletChallenge { - pub id: i64, + pub id: Id, pub message: String, } @@ -304,6 +305,34 @@ impl RecoveryCodes { } } +#[derive(Deserialize)] +pub struct WebHookData { + pub url: String, + pub description: String, + pub token: String, + pub enabled: bool, + pub on_user_created: bool, + pub on_user_deleted: bool, + pub on_user_modified: bool, + pub on_hwkey_provision: bool, +} + +impl From for WebHook { + fn from(data: WebHookData) -> Self { + Self { + id: NoId, + url: data.url, + description: data.description, + token: data.token, + enabled: data.enabled, + on_user_created: data.on_user_created, + on_user_deleted: data.on_user_deleted, + on_user_modified: data.on_user_modified, + on_hwkey_provision: data.on_hwkey_provision, + } + } +} + /// Return type needed to know if user came from openid flow /// with optional url to redirect him later if yes #[derive(Serialize, Deserialize)] @@ -315,10 +344,10 @@ pub struct AuthResponse { /// Try to fetch [`User`] if the username is of the currently logged in user, or /// the logged in user is an admin. pub async fn user_for_admin_or_self( - pool: &DbPool, + pool: &PgPool, session: &SessionInfo, username: &str, -) -> Result { +) -> Result, WebError> { if session.user.username == username || session.is_admin { debug!("The user meets one or both of these conditions: 1) the user from the current session has admin privileges, 2) the user performs this operation on themself."); if let Some(user) = User::find_by_username(pool, username).await? { @@ -342,10 +371,10 @@ pub async fn user_for_admin_or_self( /// the logged in user is an admin. #[cfg(feature = "wireguard")] pub async fn device_for_admin_or_self( - pool: &DbPool, + pool: &PgPool, session: &SessionInfo, - id: i64, -) -> Result { + id: Id, +) -> Result, WebError> { let fetch = if session.is_admin { Device::find_by_id(pool, id).await } else { diff --git a/src/handlers/openid_clients.rs b/src/handlers/openid_clients.rs index 50ec5dedf..68779814f 100644 --- a/src/handlers/openid_clients.rs +++ b/src/handlers/openid_clients.rs @@ -20,12 +20,11 @@ pub async fn add_openid_client( State(appstate): State, Json(data): Json, ) -> ApiResult { - let mut client = OAuth2Client::from_new(data); + let client = OAuth2Client::from_new(data).save(&appstate.pool).await?; debug!( "User {} adding OpenID client {}", session.user.username, client.name ); - client.save(&appstate.pool).await?; info!( "User {} added OpenID client {}", session.user.username, client.name diff --git a/src/handlers/openid_flow.rs b/src/handlers/openid_flow.rs index d48c04886..0c166ed25 100644 --- a/src/handlers/openid_flow.rs +++ b/src/handlers/openid_flow.rs @@ -32,6 +32,7 @@ use serde::{ ser::{Serialize, Serializer}, }; use serde_json::json; +use sqlx::PgPool; use time::Duration; use super::{ApiResponse, ApiResult, SESSION_COOKIE_NAME}; @@ -40,7 +41,7 @@ use crate::{ auth::{AccessUserInfo, SessionInfo}, db::{ models::{auth_code::AuthCode, oauth2client::OAuth2Client}, - DbPool, OAuth2AuthorizedApp, OAuth2Token, Session, SessionState, User, + Id, OAuth2AuthorizedApp, OAuth2Token, Session, SessionState, User, }, error::WebError, handlers::{mail::send_new_device_ocid_login_email, SIGN_IN_COOKIE_NAME}, @@ -48,8 +49,8 @@ use crate::{ }; /// https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims -impl From<&User> for StandardClaims { - fn from(user: &User) -> StandardClaims { +impl From<&User> for StandardClaims { + fn from(user: &User) -> StandardClaims { let mut name = LocalizedClaim::new(); name.insert(None, EndUserName::new(user.name())); let mut given_name = LocalizedClaim::new(); @@ -96,7 +97,7 @@ pub type DefguardTokenResponse = StandardTokenResponse FromRequestParts for OAuth2Client +impl FromRequestParts for OAuth2Client where S: Send + Sync, AppState: FromRef, @@ -225,7 +226,7 @@ pub struct AuthenticationRequest { impl AuthenticationRequest { fn validate_for_client( &self, - oauth2client: &OAuth2Client, + oauth2client: &OAuth2Client, ) -> Result<(), CoreAuthErrorResponseType> { // check scope: it is valid if any requested scope exists in the `oauth2client` if self @@ -285,6 +286,7 @@ impl AuthenticationRequest { } info!("Validation succeeded for client {}", oauth2client.name); + Ok(()) } } @@ -293,19 +295,20 @@ impl AuthenticationRequest { async fn generate_auth_code_redirect( appstate: AppState, data: AuthenticationRequest, - user_id: Option, + user_id: Id, ) -> Result { let mut url = Url::parse(&data.redirect_uri).map_err(|_| WebError::Http(StatusCode::BAD_REQUEST))?; - let mut auth_code = AuthCode::new( - user_id.unwrap(), + let auth_code = AuthCode::new( + user_id, data.client_id, data.redirect_uri, data.scope, data.nonce, data.code_challenge, - ); - auth_code.save(&appstate.pool).await?; + ) + .save(&appstate.pool) + .await?; { let mut query_pairs = url.query_pairs_mut(); @@ -424,7 +427,7 @@ pub async fn authorization( OAuth2AuthorizedApp::find_by_user_and_oauth2client_id( &appstate.pool, session.user_id, - oauth2client.id.unwrap(), + oauth2client.id, ) .await? { @@ -437,7 +440,7 @@ pub async fn authorization( let location = generate_auth_code_redirect( appstate, data, - Some(session.user_id), + session.user_id, ) .await?; Ok(redirect_to(location, private_cookies)) @@ -445,7 +448,7 @@ pub async fn authorization( // If authorized app not found redirect to consent form info!( "OAuth client id {} not yet authorized by user id {}, redirecting to consent form", - oauth2client.id.unwrap(), session.user_id + oauth2client.id, session.user_id ); Ok(redirect_to( format!( @@ -508,7 +511,7 @@ pub struct GroupClaims { impl AdditionalClaims for GroupClaims {} -pub async fn get_group_claims(pool: &DbPool, user: &User) -> Result { +pub async fn get_group_claims(pool: &PgPool, user: &User) -> Result { let groups = user.member_of_names(pool).await?; Ok(GroupClaims { groups: Some(groups), @@ -533,16 +536,13 @@ pub async fn secure_authorization( Ok(()) => { if OAuth2AuthorizedApp::find_by_user_and_oauth2client_id( &appstate.pool, - session_info.user.id.unwrap(), - oauth2client.id.unwrap(), + session_info.user.id, + oauth2client.id, ) .await? .is_none() { - let mut app = OAuth2AuthorizedApp::new( - session_info.user.id.unwrap(), - oauth2client.id.unwrap(), - ); + let app = OAuth2AuthorizedApp::new(session_info.user.id, oauth2client.id); app.save(&appstate.pool).await?; send_new_device_ocid_login_email( @@ -636,7 +636,7 @@ impl TokenRequest { fn authorization_code_flow( &self, - auth_code: &AuthCode, + auth_code: &AuthCode, token: &OAuth2Token, claims: StandardClaims, base_url: &Url, @@ -738,7 +738,7 @@ impl TokenRequest { token_response } - async fn oauth2client(&self, pool: &DbPool) -> Option { + async fn oauth2client(&self, pool: &PgPool) -> Option> { if let (Some(client_id), Some(client_secret)) = (self.client_id.as_ref(), self.client_secret.as_ref()) { @@ -757,7 +757,7 @@ impl TokenRequest { /// https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens pub async fn token( State(appstate): State, - oauth2client: Option, + oauth2client: Option>, Form(form): Form, ) -> ApiResult { // TODO: cleanup branches @@ -788,8 +788,8 @@ pub async fn token( if let Some(authorized_app) = OAuth2AuthorizedApp::find_by_user_and_oauth2client_id( &appstate.pool, - user.id.unwrap(), - client.id.unwrap(), + user.id, + client.id, ) .await? { @@ -800,14 +800,14 @@ pub async fn token( // Remove existing token in case same client asks for new token if let Some(token) = OAuth2Token::find_by_authorized_app_id( &appstate.pool, - authorized_app.id.unwrap(), + authorized_app.id, ) .await? { token.delete(&appstate.pool).await?; } let token = OAuth2Token::new( - authorized_app.id.unwrap(), + authorized_app.id, auth_code.redirect_uri.clone(), auth_code.scope.clone(), ); diff --git a/src/handlers/settings.rs b/src/handlers/settings.rs index 00282c709..90ccc76bb 100644 --- a/src/handlers/settings.rs +++ b/src/handlers/settings.rs @@ -23,7 +23,7 @@ static DEFAULT_MAIN_LOGO_URL: &str = "/svg/logo-defguard-white.svg"; pub async fn get_settings(State(appstate): State) -> ApiResult { debug!("Retrieving settings"); - if let Some(mut settings) = Settings::find_by_id(&appstate.pool, 1).await? { + if let Some(mut settings) = Settings::get(&appstate.pool).await? { if settings.nav_logo_url.is_empty() { settings.nav_logo_url = DEFAULT_NAV_LOGO_URL.into(); } @@ -46,13 +46,15 @@ pub async fn update_settings( _admin: AdminRole, session: SessionInfo, State(appstate): State, - Json(mut data): Json, + Json(data): Json, ) -> ApiResult { debug!("User {} updating settings", session.user.username); + update_cached_license(data.license.as_deref())?; - data.id = Some(1); data.save(&appstate.pool).await?; + info!("User {} updated settings", session.user.username); + Ok(ApiResponse::default()) } @@ -65,7 +67,9 @@ pub async fn get_settings_essentials(State(appstate): State) -> ApiRes if settings.main_logo_url.is_empty() { settings.main_logo_url = DEFAULT_MAIN_LOGO_URL.into(); } + info!("Retrieved essential settings"); + Ok(ApiResponse { json: json!(settings), status: StatusCode::OK, @@ -75,14 +79,14 @@ pub async fn get_settings_essentials(State(appstate): State) -> ApiRes pub async fn set_default_branding( _admin: AdminRole, State(appstate): State, - Path(id): Path, + Path(_id): Path, // TODO: check with front-end and remove. session: SessionInfo, ) -> ApiResult { debug!( "User {} restoring default branding settings", session.user.username ); - let settings = Settings::find_by_id(&appstate.pool, id).await?; + let settings = Settings::get(&appstate.pool).await?; match settings { Some(mut settings) => { settings.instance_name = "Defguard".into(); diff --git a/src/handlers/ssh_authorized_keys.rs b/src/handlers/ssh_authorized_keys.rs index 17eb4c2a0..9db9cfabe 100644 --- a/src/handlers/ssh_authorized_keys.rs +++ b/src/handlers/ssh_authorized_keys.rs @@ -4,7 +4,7 @@ use axum::{ Json, }; use serde_json::json; -use sqlx::{query, Error as SqlxError, PgExecutor}; +use sqlx::{query, Error as SqlxError, PgExecutor, PgPool}; use ssh_key::PublicKey; use super::{user_for_admin_or_self, ApiResponse, ApiResult}; @@ -13,30 +13,30 @@ use crate::{ auth::SessionInfo, db::{ models::authentication_key::{AuthenticationKey, AuthenticationKeyType}, - DbPool, Group, User, + Group, Id, User, }, error::WebError, }; #[derive(Deserialize, Serialize)] pub(crate) struct AuthenticationKeyInfo { - id: i64, + id: Id, name: Option, key_type: AuthenticationKeyType, key: String, - user_id: i64, + user_id: Id, yubikey_serial: Option, yubikey_id: Option, yubikey_name: Option, } impl AuthenticationKeyInfo { - pub async fn find_by_user_id<'e, E>(executor: E, user_id: i64) -> Result, SqlxError> + pub async fn find_by_user_id<'e, E>(executor: E, user_id: Id) -> Result, SqlxError> where E: PgExecutor<'e>, { let q_res = query!( - "SELECT k.id as key_id, k.name, k.key_type \"key_type: AuthenticationKeyType\", \ + "SELECT k.id key_id, k.name, k.key_type \"key_type: AuthenticationKeyType\", \ k.key, k.user_id, k.yubikey_id, \ y.name \"yubikey_name: Option\", y.serial \"serial: Option\" \ FROM \"authentication_key\" k \ @@ -59,23 +59,21 @@ impl AuthenticationKeyInfo { yubikey_serial: q.serial.clone(), }) .collect(); + Ok(res) } } -async fn add_user_ssh_keys_to_list(pool: &DbPool, user: &User, ssh_keys: &mut Vec) { - if let Some(user_id) = user.id { - let keys_result = - AuthenticationKey::find_by_user_id(pool, user_id, Some(AuthenticationKeyType::Ssh)) - .await; +async fn add_user_ssh_keys_to_list(pool: &PgPool, user: &User, ssh_keys: &mut Vec) { + let keys_result = + AuthenticationKey::find_by_user_id(pool, user.id, Some(AuthenticationKeyType::Ssh)).await; - if let Ok(authentication_keys) = keys_result { - let mut keys: Vec = authentication_keys - .into_iter() - .map(|item| item.key) - .collect(); - ssh_keys.append(&mut keys); - } + if let Ok(authentication_keys) = keys_result { + let mut keys: Vec = authentication_keys + .into_iter() + .map(|item| item.key) + .collect(); + ssh_keys.append(&mut keys); } } @@ -173,10 +171,6 @@ pub async fn add_authentication_key( // authorize request let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - let Some(user_id) = user.id else { - error!("Model returned user ({}) without ID", user.username); - return Err(WebError::ModelError("Model returned without ID".into())); - }; let trimmed_key = data.key.trim_end_matches(['\n', '\r']); @@ -196,7 +190,7 @@ pub async fn add_authentication_key( // check if exists let exists_res = query!( "SELECT COUNT(1) FROM \"authentication_key\" WHERE user_id = $1 AND key = $2", - user_id, + user.id, trimmed_key, ) .fetch_one(&appstate.pool) @@ -205,19 +199,22 @@ pub async fn add_authentication_key( error!("User {username} tried to insert existing key: {data:?}"); return Err(WebError::BadRequest("Key already exists.".into())); } - let mut new_key = AuthenticationKey::new( - user_id, + + AuthenticationKey::new( + user.id, trimmed_key.to_string(), Some(data.name.clone()), data.key_type.clone(), None, - ); - new_key.save(&appstate.pool).await?; + ) + .save(&appstate.pool) + .await?; info!( "Added new key \"{}\" of type {:?} for user {username}", data.name, data.key_type ); + Ok(ApiResponse { json: json!({}), status: StatusCode::CREATED, @@ -231,14 +228,7 @@ pub async fn fetch_authentication_keys( session: SessionInfo, ) -> ApiResult { let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - let Some(user_id) = user.id else { - error!("Model returned user ({}) without ID", user.username); - return Err(WebError::ModelError( - "Model returned user without ID".into(), - )); - }; - - let keys_info = AuthenticationKeyInfo::find_by_user_id(&appstate.pool, user_id).await?; + let keys_info = AuthenticationKeyInfo::find_by_user_id(&appstate.pool, user.id).await?; Ok(ApiResponse { json: json!(keys_info), @@ -252,11 +242,8 @@ pub async fn delete_authentication_key( Path((username, key_id)): Path<(String, i64)>, ) -> ApiResult { let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - let user_id = user - .id - .ok_or(WebError::DbError("Returned user had no ID".into()))?; if let Some(key) = AuthenticationKey::find_by_id(&appstate.pool, key_id).await? { - if !session.is_admin && user_id != key.user_id { + if !session.is_admin && user.id != key.user_id { return Err(WebError::Forbidden(String::new())); } key.delete(&appstate.pool).await?; @@ -264,6 +251,7 @@ pub async fn delete_authentication_key( error!("Key with id {} not found", key_id); return Err(WebError::BadRequest("Key not found".into())); } + Ok(ApiResponse { json: json!({}), status: StatusCode::OK, @@ -282,9 +270,6 @@ pub async fn rename_authentication_key( Json(data): Json, ) -> ApiResult { let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - let user_id = user - .id - .ok_or(WebError::DbError("Returned user had no ID".into()))?; if let Some(mut key) = AuthenticationKey::find_by_id(&appstate.pool, key_id).await? { if key.yubikey_id.is_some() { warn!( @@ -293,7 +278,7 @@ pub async fn rename_authentication_key( ); return Err(WebError::BadRequest("Rename yubikey instead.".into())); } - if !session.is_admin && user_id != key.user_id { + if !session.is_admin && user.id != key.user_id { warn!( "User {} tried to rename key ({}) of another user with id {}", username, key_id, key.user_id diff --git a/src/handlers/user.rs b/src/handlers/user.rs index 2a0ba2ece..51e88154f 100644 --- a/src/handlers/user.rs +++ b/src/handlers/user.rs @@ -326,15 +326,16 @@ pub async fn add_user( }; // create new user - let mut user = User::new( + let user = User::new( user_data.username, password, user_data.last_name, user_data.first_name, user_data.email, user_data.phone, - ); - user.save(&appstate.pool).await?; + ) + .save(&appstate.pool) + .await?; if let Some(password) = user_data.password { let _result = ldap_add_user(&appstate.pool, &user, &password).await; @@ -922,16 +923,12 @@ pub async fn reset_password( if let Some(user) = user { let mut transaction = appstate.pool.begin().await?; - Token::delete_unused_user_password_reset_tokens( - &mut transaction, - user.id.expect("Missing user ID"), - ) - .await?; + Token::delete_unused_user_password_reset_tokens(&mut transaction, user.id).await?; let config = server_config(); let enrollment = Token::new( - user.id.expect("Missing user ID"), - Some(session.user.id.expect("Missing admin ID")), + user.id, + Some(session.user.id), Some(user.email.clone()), config.password_reset_token_timeout.as_secs(), Some(PASSWORD_RESET_TOKEN_TYPE.to_string()), @@ -1030,8 +1027,7 @@ pub async fn wallet_challenge( // check if address already exists let wallet = if let Some(wallet) = - Wallet::find_by_user_and_address(&appstate.pool, user.id.unwrap(), &wallet_info.address) - .await? + Wallet::find_by_user_and_address(&appstate.pool, user.id, &wallet_info.address).await? { if wallet.validation_timestamp.is_some() { error!( @@ -1042,31 +1038,31 @@ pub async fn wallet_challenge( } wallet } else { - let challenge_message = - if let Some(settings) = Settings::find_by_id(&appstate.pool, 1).await? { - Wallet::format_challenge(&wallet_info.address, &settings.challenge_template) - } else { - error!("Cannot retrieve settings"); - return Err(WebError::DbError("cannot retrieve settings".into())); - }; - let mut wallet = Wallet::new_for_user( - user.id.unwrap(), + let challenge_message = if let Some(settings) = Settings::get(&appstate.pool).await? { + Wallet::format_challenge(&wallet_info.address, &settings.challenge_template) + } else { + error!("Cannot retrieve settings"); + return Err(WebError::DbError("cannot retrieve settings".into())); + }; + Wallet::new_for_user( + user.id, wallet_info.address, wallet_info.name, wallet_info.chain_id, challenge_message, - ); - wallet.save(&appstate.pool).await?; - wallet + ) + .save(&appstate.pool) + .await? }; info!( "User {} generated wallet challenge for user {username}", session.user.username ); + Ok(ApiResponse { json: json!(WalletChallenge { - id: wallet.id.unwrap(), + id: wallet.id, message: wallet.challenge_message }), status: StatusCode::OK, @@ -1105,8 +1101,7 @@ pub async fn set_wallet( ); let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; if let Some(mut wallet) = - Wallet::find_by_user_and_address(&appstate.pool, user.id.unwrap(), &wallet_info.address) - .await? + Wallet::find_by_user_and_address(&appstate.pool, user.id, &wallet_info.address).await? { if wallet.validate_signature(&wallet_info.signature).is_ok() { wallet @@ -1168,9 +1163,9 @@ pub async fn update_wallet( ); let mut user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; if let Some(mut wallet) = - Wallet::find_by_user_and_address(&appstate.pool, user.id.unwrap(), &address).await? + Wallet::find_by_user_and_address(&appstate.pool, user.id, &address).await? { - if Some(wallet.user_id) == user.id { + if wallet.user_id == user.id { let mfa_change = wallet.use_for_mfa != data.use_for_mfa; wallet.use_for_mfa = data.use_for_mfa; wallet.save(&appstate.pool).await?; @@ -1209,7 +1204,7 @@ pub async fn update_wallet( Ok(ApiResponse::default()) } else { error!( - "User {} failed to update wallet {address} for user {username} (id: {:?}), the owner id is {}", + "User {} failed to update wallet {address} for user {username} (id: {}), the owner id is {}", session.user.username, user.id, wallet.user_id ); Err(WebError::ObjectNotFound("wrong wallet".into())) @@ -1255,9 +1250,9 @@ pub async fn delete_wallet( ); let mut user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; if let Some(wallet) = - Wallet::find_by_user_and_address(&appstate.pool, user.id.unwrap(), &address).await? + Wallet::find_by_user_and_address(&appstate.pool, user.id, &address).await? { - if Some(wallet.user_id) == user.id { + if wallet.user_id == user.id { wallet.delete(&appstate.pool).await?; user.verify_mfa_state(&appstate.pool).await?; info!( @@ -1313,7 +1308,7 @@ pub async fn delete_security_key( ); let mut user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; if let Some(webauthn) = WebAuthn::find_by_id(&appstate.pool, id).await? { - if Some(webauthn.user_id) == user.id { + if webauthn.user_id == user.id { webauthn.delete(&appstate.pool).await?; user.verify_mfa_state(&appstate.pool).await?; info!( @@ -1412,12 +1407,12 @@ pub async fn delete_authorized_app( let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; if let Some(app) = OAuth2AuthorizedApp::find_by_user_and_oauth2client_id( &appstate.pool, - user.id.unwrap(), + user.id, oauth2client_id, ) .await? { - if Some(app.user_id) == user.id { + if app.user_id == user.id { app.delete(&appstate.pool).await?; info!( "User {} deleted OAuth2 client {oauth2client_id} for user {username}", diff --git a/src/handlers/webhooks.rs b/src/handlers/webhooks.rs index eb321307f..cc6513e36 100644 --- a/src/handlers/webhooks.rs +++ b/src/handlers/webhooks.rs @@ -4,7 +4,7 @@ use axum::{ }; use serde_json::json; -use super::{ApiResponse, ApiResult}; +use super::{ApiResponse, ApiResult, WebHookData}; use crate::{ appstate::AppState, auth::{AdminRole, SessionInfo}, @@ -15,15 +15,17 @@ pub async fn add_webhook( _admin: AdminRole, session: SessionInfo, State(appstate): State, - Json(mut webhook): Json, + Json(webhookdata): Json, ) -> ApiResult { - let url = webhook.url.clone(); + let url = webhookdata.url.clone(); debug!("User {} adding webhook {url}", session.user.username); + let webhook: WebHook = webhookdata.into(); let status = match webhook.save(&appstate.pool).await { - Ok(()) => StatusCode::CREATED, + Ok(_) => StatusCode::CREATED, Err(_) => StatusCode::BAD_REQUEST, }; info!("User {} added webhook {url}", session.user.username); + Ok(ApiResponse { json: json!({}), status, @@ -33,6 +35,7 @@ pub async fn add_webhook( // TODO: paginate pub async fn list_webhooks(_admin: AdminRole, State(appstate): State) -> ApiResult { let webhooks = WebHook::all(&appstate.pool).await?; + Ok(ApiResponse { json: json!(webhooks), status: StatusCode::OK, @@ -56,18 +59,6 @@ pub async fn get_webhook( } } -#[derive(Deserialize, Serialize)] -pub struct WebHookData { - pub url: String, - pub description: String, - pub token: String, - pub enabled: bool, - pub on_user_created: bool, - pub on_user_deleted: bool, - pub on_user_modified: bool, - pub on_hwkey_provision: bool, -} - pub async fn change_webhook( _admin: AdminRole, session: SessionInfo, @@ -92,6 +83,7 @@ pub async fn change_webhook( None => StatusCode::NOT_FOUND, }; info!("User {} updated webhook {id}", session.user.username); + Ok(ApiResponse { json: json!({}), status, diff --git a/src/handlers/wireguard.rs b/src/handlers/wireguard.rs index 34a72f831..149433e2d 100644 --- a/src/handlers/wireguard.rs +++ b/src/handlers/wireguard.rs @@ -12,6 +12,7 @@ use axum::{ use chrono::{DateTime, Duration, NaiveDateTime, Utc}; use ipnetwork::IpNetwork; use serde_json::{json, Value}; +use sqlx::PgPool; use utoipa::ToSchema; use uuid::Uuid; @@ -26,7 +27,7 @@ use crate::{ }, wireguard::{DateTimeAggregation, MappedDevice, WireguardNetworkInfo}, }, - AddDevice, DbPool, Device, GatewayEvent, WireguardNetwork, + AddDevice, Device, GatewayEvent, Id, WireguardNetwork, }, enterprise::handlers::CanManageDevices, grpc::GatewayMap, @@ -76,7 +77,7 @@ pub struct ImportNetworkData { #[derive(Serialize, Deserialize)] pub struct ImportedNetworkData { - pub network: WireguardNetwork, + pub network: WireguardNetwork, pub devices: Vec, } @@ -103,7 +104,7 @@ pub async fn create_network( session.user.username ); let allowed_ips = data.parse_allowed_ips(); - let mut network = WireguardNetwork::new( + let network = WireguardNetwork::new( data.name, data.address, data.port, @@ -117,7 +118,7 @@ pub async fn create_network( .map_err(|_| WebError::Serialization("Invalid network address".into()))?; let mut transaction = appstate.pool.begin().await?; - network.save(&mut *transaction).await?; + let network = network.save(&mut *transaction).await?; network .set_allowed_groups(&mut transaction, data.allowed_groups) .await?; @@ -126,19 +127,7 @@ pub async fn create_network( network.add_all_allowed_devices(&mut transaction).await?; info!("Assigning IPs for existing devices in network {network}"); - match &network.id { - Some(network_id) => { - appstate - .send_wireguard_event(GatewayEvent::NetworkCreated(*network_id, network.clone())); - } - None => { - error!("Network {} ID was not created during network creation, gateway event was not sent!", network.name); - return Ok(ApiResponse { - json: json!({}), - status: StatusCode::INTERNAL_SERVER_ERROR, - }); - } - } + appstate.send_wireguard_event(GatewayEvent::NetworkCreated(network.id, network.clone())); transaction.commit().await?; @@ -146,13 +135,14 @@ pub async fn create_network( "User {} created WireGuard network {network_name}", session.user.username ); + Ok(ApiResponse { json: json!(network), status: StatusCode::CREATED, }) } -async fn find_network(id: i64, pool: &DbPool) -> Result { +async fn find_network(id: Id, pool: &PgPool) -> Result, WebError> { WireguardNetwork::find_by_id(pool, id) .await? .ok_or_else(|| WebError::ObjectNotFound(format!("Network {id} not found"))) @@ -190,22 +180,12 @@ pub async fn modify_network( .await?; let _events = network.sync_allowed_devices(&mut transaction, None).await?; - match &network.id { - Some(network_id) => { - let peers = network.get_peers(&mut *transaction).await?; - appstate.send_wireguard_event(GatewayEvent::NetworkModified( - *network_id, - network.clone(), - peers, - )); - } - &None => { - error!( - "Network {} id not found, gateway update not sent!", - network.name - ); - } - } + let peers = network.get_peers(&mut *transaction).await?; + appstate.send_wireguard_event(GatewayEvent::NetworkModified( + network.id, + network.clone(), + peers, + )); // commit DB transaction transaction.commit().await?; @@ -238,6 +218,7 @@ pub async fn delete_network( "User {} deleted WireGuard network {network_id}", session.user.username, ); + Ok(ApiResponse::default()) } @@ -251,7 +232,7 @@ pub async fn list_networks( let networks = WireguardNetwork::all(&appstate.pool).await?; for network in networks { - let network_id = network.id.expect("Network does not have an ID"); + let network_id = network.id; let allowed_groups = network.fetch_allowed_groups(&appstate.pool).await?; { let gateway_state = gateway_state @@ -364,21 +345,13 @@ pub async fn import_network( network.endpoint = data.endpoint; let mut transaction = appstate.pool.begin().await?; - network.save(&mut *transaction).await?; + let network = network.save(&mut *transaction).await?; network .set_allowed_groups(&mut transaction, data.allowed_groups) .await?; info!("New network {network} created"); - match network.id { - Some(network_id) => { - appstate - .send_wireguard_event(GatewayEvent::NetworkCreated(network_id, network.clone())); - } - None => { - error!("Network {network} id not found, gateway event not sent!"); - } - } + appstate.send_wireguard_event(GatewayEvent::NetworkCreated(network.id, network.clone())); let reserved_ips: Vec = imported_devices .iter() @@ -463,7 +436,7 @@ pub async fn add_user_devices( #[derive(Serialize, ToSchema)] pub struct AddDeviceResult { configs: Vec, - device: Device, + device: Device, } /// Add device @@ -566,17 +539,10 @@ pub async fn add_device( } // save device - let Some(user_id) = user.id else { - error!( - "Failed to add device {device_name}, user {} has no id", - user.username - ); - return Err(WebError::ModelError("User has no id".to_string())); - }; - let mut device = Device::new(add_device.name, add_device.wireguard_pubkey, user_id); - let mut transaction = appstate.pool.begin().await?; - device.save(&mut *transaction).await?; + let device = Device::new(add_device.name, add_device.wireguard_pubkey, user.id) + .save(&mut *transaction) + .await?; let (network_info, configs) = device.add_to_all_networks(&mut transaction).await?; @@ -701,20 +667,16 @@ pub async fn modify_device( // send update to gateway's let mut network_info = Vec::new(); for network in &networks { - if let Some(network_id) = network.id { - if let Some(device_id) = device.id { - let wireguard_network_device = - WireguardNetworkDevice::find(&appstate.pool, device_id, network_id).await?; - if let Some(wireguard_network_device) = wireguard_network_device { - let device_network_info = DeviceNetworkInfo { - network_id, - device_wireguard_ip: wireguard_network_device.wireguard_ip, - preshared_key: wireguard_network_device.preshared_key, - is_authorized: wireguard_network_device.is_authorized, - }; - network_info.push(device_network_info); - } - } + let wireguard_network_device = + WireguardNetworkDevice::find(&appstate.pool, device.id, network.id).await?; + if let Some(wireguard_network_device) = wireguard_network_device { + let device_network_info = DeviceNetworkInfo { + network_id: network.id, + device_wireguard_ip: wireguard_network_device.wireguard_ip, + preshared_key: wireguard_network_device.preshared_key, + is_authorized: wireguard_network_device.is_authorized, + }; + network_info.push(device_network_info); } } appstate.send_wireguard_event(GatewayEvent::DeviceModified(DeviceInfo { @@ -898,18 +860,13 @@ pub async fn download_config( info!("Created config for device {}({device_id})", device.name); Ok(device.create_config(&network, &wireguard_network_device)) } else { - let device_id = if let Some(id) = device.id { - id.to_string() - } else { - String::new() - }; error!( - "Failed to create config, no IP address found for device: {}({device_id})", - device.name + "Failed to create config, no IP address found for device: {}({})", + device.name, device.id ); Err(WebError::ObjectNotFound(format!( - "No IP address found for device: {}({device_id})", - device.name + "No IP address found for device: {}({})", + device.name, device.id ))) } } diff --git a/src/handlers/yubikey.rs b/src/handlers/yubikey.rs index aab077d88..c8660f740 100644 --- a/src/handlers/yubikey.rs +++ b/src/handlers/yubikey.rs @@ -15,22 +15,19 @@ pub async fn delete_yubikey( ) -> ApiResult { debug!("Deleting yubikey {key_id} by {:?}", &session.user.id); let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - let user_id = user - .id - .ok_or(WebError::DbError("Returned user had no ID".into()))?; let Some(yubikey) = YubiKey::find_by_id(&appstate.pool, key_id).await? else { error!("Yubikey with id {key_id} not found"); return Err(WebError::ObjectNotFound("YubiKey not found".into())); }; - if !session.is_admin && yubikey.user_id != user_id { + if !session.is_admin && yubikey.user_id != user.id { warn!( - "User {user_id} tried to delete yubikey {key_id} of user {} without being an admin.", - yubikey.user_id + "User {} tried to delete yubikey {key_id} of user {} without being an admin.", + user.id, yubikey.user_id ); return Err(WebError::Forbidden("Not allowed to delete YubiKey".into())); } yubikey.delete(&appstate.pool).await?; - info!("Yubikey {key_id} deleted by user {user_id}"); + info!("Yubikey {key_id} deleted by user {}", user.id); Ok(ApiResponse { json: json!({}), status: StatusCode::OK, @@ -49,24 +46,21 @@ pub async fn rename_yubikey( Json(data): Json, ) -> ApiResult { let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - let user_id = user - .id - .ok_or(WebError::DbError("Returned user had no ID".into()))?; - debug!("User {} attempts to rename yubikey {}", user_id, key_id); + debug!("User {} attempts to rename yubikey {}", user.id, key_id); let Some(mut yubikey) = YubiKey::find_by_id(&appstate.pool, key_id).await? else { error!("Yubikey with id {key_id} not found"); return Err(WebError::ObjectNotFound("YubiKey not found".into())); }; - if !session.is_admin && yubikey.user_id != user_id { + if !session.is_admin && yubikey.user_id != user.id { warn!( - "User {user_id}, tried to rename yubikey {key_id} of user {} without being an admin.", - yubikey.user_id + "User {}, tried to rename yubikey {key_id} of user {} without being an admin.", + user.id, yubikey.user_id ); return Err(WebError::Forbidden(String::new())); } yubikey.name = data.name; yubikey.save(&appstate.pool).await?; - info!("Yubikey {:?} renamed by user {user_id}", yubikey.id); + info!("Yubikey {} renamed by user {}", yubikey.id, user.id); Ok(ApiResponse { json: json!(yubikey), status: StatusCode::OK, diff --git a/src/headers.rs b/src/headers.rs index f24256113..0ceba071a 100644 --- a/src/headers.rs +++ b/src/headers.rs @@ -1,10 +1,11 @@ use std::{borrow::Borrow, sync::Arc}; +use sqlx::PgPool; use tokio::sync::mpsc::UnboundedSender; use uaparser::{Client, Parser, UserAgentParser}; use crate::{ - db::{models::device_login::DeviceLoginEvent, DbPool, Session, User}, + db::{models::device_login::DeviceLoginEvent, Id, Session, User}, handlers::mail::send_new_device_login_email, mail::Mail, templates::TemplateError, @@ -70,7 +71,7 @@ pub(crate) fn get_user_agent_device(user_agent_client: &Client) -> String { #[must_use] pub(crate) fn get_device_login_event( - user_id: i64, + user_id: Id, ip_address: String, event_type: String, user_agent_client: Option, @@ -80,7 +81,7 @@ pub(crate) fn get_device_login_event( } pub(crate) fn get_user_agent_device_login_data( - user_id: i64, + user_id: Id, ip_address: String, event_type: String, user_agent_client: &Client, @@ -107,30 +108,28 @@ pub(crate) fn get_user_agent_device_login_data( } pub(crate) async fn check_new_device_login( - pool: &DbPool, + pool: &PgPool, mail_tx: &UnboundedSender, session: &Session, - user: &User, + user: &User, ip_address: String, event_type: String, agent: Option>, ) -> Result<(), TemplateError> { - if let Some(user_id) = user.id { - if let Some(device_login_event) = - get_device_login_event(user_id, ip_address, event_type, agent) + eprintln!("ARSE"); + if let Some(device_login_event) = get_device_login_event(user.id, ip_address, event_type, agent) + { + if let Ok(Some(created_device_login_event)) = device_login_event + .check_if_device_already_logged_in(pool) + .await { - if let Ok(Some(created_device_login_event)) = device_login_event - .check_if_device_already_logged_in(pool) - .await - { - send_new_device_login_email( - &user.email, - mail_tx, - session, - created_device_login_event.created, - ) - .await?; - } + send_new_device_login_email( + &user.email, + mail_tx, + session, + created_device_login_event.created, + ) + .await?; } } diff --git a/src/ldap/error.rs b/src/ldap/error.rs index ac790cd33..6d6e9d806 100644 --- a/src/ldap/error.rs +++ b/src/ldap/error.rs @@ -5,16 +5,19 @@ pub enum LdapError { Ldap(String), ObjectNotFound(String), MissingSettings, + // TODO: include the error + Database, } impl fmt::Display for LdapError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - LdapError::Ldap(msg) => write!(f, "LDAP error: {msg}"), - LdapError::ObjectNotFound(msg) => write!(f, "Object not found: {msg}"), - LdapError::MissingSettings => { - write!(f, "LDAP settings are missing.") + Self::Ldap(msg) => write!(f, "LDAP error: {msg}"), + Self::ObjectNotFound(msg) => write!(f, "Object not found: {msg}"), + Self::MissingSettings => { + write!(f, "LDAP settings are missing") } + Self::Database => write!(f, "Database error"), } } } diff --git a/src/ldap/mod.rs b/src/ldap/mod.rs index 770a0a1a3..a68111516 100644 --- a/src/ldap/mod.rs +++ b/src/ldap/mod.rs @@ -4,7 +4,7 @@ use ldap3::{drive, Ldap, LdapConnAsync, Mod, Scope, SearchEntry}; use sqlx::PgExecutor; use self::error::LdapError; -use crate::db::{self, Settings, User}; +use crate::db::{self, Id, Settings, User}; pub mod error; pub mod hash; @@ -117,6 +117,7 @@ impl LDAPConnection { ldap.simple_bind(&config.ldap_bind_username, password.expose_secret()) .await? .success()?; + Ok(Self { config, ldap }) } @@ -133,6 +134,7 @@ impl LDAPConnection { .await? .success()?; info!("Performed LDAP user search with filter = {filter}"); + Ok(rs.into_iter().map(SearchEntry::construct).collect()) } @@ -160,6 +162,7 @@ impl LDAPConnection { debug!("Adding object {dn}"); self.ldap.add(dn, attrs).await?.success()?; info!("Added object {dn}"); + Ok(()) } @@ -178,6 +181,7 @@ impl LDAPConnection { } } info!("Modified LDAP object {old_dn}"); + Ok(()) } @@ -186,6 +190,7 @@ impl LDAPConnection { debug!("Deleting LDAP object {dn}"); self.ldap.delete(dn).await?; info!("Deleted LDAP object {dn}"); + Ok(()) } @@ -224,7 +229,7 @@ impl LDAPConnection { } /// Adds user to LDAP. - pub async fn add_user(&mut self, user: &User, password: &str) -> Result<(), LdapError> { + pub async fn add_user(&mut self, user: &User, password: &str) -> Result<(), LdapError> { debug!("Adding LDAP user {}", user.username); let dn = self.config.user_dn(&user.username); let ssha_password = hash::salted_sha1_hash(password); @@ -232,17 +237,19 @@ impl LDAPConnection { self.add(&dn, user.as_ldap_attrs(&ssha_password, &ht_password)) .await?; info!("Added LDAP user {}", user.username); + Ok(()) } /// Modifies LDAP user. - pub async fn modify_user(&mut self, username: &str, user: &User) -> Result<(), LdapError> { + pub async fn modify_user(&mut self, username: &str, user: &User) -> Result<(), LdapError> { debug!("Modifying user {username}"); let old_dn = self.config.user_dn(username); let new_dn = self.config.user_dn(&user.username); self.modify(&old_dn, &new_dn, user.as_ldap_mod(&self.config)) .await?; info!("Modified user {username}"); + Ok(()) } @@ -252,6 +259,7 @@ impl LDAPConnection { let dn = self.config.user_dn(username); self.delete(&dn).await?; info!("Deleted user {username}"); + Ok(()) } @@ -271,6 +279,7 @@ impl LDAPConnection { ) .await?; info!("Password set for user {username}"); + Ok(()) } @@ -309,6 +318,7 @@ impl LDAPConnection { ) .await?; info!("Modified LDAP group {groupname}"); + Ok(()) } @@ -346,6 +356,7 @@ impl LDAPConnection { )], ) .await?; + Ok(()) } @@ -366,6 +377,7 @@ impl LDAPConnection { )], ) .await?; + Ok(()) } } diff --git a/src/ldap/model.rs b/src/ldap/model.rs index e16d0ca90..a817feaf0 100644 --- a/src/ldap/model.rs +++ b/src/ldap/model.rs @@ -17,7 +17,9 @@ impl User { get_value(entry, "mobile"), ) } +} +impl User { #[must_use] pub fn as_ldap_mod(&self, config: &LDAPConfig) -> Vec> { let mut changes = vec![ diff --git a/src/ldap/utils.rs b/src/ldap/utils.rs index 1fa8e07e5..1e80af19d 100644 --- a/src/ldap/utils.rs +++ b/src/ldap/utils.rs @@ -1,20 +1,28 @@ -use sqlx::PgExecutor; +use sqlx::{PgExecutor, PgPool}; use super::{error::LdapError, LDAPConnection}; -use crate::db::{DbPool, Group, User}; +use crate::db::{Group, Id, User}; pub async fn user_from_ldap( - pool: &DbPool, + pool: &PgPool, username: &str, password: &str, -) -> Result { +) -> Result, LdapError> { let mut ldap_connection = LDAPConnection::create(pool).await?; - let mut user = ldap_connection.get_user(username, password).await?; - let _result = user.save(pool).await; // FIXME: do not ignore errors - Ok(user) + // FIXME: do not ignore errors + ldap_connection + .get_user(username, password) + .await? + .save(pool) + .await + .map_err(|_| LdapError::Database) } -pub async fn ldap_add_user<'e, E>(executor: E, user: &User, password: &str) -> Result<(), LdapError> +pub async fn ldap_add_user<'e, E>( + executor: E, + user: &User, + password: &str, +) -> Result<(), LdapError> where E: PgExecutor<'e>, { @@ -29,7 +37,7 @@ where pub async fn ldap_modify_user<'e, E>( executor: E, username: &str, - user: &User, + user: &User, ) -> Result<(), LdapError> where E: PgExecutor<'e>, diff --git a/src/lib.rs b/src/lib.rs index 1e0f32a2e..f3f2f3deb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ use handlers::{ }; use ipnetwork::IpNetwork; use secrecy::ExposeSecret; +use sqlx::PgPool; use tokio::{ net::TcpListener, sync::{ @@ -72,7 +73,7 @@ use self::{ db::{ init_db, models::wireguard::{DEFAULT_DISCONNECT_THRESHOLD, DEFAULT_KEEPALIVE_INTERVAL}, - AppEvent, DbPool, Device, GatewayEvent, User, WireguardNetwork, + AppEvent, Device, GatewayEvent, User, WireguardNetwork, }, handlers::{ auth::{ @@ -281,7 +282,7 @@ pub fn build_webapp( mail_tx: UnboundedSender, worker_state: Arc>, gateway_state: Arc>, - pool: DbPool, + pool: PgPool, user_agent_parser: Arc, failed_logins: Arc>, ) -> Router { @@ -520,7 +521,7 @@ pub async fn run_web_server( webhook_rx: UnboundedReceiver, wireguard_tx: Sender, mail_tx: UnboundedSender, - pool: DbPool, + pool: PgPool, user_agent_parser: Arc, failed_logins: Arc>, ) -> Result<(), anyhow::Error> { @@ -600,8 +601,7 @@ pub async fn init_dev_env(config: &DefGuardConfig) { network .save(&mut *transaction) .await - .expect("Could not save network"); - network + .expect("Could not save network") }; if Device::find_by_pubkey( @@ -615,15 +615,14 @@ pub async fn init_dev_env(config: &DefGuardConfig) { info!("Test device exists already, skipping creation..."); } else { info!("Creating test device"); - let mut device = Device::new( + let device = Device::new( "TestDevice".to_string(), "gQYL5eMeFDj0R+lpC7oZyIl0/sNVmQDC6ckP7husZjc=".to_string(), 1, - ); - device - .save(&mut *transaction) - .await - .expect("Could not save device"); + ) + .save(&mut *transaction) + .await + .expect("Could not save device"); device .assign_network_ip(&mut transaction, &network, None) .await @@ -632,14 +631,14 @@ pub async fn init_dev_env(config: &DefGuardConfig) { #[cfg(feature = "openid")] for app_id in 1..=3 { - let mut app = OAuth2Client::new( + OAuth2Client::new( vec![format!("https://app-{app_id}.com")], vec!["openid".into(), "profile".into(), "email".into()], format!("app-{app_id}"), - ); - app.save(&mut *transaction) - .await - .expect("Could not save oauth2client"); + ) + .save(&mut *transaction) + .await + .expect("Could not save oauth2client"); } transaction .commit() @@ -653,7 +652,7 @@ pub async fn init_dev_env(config: &DefGuardConfig) { /// Meant to be used to automate setting up a new defguard instance. /// Does not handle assigning device IPs, since no device should exist at this point. pub async fn init_vpn_location( - pool: &DbPool, + pool: &PgPool, args: &InitVpnLocationArgs, ) -> Result { // check if a VPN location exists already @@ -665,7 +664,7 @@ pub async fn init_vpn_location( }; // create a new network - let mut network = WireguardNetwork::new( + let network = WireguardNetwork::new( args.name.clone(), args.address, args.port, @@ -675,15 +674,15 @@ pub async fn init_vpn_location( false, DEFAULT_KEEPALIVE_INTERVAL, DEFAULT_DISCONNECT_THRESHOLD, - )?; - network.save(pool).await?; - let network_id = network.get_id()?; + )? + .save(pool) + .await?; // generate gateway token let token = Claims::new( ClaimsType::Gateway, - format!("DEFGUARD-NETWORK-{network_id}"), - network_id.to_string(), + format!("DEFGUARD-NETWORK-{}", network.id), + network.id.to_string(), u32::MAX.into(), ) .to_jwt()?; diff --git a/src/mail.rs b/src/mail.rs index ebfc21aff..6b93b326f 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -6,15 +6,15 @@ use lettre::{ transport::smtp::{authentication::Credentials, response::Response}, Address, AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor, }; -use sqlx::{Pool, Postgres}; +use sqlx::PgPool; use thiserror::Error; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use crate::db::{models::settings::SmtpEncryption, Settings}; -static SMTP_TIMEOUT_SECONDS: u64 = 15; +const SMTP_TIMEOUT_SECONDS: u64 = 15; -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum MailError { #[error(transparent)] LettreError(#[from] lettre::error::Error), @@ -49,12 +49,12 @@ struct SmtpSettings { } impl SmtpSettings { - /// Retrieves Settings object from database and builds SmtpSettings - pub async fn get(db: &Pool) -> Result { + /// Retrieves `Settings` from database and builds `SmtpSettings`. + pub async fn get(db: &PgPool) -> Result { Self::from_settings(Self::get_settings(db).await?) } - /// Constructs SmtpSettings object from Settings. Returns error if SMTP settings are incomplete. + /// Constructs `SmtpSettings` from `Settings`. Returns error if `SmtpSettings` are incomplete. pub fn from_settings(settings: Settings) -> Result { if let (Some(server), Some(port), encryption, Some(user), Some(password), Some(sender)) = ( settings.smtp_server, @@ -79,10 +79,8 @@ impl SmtpSettings { } /// Retrieves Settings object from database - async fn get_settings(db: &Pool) -> Result { - Settings::find_by_id(db, 1) - .await? - .ok_or(MailError::EmptySettings) + async fn get_settings(db: &PgPool) -> Result { + Settings::get(db).await?.ok_or(MailError::EmptySettings) } } @@ -143,11 +141,11 @@ impl Mail { struct MailHandler { rx: UnboundedReceiver, - db: Pool, + db: PgPool, } impl MailHandler { - pub fn new(rx: UnboundedReceiver, db: Pool) -> Self { + pub fn new(rx: UnboundedReceiver, db: PgPool) -> Self { Self { rx, db } } @@ -236,6 +234,6 @@ impl MailHandler { } /// Builds MailHandler and runs it. -pub async fn run_mail_handler(rx: UnboundedReceiver, db: Pool) { +pub async fn run_mail_handler(rx: UnboundedReceiver, db: PgPool) { MailHandler::new(rx, db).run().await; } diff --git a/src/secret.rs b/src/secret.rs index d0434a8f9..9eed4570f 100644 --- a/src/secret.rs +++ b/src/secret.rs @@ -2,11 +2,7 @@ use std::{convert::Infallible, error::Error, str::FromStr}; use secrecy::{ExposeSecret, Secret}; use serde::{Deserialize, Serialize}; -use sqlx::{ - database::{HasArguments, HasValueRef}, - encode::IsNull, - Database, Decode, Encode, Type, -}; +use sqlx::{encode::IsNull, Database, Decode, Encode, Type}; /// Wrapper for secrecy Secret struct which implements sqlx Postgres #[derive(Clone, Deserialize, Debug)] @@ -40,9 +36,7 @@ impl<'q, DB: Database> Decode<'q, DB> for SecretString where String: Decode<'q, DB>, { - fn decode( - value: >::ValueRef, - ) -> Result> { + fn decode(value: ::ValueRef<'q>) -> Result> { >::decode(value).map(|v| Self(Secret::from(v))) } } @@ -51,7 +45,10 @@ impl<'q, DB: Database> Encode<'q, DB> for SecretString where String: Encode<'q, DB>, { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref( + &self, + buf: &mut ::ArgumentBuffer<'q>, + ) -> Result> { >::encode_by_ref(self.0.expose_secret(), buf) } diff --git a/src/support.rs b/src/support.rs index b43007577..d20cdc283 100644 --- a/src/support.rs +++ b/src/support.rs @@ -2,9 +2,10 @@ use std::{collections::HashMap, fmt::Display}; use serde::Serialize; use serde_json::{json, value::to_value, Value}; +use sqlx::PgPool; use crate::{ - db::{models::device::WireguardNetworkDevice, DbPool, Settings, User, WireguardNetwork}, + db::{models::device::WireguardNetworkDevice, Id, Settings, User, WireguardNetwork}, server_config, VERSION, }; @@ -17,9 +18,9 @@ fn unwrap_json(result: Result) -> Value { } /// Dumps all data that could be used for debugging. -pub async fn dump_config(db: &DbPool) -> Value { +pub async fn dump_config(db: &PgPool) -> Value { // App settings DB records - let settings = match Settings::find_by_id(db, 1).await { + let settings = match Settings::get(db).await { Ok(Some(mut settings)) => { settings.smtp_password = None; json!(settings) @@ -31,14 +32,11 @@ pub async fn dump_config(db: &DbPool) -> Value { let (networks, devices) = match WireguardNetwork::all(db).await { Ok(networks) => { // Devices for each network - let mut devices = HashMap::::new(); + let mut devices = HashMap::::new(); for network in &networks { - let Some(network_id) = network.id else { - continue; - }; devices.insert( - network_id, - unwrap_json(WireguardNetworkDevice::all_for_network(db, network_id).await), + network.id, + unwrap_json(WireguardNetworkDevice::all_for_network(db, network.id).await), ); } ( diff --git a/src/templates.rs b/src/templates.rs index 965db0a2a..38890abfa 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -4,7 +4,7 @@ use tera::{Context, Tera}; use thiserror::Error; use crate::{ - db::{MFAMethod, Session, User}, + db::{Id, MFAMethod, Session, User}, server_config, VERSION, }; @@ -145,9 +145,9 @@ pub fn enrollment_welcome_mail( } // notification sent to admin after user completes enrollment -pub fn enrollment_admin_notification( - user: &User, - admin: &User, +pub fn enrollment_admin_notification( + user: &User, + admin: &User, ip_address: &str, device_info: Option<&str>, ) -> Result { @@ -162,6 +162,7 @@ pub fn enrollment_admin_notification( context.insert("last_name", &user.last_name); context.insert("admin_first_name", &admin.first_name); context.insert("admin_last_name", &admin.last_name); + Ok(tera.render("mail_enrollment_admin_notification", &context)?) } @@ -252,7 +253,7 @@ pub fn gateway_disconnected_mail( } pub fn email_mfa_activation_mail( - user: &User, + user: &User, code: &str, session: &Session, ) -> Result { @@ -268,7 +269,7 @@ pub fn email_mfa_activation_mail( } pub fn email_mfa_code_mail( - user: &User, + user: &User, code: &str, session: Option<&Session>, ) -> Result { diff --git a/src/wg_config.rs b/src/wg_config.rs index d57103ec5..0e65ac62f 100644 --- a/src/wg_config.rs +++ b/src/wg_config.rs @@ -15,7 +15,7 @@ use crate::{ KEY_LENGTH, }; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct ImportedDevice { pub user_id: Option, pub name: String, @@ -143,6 +143,7 @@ pub fn parse_wireguard_config( #[cfg(test)] mod test { use super::*; + use crate::db::NoId; #[test] fn test_parse_config() { @@ -168,7 +169,7 @@ mod test { network.prvkey, "GAA2X3DW0WakGVx+DsGjhDpTgg50s1MlmrLf24Psrlg=" ); - assert_eq!(network.id, None); + assert_eq!(network.id, NoId); assert_eq!(network.name, "Y5ewP5RXstQd71gkmS/M0xL8wi0yVbbVY/ocLM4cQ1Y="); assert_eq!(network.address, "10.0.0.1/24".parse().unwrap()); assert_eq!(network.port, 55055); diff --git a/src/wireguard_peer_disconnect.rs b/src/wireguard_peer_disconnect.rs index eab1aa2be..e8d0fc0d4 100644 --- a/src/wireguard_peer_disconnect.rs +++ b/src/wireguard_peer_disconnect.rs @@ -6,7 +6,7 @@ use std::time::Duration; -use sqlx::{query_as, Error as SqlxError}; +use sqlx::{query_as, Error as SqlxError, PgPool}; use thiserror::Error; use tokio::{sync::broadcast::Sender, time::sleep}; @@ -16,7 +16,7 @@ use crate::db::{ error::ModelError, wireguard::WireguardNetworkError, }, - DbPool, Device, GatewayEvent, WireguardNetwork, + Device, GatewayEvent, Id, WireguardNetwork, }; // How long to sleep between loop iterations @@ -38,7 +38,7 @@ pub enum PeerDisconnectError { /// /// Run with a specified frequency and disconnect all inactive peers in MFA-protected locations. pub async fn run_periodic_peer_disconnect( - pool: DbPool, + pool: PgPool, wireguard_tx: Sender, ) -> Result<(), PeerDisconnectError> { info!("Starting periodic disconnect of inactive devices in MFA-protected locations"); @@ -47,9 +47,9 @@ pub async fn run_periodic_peer_disconnect( // get all MFA-protected locations let locations = query_as!( - WireguardNetwork, + WireguardNetwork::, "SELECT \ - id as \"id?\", name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ + id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ connected_at, mfa_enabled, keepalive_interval, peer_disconnect_threshold \ FROM wireguard_network WHERE mfa_enabled = true", ) @@ -59,7 +59,6 @@ pub async fn run_periodic_peer_disconnect( // loop over all locations for location in locations { debug!("Fetching inactive devices for location {location}"); - let location_id = location.get_id()?; let devices = query_as!( Device, "WITH stats AS ( \ @@ -68,14 +67,14 @@ pub async fn run_periodic_peer_disconnect( WHERE network = $1 \ ORDER BY device_id, collected_at DESC \ ) \ - SELECT d.id as \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created \ + SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created \ FROM device d \ JOIN wireguard_network_device wnd ON wnd.device_id = d.id \ LEFT JOIN stats on d.id = stats.device_id \ WHERE wnd.wireguard_network_id = $1 AND wnd.is_authorized = true AND \ (wnd.authorized_at IS NULL OR (NOW() - wnd.authorized_at) > $2 * interval '1 second') AND \ (stats.latest_handshake IS NULL OR (NOW() - stats.latest_handshake) > $2 * interval '1 second')", - location_id, + location.id, f64::from(location.peer_disconnect_threshold) ) .fetch_all(&pool) @@ -83,14 +82,13 @@ pub async fn run_periodic_peer_disconnect( for device in devices { debug!("Processing inactive device {device}"); - let device_id = device.get_id()?; // start transaction let mut transaction = pool.begin().await?; // get network config for device if let Some(mut device_network_config) = - WireguardNetworkDevice::find(&mut *transaction, device_id, location_id).await? + WireguardNetworkDevice::find(&mut *transaction, device.id, location.id).await? { info!("Marking device {device} as not authorized to connect to location {location}"); // change `is_authorized` value for device @@ -103,7 +101,7 @@ pub async fn run_periodic_peer_disconnect( let device_info = DeviceInfo { device, network_info: vec![DeviceNetworkInfo { - network_id: location_id, + network_id: location.id, device_wireguard_ip: device_network_config.wireguard_ip, preshared_key: device_network_config.preshared_key, is_authorized: device_network_config.is_authorized, diff --git a/src/wireguard_stats_purge.rs b/src/wireguard_stats_purge.rs index b1949ee90..3a9580c2d 100644 --- a/src/wireguard_stats_purge.rs +++ b/src/wireguard_stats_purge.rs @@ -2,10 +2,10 @@ use std::time::Duration; use chrono::{DateTime, Duration as ChronoDuration, NaiveDateTime, Utc}; use humantime::format_duration; -use sqlx::{query, query_scalar, Error as SqlxError, PgExecutor}; +use sqlx::{query, query_scalar, Error as SqlxError, PgExecutor, PgPool}; use tokio::time::sleep; -use crate::db::{DbPool, WireguardPeerStats}; +use crate::db::WireguardPeerStats; // How long to sleep between loop iterations const PURGE_LOOP_SLEEP_SECONDS: u64 = 300; // 5 minutes @@ -16,7 +16,7 @@ impl WireguardPeerStats { /// At least one record is retained for each device & network combination, /// even when older than set threshold. pub async fn purge_old_stats( - pool: &DbPool, + pool: &PgPool, stats_purge_threshold: Duration, ) -> Result<(), SqlxError> { let start = Utc::now(); @@ -94,7 +94,7 @@ impl WireguardPeerStats { } pub async fn run_periodic_stats_purge( - pool: DbPool, + pool: PgPool, stats_purge_frequency: Duration, stats_purge_threshold: Duration, ) -> Result<(), SqlxError> { diff --git a/tests/auth.rs b/tests/auth.rs index c44afe1b3..ebcb932b1 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -7,9 +7,7 @@ use claims::assert_err; use common::fetch_user_details; use defguard::{ auth::{TOTP_CODE_DIGITS, TOTP_CODE_VALIDITY_PERIOD}, - db::{ - models::wallet::keccak256, DbPool, MFAInfo, MFAMethod, Settings, User, UserDetails, Wallet, - }, + db::{models::wallet::keccak256, MFAInfo, MFAMethod, Settings, User, UserDetails, Wallet}, handlers::{Auth, AuthCode, AuthResponse, AuthTotp, WalletChallenge}, hex::to_lower_hex, secret::SecretString, @@ -19,7 +17,7 @@ use reqwest::{header::USER_AGENT, StatusCode}; use secp256k1::{rand::rngs::OsRng, All, Message, Secp256k1, SecretKey}; use serde::Deserialize; use serde_json::json; -use sqlx::query; +use sqlx::{query, PgPool}; use totp_lite::{totp_custom, Sha1}; use webauthn_authenticator_rs::{prelude::Url, softpasskey::SoftPasskey, WebauthnAuthenticator}; use webauthn_rs::prelude::{CreationChallengeResponse, RequestChallengeResponse}; @@ -36,29 +34,33 @@ pub struct RecoveryCodes { async fn make_client() -> TestClient { let (client, client_state) = make_test_client().await; - let mut wallet = Wallet::new_for_user( - client_state.test_user.id.unwrap(), + Wallet::new_for_user( + client_state.test_user.id, "0x4aF8803CBAD86BA65ED347a3fbB3fb50e96eDD3e", "test", 5, "", - ); - wallet.save(&client_state.pool).await.unwrap(); + ) + .save(&client_state.pool) + .await + .unwrap(); client } -async fn make_client_with_db() -> (TestClient, DbPool) { +async fn make_client_with_db() -> (TestClient, PgPool) { let (client, client_state) = make_test_client().await; - let mut wallet = Wallet::new_for_user( - client_state.test_user.id.unwrap(), + Wallet::new_for_user( + client_state.test_user.id, "0x4aF8803CBAD86BA65ED347a3fbB3fb50e96eDD3e", "test", 5, "", - ); - wallet.save(&client_state.pool).await.unwrap(); + ) + .save(&client_state.pool) + .await + .unwrap(); (client, client_state.pool) } @@ -66,14 +68,16 @@ async fn make_client_with_db() -> (TestClient, DbPool) { async fn make_client_with_state() -> (TestClient, ClientState) { let (client, client_state) = make_test_client().await; - let mut wallet = Wallet::new_for_user( - client_state.test_user.id.unwrap(), + Wallet::new_for_user( + client_state.test_user.id, "0x4aF8803CBAD86BA65ED347a3fbB3fb50e96eDD3e", "test", 5, "", - ); - wallet.save(&client_state.pool).await.unwrap(); + ) + .save(&client_state.pool) + .await + .unwrap(); (client, client_state) } @@ -81,9 +85,10 @@ async fn make_client_with_state() -> (TestClient, ClientState) { async fn make_client_with_wallet(address: &str) -> TestClient { let (client, client_state) = make_test_client().await; - let mut wallet = - Wallet::new_for_user(client_state.test_user.id.unwrap(), address, "test", 5, ""); - wallet.save(&client_state.pool).await.unwrap(); + Wallet::new_for_user(client_state.test_user.id, address, "test", 5, "") + .save(&client_state.pool) + .await + .unwrap(); client } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 8fd49fff2..394bedce5 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -6,7 +6,7 @@ use defguard::{ auth::failed_login::FailedLoginMap, build_webapp, config::DefGuardConfig, - db::{init_db, AppEvent, DbPool, GatewayEvent, User, UserDetails}, + db::{init_db, AppEvent, GatewayEvent, Id, User, UserDetails}, enterprise::license::{set_cached_license, License}, grpc::{GatewayMap, WorkerState}, headers::create_user_agent_parser, @@ -15,7 +15,7 @@ use defguard::{ }; use reqwest::{header::HeaderName, StatusCode}; use secrecy::ExposeSecret; -use sqlx::{postgres::PgConnectOptions, query, types::Uuid}; +use sqlx::{postgres::PgConnectOptions, query, types::Uuid, PgPool}; use tokio::sync::{ broadcast::{self, Receiver}, mpsc::{unbounded_channel, UnboundedReceiver}, @@ -30,7 +30,7 @@ pub const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for #[allow(dead_code, clippy::declare_interior_mutable_const)] pub const X_FORWARDED_URI: HeaderName = HeaderName::from_static("x-forwarded-uri"); -pub async fn init_test_db() -> (DbPool, DefGuardConfig) { +pub async fn init_test_db() -> (PgPool, DefGuardConfig) { let config = DefGuardConfig::new_test_config(); let _ = SERVER_CONFIG.set(config.clone()); let opts = PgConnectOptions::new() @@ -39,7 +39,7 @@ pub async fn init_test_db() -> (DbPool, DefGuardConfig) { .username(&config.database_user) .password(config.database_password.expose_secret()) .database(&config.database_name); - let pool = DbPool::connect_with(opts) + let pool = PgPool::connect_with(opts) .await .expect("Failed to connect to Postgres"); let db_name = Uuid::new_v4().to_string(); @@ -61,40 +61,42 @@ pub async fn init_test_db() -> (DbPool, DefGuardConfig) { (pool, config) } -async fn initialize_users(pool: &DbPool, config: &DefGuardConfig) { +async fn initialize_users(pool: &PgPool, config: &DefGuardConfig) { User::init_admin_user(pool, config.default_admin_password.expose_secret()) .await .unwrap(); - let mut test_user = User::new( + User::new( "hpotter", Some("pass123"), "Potter", "Harry", "h.potter@hogwart.edu.uk", None, - ); - test_user.save(pool).await.unwrap(); + ) + .save(pool) + .await + .unwrap(); } pub struct ClientState { - pub pool: DbPool, + pub pool: PgPool, pub worker_state: Arc>, pub wireguard_rx: Receiver, pub mail_rx: UnboundedReceiver, pub failed_logins: Arc>, - pub test_user: User, + pub test_user: User, pub config: DefGuardConfig, } impl ClientState { pub fn new( - pool: DbPool, + pool: PgPool, worker_state: Arc>, wireguard_rx: Receiver, mail_rx: UnboundedReceiver, failed_logins: Arc>, - test_user: User, + test_user: User, config: DefGuardConfig, ) -> Self { Self { @@ -109,7 +111,7 @@ impl ClientState { } } -pub async fn make_base_client(pool: DbPool, config: DefGuardConfig) -> (TestClient, ClientState) { +pub async fn make_base_client(pool: PgPool, config: DefGuardConfig) -> (TestClient, ClientState) { let (tx, rx) = unbounded_channel::(); let worker_state = Arc::new(Mutex::new(WorkerState::new(tx.clone()))); let (wg_tx, wg_rx) = broadcast::channel::(16); @@ -166,6 +168,7 @@ pub async fn make_base_client(pool: DbPool, config: DefGuardConfig) -> (TestClie user_agent_parser, failed_logins, ); + (TestClient::new(webapp).await, client_state) } diff --git a/tests/enrollment.rs b/tests/enrollment.rs index 3e2fe00ef..63fe4112d 100644 --- a/tests/enrollment.rs +++ b/tests/enrollment.rs @@ -2,16 +2,17 @@ mod common; use common::fetch_user_details; use defguard::{ - db::{models::enrollment::Token, DbPool}, + db::models::enrollment::Token, handlers::{AddUserData, Auth}, }; use reqwest::StatusCode; use serde::Deserialize; use serde_json::json; +use sqlx::PgPool; use self::common::{client::TestClient, make_test_client}; -async fn make_client() -> (TestClient, DbPool) { +async fn make_client() -> (TestClient, PgPool) { let (client, client_state) = make_test_client().await; (client, client_state.pool) } diff --git a/tests/enterprise_settings.rs b/tests/enterprise_settings.rs index 88a3fc41c..66d63f5c3 100644 --- a/tests/enterprise_settings.rs +++ b/tests/enterprise_settings.rs @@ -41,7 +41,6 @@ async fn test_only_enterprise_can_modify() { // try to patch enterprise settings let settings = EnterpriseSettings { - id: None, admin_device_management: true, disable_all_traffic: false, only_client_activation: false, @@ -86,7 +85,6 @@ async fn test_admin_devices_management_is_enforced() { // setup admin devices management let settings = EnterpriseSettings { - id: None, admin_device_management: true, disable_all_traffic: false, only_client_activation: false, @@ -164,7 +162,6 @@ async fn test_regular_user_device_management() { // setup admin devices management let settings = EnterpriseSettings { - id: None, admin_device_management: false, disable_all_traffic: false, only_client_activation: false, diff --git a/tests/forward_auth.rs b/tests/forward_auth.rs index 9b939b354..f5edd5e8c 100644 --- a/tests/forward_auth.rs +++ b/tests/forward_auth.rs @@ -8,14 +8,16 @@ use self::common::{client::TestClient, make_test_client, X_FORWARDED_HOST, X_FOR async fn make_client() -> TestClient { let (client, client_state) = make_test_client().await; - let mut wallet = Wallet::new_for_user( - client_state.test_user.id.unwrap(), + Wallet::new_for_user( + client_state.test_user.id, "0x4aF8803CBAD86BA65ED347a3fbB3fb50e96eDD3e", "test", 5, "", - ); - wallet.save(&client_state.pool).await.unwrap(); + ) + .save(&client_state.pool) + .await + .unwrap(); client } diff --git a/tests/oauth.rs b/tests/oauth.rs index aebe081d1..bbe5ac5b3 100644 --- a/tests/oauth.rs +++ b/tests/oauth.rs @@ -8,16 +8,17 @@ use defguard::{ oauth2client::{OAuth2Client, OAuth2ClientSafe}, NewOpenIDClient, }, - DbPool, OAuth2AuthorizedApp, + Id, OAuth2AuthorizedApp, }, handlers::Auth, }; use reqwest::{header::CONTENT_TYPE, StatusCode, Url}; use serde_json::json; +use sqlx::PgPool; use self::common::{client::TestClient, make_test_client}; -async fn make_client() -> (TestClient, DbPool) { +async fn make_client() -> (TestClient, PgPool) { let (client, client_state) = make_test_client().await; (client, client_state.pool) } @@ -43,11 +44,13 @@ async fn test_authorize() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let oauth_client: OAuth2Client = response.json().await; + let oauth_client: OAuth2Client = response.json().await; // authorize client for test user - let mut app = OAuth2AuthorizedApp::new(1, oauth_client.id.unwrap()); - app.save(&pool).await.unwrap(); + OAuth2AuthorizedApp::new(1, oauth_client.id) + .save(&pool) + .await + .unwrap(); // wrong response type let response = client @@ -186,7 +189,7 @@ async fn test_openid_app_management_access() { // list apps let response = client.get("/api/v1/oauth").send().await; assert_eq!(response.status(), StatusCode::OK); - let apps: Vec = response.json().await; + let apps: Vec> = response.json().await; assert_eq!(apps.len(), 1); let test_app = &apps[0]; assert_eq!(test_app.name, oauth2client.name); @@ -197,7 +200,7 @@ async fn test_openid_app_management_access() { .send() .await; assert_eq!(response.status(), StatusCode::OK); - let app: OAuth2Client = response.json().await; + let app: OAuth2Client = response.json().await; assert_eq!(app.name, oauth2client.name); // edit app @@ -231,7 +234,7 @@ async fn test_openid_app_management_access() { .send() .await; assert_eq!(response.status(), StatusCode::OK); - let app: OAuth2Client = response.json().await; + let app: OAuth2Client = response.json().await; assert_eq!(app.name, oauth2client.name); assert!(!app.enabled); @@ -245,7 +248,7 @@ async fn test_openid_app_management_access() { // list apps let response = client.get("/api/v1/oauth").send().await; assert_eq!(response.status(), StatusCode::OK); - let apps: Vec = response.json().await; + let apps: Vec> = response.json().await; assert_eq!(apps.len(), 0); // add another app for further testing @@ -263,7 +266,7 @@ async fn test_openid_app_management_access() { assert_eq!(response.status(), StatusCode::CREATED); let response = client.get("/api/v1/oauth").send().await; assert_eq!(response.status(), StatusCode::OK); - let apps: Vec = response.json().await; + let apps: Vec> = response.json().await; let test_app = &apps[0]; // // login as standard user diff --git a/tests/openid.rs b/tests/openid.rs index 260576622..2394335c9 100644 --- a/tests/openid.rs +++ b/tests/openid.rs @@ -6,7 +6,7 @@ use defguard::{ config::DefGuardConfig, db::{ models::{oauth2client::OAuth2Client, NewOpenIDClient}, - DbPool, + Id, }, handlers::Auth, }; @@ -25,6 +25,7 @@ use reqwest::{ }; use rsa::RsaPrivateKey; use serde::Deserialize; +use sqlx::PgPool; mod common; use self::common::{client::TestClient, init_test_db, make_base_client, make_test_client}; @@ -34,7 +35,7 @@ async fn make_client() -> TestClient { client } -async fn make_client_v2(pool: DbPool, config: DefGuardConfig) -> TestClient { +async fn make_client_v2(pool: PgPool, config: DefGuardConfig) -> TestClient { let (client, _) = make_base_client(pool, config).await; client } @@ -69,7 +70,7 @@ async fn test_openid_client() { let response = client.get("/api/v1/oauth").send().await; assert_eq!(response.status(), StatusCode::OK); - let openid_clients: Vec = response.json().await; + let openid_clients: Vec> = response.json().await; assert_eq!(openid_clients.len(), 1); openid_client.name = "Test changed".into(); @@ -85,7 +86,7 @@ async fn test_openid_client() { .send() .await; assert_eq!(response.status(), StatusCode::OK); - let fetched_client: OAuth2Client = response.json().await; + let fetched_client: OAuth2Client = response.json().await; assert_eq!(fetched_client.name, openid_client.name); // OpenID flow tests @@ -100,7 +101,7 @@ async fn test_openid_client() { let response = client.get("/api/v1/oauth").send().await; assert_eq!(response.status(), StatusCode::OK); - let openid_clients: Vec = response.json().await; + let openid_clients: Vec> = response.json().await; assert!(openid_clients.is_empty()); } @@ -123,7 +124,7 @@ async fn test_openid_flow() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let openid_client: OAuth2Client = response.json().await; + let openid_client: OAuth2Client = response.json().await; assert_eq!(openid_client.name, "Test"); // all clients @@ -360,7 +361,7 @@ async fn test_openid_flow() { /// Helper function for translating HTTP communication from `HttpRequest` to `LocalClient`. async fn http_client( request: HttpRequest, - pool: DbPool, + pool: PgPool, config: DefGuardConfig, ) -> Result { let client = make_client_v2(pool, config).await; @@ -429,7 +430,7 @@ async fn test_openid_authorization_code() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let oauth2client: OAuth2Client = response.json().await; + let oauth2client: OAuth2Client = response.json().await; assert_eq!(oauth2client.name, "My test client"); assert_eq!(oauth2client.scope[0], "openid"); assert_eq!(oauth2client.client_id.len(), 16); @@ -534,7 +535,7 @@ async fn test_openid_authorization_code_with_pkce() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let oauth2client: OAuth2Client = response.json().await; + let oauth2client: OAuth2Client = response.json().await; assert_eq!(oauth2client.name, "My test client"); assert_eq!(oauth2client.scope[0], "openid"); @@ -643,7 +644,7 @@ async fn test_openid_flow_new_login_mail() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let openid_client: OAuth2Client = response.json().await; + let openid_client: OAuth2Client = response.json().await; assert_eq!(openid_client.name, "Test"); // all clients diff --git a/tests/openid_login.rs b/tests/openid_login.rs index f1b7f8d4e..beb62c7e5 100644 --- a/tests/openid_login.rs +++ b/tests/openid_login.rs @@ -1,7 +1,6 @@ use chrono::{Duration, Utc}; use defguard::{ config::DefGuardConfig, - db::DbPool, enterprise::{ handlers::openid_providers::AddProviderData, license::{set_cached_license, License}, @@ -10,6 +9,7 @@ use defguard::{ }; use reqwest::{StatusCode, Url}; use serde::Deserialize; +use sqlx::PgPool; mod common; use self::common::{client::TestClient, make_base_client, make_test_client}; @@ -20,7 +20,7 @@ async fn make_client() -> TestClient { } #[allow(dead_code)] -async fn make_client_v2(pool: DbPool, config: DefGuardConfig) -> TestClient { +async fn make_client_v2(pool: PgPool, config: DefGuardConfig) -> TestClient { let (client, _) = make_base_client(pool, config).await; client } diff --git a/tests/user.rs b/tests/user.rs index bae25d3de..66d6730b2 100644 --- a/tests/user.rs +++ b/tests/user.rs @@ -3,7 +3,7 @@ mod common; use defguard::{ db::{ models::{oauth2client::OAuth2Client, wallet::keccak256, NewOpenIDClient}, - AddDevice, UserInfo, + AddDevice, Id, UserInfo, }, handlers::{AddUserData, Auth, PasswordChange, PasswordChangeSelf, Username, WalletChallenge}, hex::to_lower_hex, @@ -528,7 +528,7 @@ async fn test_user_unregister_authorized_app() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let openid_client: OAuth2Client = response.json().await; + let openid_client: OAuth2Client = response.json().await; assert_eq!(openid_client.name, "Test"); let response = client .post(format!( diff --git a/tests/webhook.rs b/tests/webhook.rs index fe986d582..42ec6876a 100644 --- a/tests/webhook.rs +++ b/tests/webhook.rs @@ -1,6 +1,9 @@ mod common; -use defguard::{db::WebHook, handlers::Auth}; +use defguard::{ + db::{Id, NoId, WebHook}, + handlers::Auth, +}; use reqwest::StatusCode; use self::common::{client::TestClient, make_test_client}; @@ -19,7 +22,7 @@ async fn test_webhooks() { assert_eq!(response.status(), StatusCode::OK); let mut webhook = WebHook { - id: None, + id: NoId, url: "http://localhost:3000/trigger-happy".into(), description: "Test".into(), token: "1234567890".into(), @@ -35,36 +38,36 @@ async fn test_webhooks() { let response = client.get("/api/v1/webhook").send().await; assert_eq!(response.status(), StatusCode::OK); - let webhooks: Vec = response.json().await; + let webhooks: Vec> = response.json().await; assert_eq!(webhooks.len(), 1); webhook.description = "Changed".into(); webhook.on_user_modified = false; let response = client - .put(format!("/api/v1/webhook/{}", webhooks[0].id.unwrap())) + .put(format!("/api/v1/webhook/{}", webhooks[0].id)) .json(&webhook) .send() .await; assert_eq!(response.status(), StatusCode::OK); let response = client - .get(format!("/api/v1/webhook/{}", webhooks[0].id.unwrap())) + .get(format!("/api/v1/webhook/{}", webhooks[0].id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); - let fetched_webhook: WebHook = response.json().await; + let fetched_webhook: WebHook = response.json().await; assert_eq!(fetched_webhook.url, webhook.url); assert_eq!(fetched_webhook.description, webhook.description); assert_eq!(fetched_webhook.on_user_modified, webhook.on_user_modified); let response = client - .delete(format!("/api/v1/webhook/{}", webhooks[0].id.unwrap())) + .delete(format!("/api/v1/webhook/{}", webhooks[0].id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); let response = client.get("/api/v1/webhook").send().await; assert_eq!(response.status(), StatusCode::OK); - let webhooks: Vec = response.json().await; + let webhooks: Vec> = response.json().await; assert!(webhooks.is_empty()); } diff --git a/tests/wireguard.rs b/tests/wireguard.rs index 5fbe1983c..a3cd2814f 100644 --- a/tests/wireguard.rs +++ b/tests/wireguard.rs @@ -6,7 +6,7 @@ use defguard::{ device::WireguardNetworkDevice, wireguard::{DEFAULT_DISCONNECT_THRESHOLD, DEFAULT_KEEPALIVE_INTERVAL}, }, - Device, GatewayEvent, WireguardNetwork, + Device, GatewayEvent, Id, WireguardNetwork, }, handlers::{wireguard::WireguardNetworkData, Auth, GroupInfo}, }; @@ -48,7 +48,7 @@ async fn test_network() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let network: WireguardNetwork = response.json().await; + let network: WireguardNetwork = response.json().await; assert_eq!(network.name, "network"); let event = wg_rx.try_recv().unwrap(); assert_matches!(event, GatewayEvent::NetworkCreated(..)); @@ -72,7 +72,7 @@ async fn test_network() { peer_disconnect_threshold: DEFAULT_DISCONNECT_THRESHOLD, }; let response = client - .put(format!("/api/v1/network/{}", network.id.unwrap())) + .put(format!("/api/v1/network/{}", network.id)) .json(&network_data) .send() .await; @@ -89,23 +89,23 @@ async fn test_network() { // list networks let response = client.get("/api/v1/network").send().await; assert_eq!(response.status(), StatusCode::OK); - let networks: Vec = response.json().await; + let networks: Vec> = response.json().await; assert_eq!(networks.len(), 1); // network details let network_from_list = networks[0].clone(); assert_eq!(network_from_list.name, "my network"); let response = client - .get(format!("/api/v1/network/{}", network_from_list.id.unwrap())) + .get(format!("/api/v1/network/{}", network_from_list.id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); - let network_from_details: WireguardNetwork = response.json().await; + let network_from_details: WireguardNetwork = response.json().await; assert_eq!(network_from_details, network_from_list); // delete network let response = client - .delete(format!("/api/v1/network/{}", network.id.unwrap())) + .delete(format!("/api/v1/network/{}", network.id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); @@ -136,7 +136,7 @@ async fn test_device() { // network details let response = client.get("/api/v1/network/1").send().await; assert_eq!(response.status(), StatusCode::OK); - let network_from_details: WireguardNetwork = response.json().await; + let network_from_details: WireguardNetwork = response.json().await; // create device let device = json!({ @@ -159,7 +159,7 @@ async fn test_device() { .unwrap(); assert_eq!( network_devices[0].wireguard_network_id, - network_from_details.id.unwrap() + network_from_details.id ); // add another network @@ -181,7 +181,7 @@ async fn test_device() { // list devices let response = client.get("/api/v1/device").json(&device).send().await; assert_eq!(response.status(), StatusCode::OK); - let devices: Vec = response.json().await; + let devices: Vec> = response.json().await; assert_eq!(devices.len(), 1); let device = devices[0].clone(); assert_eq!(device.name, "device"); @@ -197,7 +197,7 @@ async fn test_device() { .send() .await; assert_eq!(response.status(), StatusCode::OK); - let user_devices: Vec = response.json().await; + let user_devices: Vec> = response.json().await; assert_eq!(user_devices.len(), 1); assert_eq!(devices.len(), 1); assert_eq!(device.id, user_devices[0].id); @@ -209,7 +209,7 @@ async fn test_device() { modified_device.name = modified_name.into(); modified_device.wireguard_pubkey = modified_key.into(); let response = client - .put(format!("/api/v1/device/{}", device.id.unwrap())) + .put(format!("/api/v1/device/{}", device.id)) .json(&modified_device) .send() .await; @@ -219,20 +219,17 @@ async fn test_device() { // device details let response = client - .get(format!("/api/v1/device/{}", device.id.unwrap())) + .get(format!("/api/v1/device/{}", device.id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); - let device_from_details: Device = response.json().await; + let device_from_details: Device = response.json().await; assert_eq!(device_from_details.name, modified_name); assert_eq!(device_from_details.wireguard_pubkey, modified_key); // device config let response = client - .get(format!( - "/api/v1/network/1/device/{}/config", - device.id.unwrap() - )) + .get(format!("/api/v1/network/1/device/{}/config", device.id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); @@ -255,10 +252,7 @@ async fn test_device() { ); let response = client - .delete(format!( - "/api/v1/network/{}", - network_from_details.id.unwrap() - )) + .delete(format!("/api/v1/network/{}", network_from_details.id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); @@ -267,7 +261,7 @@ async fn test_device() { // delete device let response = client - .delete(format!("/api/v1/device/{}", device.id.unwrap())) + .delete(format!("/api/v1/device/{}", device.id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); @@ -276,7 +270,7 @@ async fn test_device() { let response = client.get("/api/v1/device").json(&device).send().await; assert_eq!(response.status(), StatusCode::OK); - let devices: Vec = response.json().await; + let devices: Vec> = response.json().await; assert!(devices.is_empty()); } @@ -404,7 +398,7 @@ async fn test_device_permissions() { let response = client.get("/api/v1/device/user/hpotter").send().await; assert_eq!(response.status(), StatusCode::OK); - let user_devices: Vec = response.json().await; + let user_devices: Vec> = response.json().await; assert_eq!(user_devices.len(), 3); // admin can list devices of other users @@ -414,12 +408,12 @@ async fn test_device_permissions() { let response = client.get("/api/v1/device/user/admin").send().await; assert_eq!(response.status(), StatusCode::OK); - let user_devices: Vec = response.json().await; + let user_devices: Vec> = response.json().await; assert_eq!(user_devices.len(), 2); let response = client.get("/api/v1/device/user/hpotter").send().await; assert_eq!(response.status(), StatusCode::OK); - let user_devices: Vec = response.json().await; + let user_devices: Vec> = response.json().await; assert_eq!(user_devices.len(), 3); } @@ -446,7 +440,7 @@ async fn test_device_pubkey() { // network details let response = client.get("/api/v1/network/1").send().await; assert_eq!(response.status(), StatusCode::OK); - let network_from_details: WireguardNetwork = response.json().await; + let network_from_details: WireguardNetwork = response.json().await; // create bad device let device = json!({ @@ -487,14 +481,14 @@ async fn test_device_pubkey() { // list devices let response = client.get("/api/v1/device").json(&device).send().await; assert_eq!(response.status(), StatusCode::OK); - let devices: Vec = response.json().await; + let devices: Vec> = response.json().await; assert_eq!(devices.len(), 1); // modify device let mut device = devices[0].clone(); device.wireguard_pubkey = network_from_details.pubkey; let response = client - .put(format!("/api/v1/device/{}", device.id.unwrap())) + .put(format!("/api/v1/device/{}", device.id)) .json(&device) .send() .await; @@ -525,6 +519,6 @@ async fn test_device_pubkey() { // make sure no device was created let response = client.get("/api/v1/device").json(&device).send().await; assert_eq!(response.status(), StatusCode::OK); - let devices: Vec = response.json().await; + let devices: Vec> = response.json().await; assert_eq!(devices.len(), 1); } diff --git a/tests/wireguard_network_allowed_groups.rs b/tests/wireguard_network_allowed_groups.rs index 484e612a4..c5e93e471 100644 --- a/tests/wireguard_network_allowed_groups.rs +++ b/tests/wireguard_network_allowed_groups.rs @@ -2,37 +2,38 @@ mod common; use claims::assert_err; use defguard::{ - db::{DbPool, Device, GatewayEvent, Group, User, WireguardNetwork}, + db::{Device, GatewayEvent, Group, Id, User, WireguardNetwork}, handlers::{wireguard::ImportedNetworkData, Auth}, }; use matches::assert_matches; use reqwest::StatusCode; use serde_json::json; +use sqlx::PgPool; use self::common::{fetch_user_details, make_test_client}; // setup user groups, test users and devices -async fn setup_test_users(pool: &DbPool) -> (Vec, Vec) { +async fn setup_test_users(pool: &PgPool) -> (Vec>, Vec>) { let mut users = Vec::new(); let mut devices = Vec::new(); // create user groups - let mut allowed_group = Group::new("allowed group"); - allowed_group.save(pool).await.unwrap(); + let allowed_group = Group::new("allowed group").save(pool).await.unwrap(); - let mut not_allowed_group = Group::new("not allowed group"); - not_allowed_group.save(pool).await.unwrap(); + let not_allowed_group = Group::new("not allowed group").save(pool).await.unwrap(); // admin user let admin_user = User::find_by_username(pool, "admin") .await .unwrap() .unwrap(); - let mut admin_device = Device::new( + let admin_device = Device::new( "admin device".into(), "nst4lmZz9kPTq6OdeQq2G2th3n+QneHKmG1wJJ3Jrq0=".into(), - admin_user.id.unwrap(), - ); - admin_device.save(pool).await.unwrap(); + admin_user.id, + ) + .save(pool) + .await + .unwrap(); users.push(admin_user); devices.push(admin_device); @@ -42,54 +43,64 @@ async fn setup_test_users(pool: &DbPool) -> (Vec, Vec) { .unwrap() .unwrap(); test_user.add_to_group(pool, &allowed_group).await.unwrap(); - let mut test_device = Device::new( + let test_device = Device::new( "test device".into(), "wYOt6ImBaQ3BEMQ3Xf5P5fTnbqwOvjcqYkkSBt+1xOg=".into(), - test_user.id.unwrap(), - ); - test_device.save(pool).await.unwrap(); + test_user.id, + ) + .save(pool) + .await + .unwrap(); users.push(test_user); devices.push(test_device); // standard user in other, non-allowed group - let mut other_user = User::new( + let other_user = User::new( "ssnape", Some("pass123"), "Snape", "Severus", "s.snape@hogwart.edu.uk", None, - ); - other_user.save(pool).await.unwrap(); + ) + .save(pool) + .await + .unwrap(); other_user .add_to_group(pool, ¬_allowed_group) .await .unwrap(); - let mut other_device = Device::new( + let other_device = Device::new( "other device".into(), "v2U14sjNN4tOYD3P15z0WkjriKY9Hl85I3vIEPomrYs=".into(), - other_user.id.unwrap(), - ); - other_device.save(pool).await.unwrap(); + other_user.id, + ) + .save(pool) + .await + .unwrap(); users.push(other_user); devices.push(other_device); // standard user in no groups - let mut non_group_user = User::new( + let non_group_user = User::new( "dobby", Some("pass123"), "Elf", "Dobby", "dobby@hogwart.edu.uk", None, - ); - non_group_user.save(pool).await.unwrap(); - let mut non_group_device = Device::new( + ) + .save(pool) + .await + .unwrap(); + let non_group_device = Device::new( "non group device".into(), "6xmL/jRuxmzQ3J2/kVZnKnh+6dwODcEEczmmkIKU4sM=".into(), - non_group_user.id.unwrap(), - ); - non_group_device.save(pool).await.unwrap(); + non_group_user.id, + ) + .save(pool) + .await + .unwrap(); users.push(non_group_user); devices.push(non_group_device); @@ -125,7 +136,7 @@ async fn test_create_new_network() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let network: WireguardNetwork = response.json().await; + let network: WireguardNetwork = response.json().await; assert_eq!(network.name, "network"); let event = wg_rx.try_recv().unwrap(); assert_matches!(event, GatewayEvent::NetworkCreated(..)); @@ -167,7 +178,7 @@ async fn test_modify_network() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let network: WireguardNetwork = response.json().await; + let network: WireguardNetwork = response.json().await; assert_eq!(network.name, "network"); let event = wg_rx.try_recv().unwrap(); assert_matches!(event, GatewayEvent::NetworkCreated(..)); @@ -353,7 +364,7 @@ async fn test_import_network_existing_devices() { let GatewayEvent::DeviceModified(device_info) = wg_rx.try_recv().unwrap() else { panic!() }; - assert_eq!(device_info.device.id.unwrap(), devices[1].id.unwrap()); + assert_eq!(device_info.device.id, devices[1].id); assert_eq!(device_info.network_info.len(), 1); assert_eq!(device_info.network_info[0].network_id, 1); assert_eq!( @@ -364,7 +375,7 @@ async fn test_import_network_existing_devices() { let GatewayEvent::DeviceCreated(device_info) = wg_rx.try_recv().unwrap() else { panic!() }; - assert_eq!(device_info.device.id.unwrap(), devices[0].id.unwrap()); + assert_eq!(device_info.device.id, devices[0].id); assert_eq!(device_info.network_info.len(), 1); assert_eq!(device_info.network_info[0].network_id, 1); assert_eq!( @@ -431,13 +442,13 @@ PersistentKeepalive = 300 } // assign devices to users - mapped_devices[0].user_id = users[0].id; - mapped_devices[1].user_id = users[1].id; - mapped_devices[2].user_id = users[2].id; - mapped_devices[3].user_id = users[3].id; + mapped_devices[0].user_id = Some(users[0].id); + mapped_devices[1].user_id = Some(users[1].id); + mapped_devices[2].user_id = Some(users[2].id); + mapped_devices[3].user_id = Some(users[3].id); let response = client - .post(format!("/api/v1/network/{}/devices", network.id.unwrap())) + .post(format!("/api/v1/network/{}/devices", network.id)) .json(&json!({"devices": mapped_devices.clone()})) .send() .await; @@ -512,7 +523,7 @@ async fn test_modify_user() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let network: WireguardNetwork = response.json().await; + let network: WireguardNetwork = response.json().await; assert_eq!(network.name, "network"); let event = wg_rx.try_recv().unwrap(); assert_matches!(event, GatewayEvent::NetworkCreated(..)); @@ -607,7 +618,7 @@ async fn test_delete_only_allowed_group() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let network: WireguardNetwork = response.json().await; + let network: WireguardNetwork = response.json().await; assert_eq!(network.name, "network"); let event = wg_rx.try_recv().unwrap(); assert_matches!(event, GatewayEvent::NetworkCreated(..)); diff --git a/tests/wireguard_network_import.rs b/tests/wireguard_network_import.rs index 1ac5c5f3a..d5e43656f 100644 --- a/tests/wireguard_network_import.rs +++ b/tests/wireguard_network_import.rs @@ -45,7 +45,7 @@ async fn test_config_import() { let pool = client_state.pool; // setup initial network - let mut initial_network = WireguardNetwork::new( + let initial_network = WireguardNetwork::new( "initial".into(), "10.1.9.0/24".parse().unwrap(), 51515, @@ -62,23 +62,27 @@ async fn test_config_import() { // add existing devices let mut transaction = pool.begin().await.unwrap(); - let mut device_1 = Device::new( + let device_1 = Device::new( "test device".into(), "l07+qPWs4jzW3Gp1DKbHgBMRRm4Jg3q2BJxw0ZYl6c4=".into(), 1, - ); - device_1.save(&mut *transaction).await.unwrap(); + ) + .save(&mut *transaction) + .await + .unwrap(); device_1 .add_to_all_networks(&mut transaction) .await .unwrap(); - let mut device_2 = Device::new( + let device_2 = Device::new( "another test device".into(), "v2U14sjNN4tOYD3P15z0WkjriKY9Hl85I3vIEPomrYs=".into(), 1, - ); - device_2.save(&mut *transaction).await.unwrap(); + ) + .save(&mut *transaction) + .await + .unwrap(); device_2 .add_to_all_networks(&mut transaction) .await @@ -103,7 +107,7 @@ async fn test_config_import() { // network assertions let network = response.network; - assert_eq!(network.id, Some(2)); + assert_eq!(network.id, 2); assert_eq!(network.name, "network"); assert_eq!(network.address, "10.0.0.1/24".parse().unwrap()); assert_eq!(network.port, 55055); @@ -166,7 +170,7 @@ async fn test_config_import() { // post modified devices let response = client - .post(format!("/api/v1/network/{}/devices", network.id.unwrap())) + .post(format!("/api/v1/network/{}/devices", network.id)) .json(&json!({"devices": [device1, device2]})) .send() .await; diff --git a/tests/wireguard_network_stats.rs b/tests/wireguard_network_stats.rs index 8c8c306fc..11f5f7654 100644 --- a/tests/wireguard_network_stats.rs +++ b/tests/wireguard_network_stats.rs @@ -6,7 +6,7 @@ use defguard::{ models::wireguard::{ WireguardDeviceTransferRow, WireguardNetworkStats, WireguardUserStatsRow, }, - Device, WireguardPeerStats, + Device, Id, NoId, WireguardPeerStats, }, handlers::Auth, }; @@ -73,7 +73,7 @@ async fn test_stats() { assert_eq!(response.status(), StatusCode::CREATED); // get devices - let mut devices = Vec::::new(); + let mut devices = Vec::>::new(); let response = client.get("/api/v1/device/1").send().await; assert_eq!(response.status(), StatusCode::OK); devices.push(response.json().await); @@ -100,9 +100,9 @@ async fn test_stats() { let samples = 60 * 11; // 11 hours of samples for i in 0..samples { for (d, device) in devices.iter().enumerate().take(2) { - let mut wps = WireguardPeerStats { - id: None, - device_id: device.id.unwrap(), + WireguardPeerStats { + id: NoId, + device_id: device.id, collected_at: now - Duration::minutes(i), network: 1, endpoint: Some("11.22.33.44".into()), @@ -110,8 +110,10 @@ async fn test_stats() { download: (samples - i) * 20 * (d as i64 + 1), latest_handshake: now - Duration::minutes(i * 10), allowed_ips: Some("10.1.1.0/24".into()), - }; - wps.save(&pool).await.unwrap(); + } + .save(&pool) + .await + .unwrap(); } }