From 5f187a6817285111dd0d6d39ab23175b283746c7 Mon Sep 17 00:00:00 2001 From: "a.tolmachev" Date: Sun, 12 Apr 2026 12:24:27 +0300 Subject: [PATCH] registry: add sqlx compile-time checks for auth and secrets --- ...8a1c87d1cd184973bf1cecc0c725691432c08.json | 25 ++ ...9517becbccf7462aa3008c356f7ef93cd73b4.json | 70 +++++ ...52e35c0251e45ed744b3c4883e7ab8dd64747.json | 71 +++++ ...c6bd13f51b118a1f5395513ad4783b3b2e1fd.json | 52 ++++ ...864379a0236889b0a66e7b9b45197565b3787.json | 59 +++++ ...c73a85fd24606e88c9b07fc33535690b540d1.json | 53 ++++ ...251f5dd1af2b7667e3fa01994e568c962ea77.json | 52 ++++ Cargo.lock | 249 ++++++++++++++++++ Cargo.toml | 2 +- crates/crank-registry/src/postgres/auth.rs | 81 ++++-- crates/crank-registry/src/postgres/mod.rs | 41 --- crates/crank-registry/src/postgres/secret.rs | 75 ++++-- justfile | 6 + 13 files changed, 749 insertions(+), 87 deletions(-) create mode 100644 .sqlx/query-3df72b8a0ac4a76b15909fc78268a1c87d1cd184973bf1cecc0c725691432c08.json create mode 100644 .sqlx/query-4b6bd8192eea43df8d50504e86e9517becbccf7462aa3008c356f7ef93cd73b4.json create mode 100644 .sqlx/query-4fcd20a68487da32614c2d7ad6152e35c0251e45ed744b3c4883e7ab8dd64747.json create mode 100644 .sqlx/query-5c767e8840c289a2afdde4b13d8c6bd13f51b118a1f5395513ad4783b3b2e1fd.json create mode 100644 .sqlx/query-88116bca616419bee79adc6aa5d864379a0236889b0a66e7b9b45197565b3787.json create mode 100644 .sqlx/query-d50ada14acca676613ea3312e92c73a85fd24606e88c9b07fc33535690b540d1.json create mode 100644 .sqlx/query-da6cb061da338d4ed4f0c0da995251f5dd1af2b7667e3fa01994e568c962ea77.json diff --git a/.sqlx/query-3df72b8a0ac4a76b15909fc78268a1c87d1cd184973bf1cecc0c725691432c08.json b/.sqlx/query-3df72b8a0ac4a76b15909fc78268a1c87d1cd184973bf1cecc0c725691432c08.json new file mode 100644 index 0000000..83e24cc --- /dev/null +++ b/.sqlx/query-3df72b8a0ac4a76b15909fc78268a1c87d1cd184973bf1cecc0c725691432c08.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "insert into users (\n id,\n email,\n display_name,\n password_hash,\n status,\n created_at\n ) values (\n $1, $2, $3, $4, 'active', now()\n )\n on conflict (email) do update\n set display_name = excluded.display_name,\n password_hash = excluded.password_hash,\n status = 'active'\n returning id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "3df72b8a0ac4a76b15909fc78268a1c87d1cd184973bf1cecc0c725691432c08" +} diff --git a/.sqlx/query-4b6bd8192eea43df8d50504e86e9517becbccf7462aa3008c356f7ef93cd73b4.json b/.sqlx/query-4b6bd8192eea43df8d50504e86e9517becbccf7462aa3008c356f7ef93cd73b4.json new file mode 100644 index 0000000..e9d2c59 --- /dev/null +++ b/.sqlx/query-4b6bd8192eea43df8d50504e86e9517becbccf7462aa3008c356f7ef93cd73b4.json @@ -0,0 +1,70 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n workspace_id,\n name,\n kind,\n status,\n current_version,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",\n to_char(last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at\n from secrets\n where workspace_id = $1\n order by name asc", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "kind", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "current_version", + "type_info": "Int4" + }, + { + "ordinal": 6, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "updated_at!", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "last_used_at", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + null, + null, + null + ] + }, + "hash": "4b6bd8192eea43df8d50504e86e9517becbccf7462aa3008c356f7ef93cd73b4" +} diff --git a/.sqlx/query-4fcd20a68487da32614c2d7ad6152e35c0251e45ed744b3c4883e7ab8dd64747.json b/.sqlx/query-4fcd20a68487da32614c2d7ad6152e35c0251e45ed744b3c4883e7ab8dd64747.json new file mode 100644 index 0000000..8d0ff27 --- /dev/null +++ b/.sqlx/query-4fcd20a68487da32614c2d7ad6152e35c0251e45ed744b3c4883e7ab8dd64747.json @@ -0,0 +1,71 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n workspace_id,\n name,\n kind,\n status,\n current_version,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",\n to_char(last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at\n from secrets\n where workspace_id = $1 and id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "kind", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "current_version", + "type_info": "Int4" + }, + { + "ordinal": 6, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "updated_at!", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "last_used_at", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + null, + null, + null + ] + }, + "hash": "4fcd20a68487da32614c2d7ad6152e35c0251e45ed744b3c4883e7ab8dd64747" +} diff --git a/.sqlx/query-5c767e8840c289a2afdde4b13d8c6bd13f51b118a1f5395513ad4783b3b2e1fd.json b/.sqlx/query-5c767e8840c289a2afdde4b13d8c6bd13f51b118a1f5395513ad4783b3b2e1fd.json new file mode 100644 index 0000000..5f11bf2 --- /dev/null +++ b/.sqlx/query-5c767e8840c289a2afdde4b13d8c6bd13f51b118a1f5395513ad4783b3b2e1fd.json @@ -0,0 +1,52 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n email,\n display_name,\n password_hash as \"password_hash!\",\n status,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"\n from users\n where id = $1\n limit 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash!", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "created_at!", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + true, + false, + null + ] + }, + "hash": "5c767e8840c289a2afdde4b13d8c6bd13f51b118a1f5395513ad4783b3b2e1fd" +} diff --git a/.sqlx/query-88116bca616419bee79adc6aa5d864379a0236889b0a66e7b9b45197565b3787.json b/.sqlx/query-88116bca616419bee79adc6aa5d864379a0236889b0a66e7b9b45197565b3787.json new file mode 100644 index 0000000..6bb1035 --- /dev/null +++ b/.sqlx/query-88116bca616419bee79adc6aa5d864379a0236889b0a66e7b9b45197565b3787.json @@ -0,0 +1,59 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n s.id,\n s.user_id,\n s.current_workspace_id,\n u.email,\n u.display_name,\n u.status,\n to_char(u.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"\n from user_sessions s\n join users u on u.id = s.user_id\n where s.id = $1\n and s.secret_hash = $2\n and s.status = 'active'\n and s.expires_at > now()\n limit 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "user_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "current_workspace_id", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "created_at!", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + true, + false, + false, + false, + null + ] + }, + "hash": "88116bca616419bee79adc6aa5d864379a0236889b0a66e7b9b45197565b3787" +} diff --git a/.sqlx/query-d50ada14acca676613ea3312e92c73a85fd24606e88c9b07fc33535690b540d1.json b/.sqlx/query-d50ada14acca676613ea3312e92c73a85fd24606e88c9b07fc33535690b540d1.json new file mode 100644 index 0000000..a9356cb --- /dev/null +++ b/.sqlx/query-d50ada14acca676613ea3312e92c73a85fd24606e88c9b07fc33535690b540d1.json @@ -0,0 +1,53 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n sv.secret_id,\n sv.version,\n sv.ciphertext,\n sv.key_version,\n to_char(sv.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n sv.created_by\n from secrets s\n join secret_versions sv\n on sv.secret_id = s.id and sv.version = s.current_version\n where s.workspace_id = $1 and s.id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "secret_id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "version", + "type_info": "Int4" + }, + { + "ordinal": 2, + "name": "ciphertext", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "key_version", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "created_by", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + null, + true + ] + }, + "hash": "d50ada14acca676613ea3312e92c73a85fd24606e88c9b07fc33535690b540d1" +} diff --git a/.sqlx/query-da6cb061da338d4ed4f0c0da995251f5dd1af2b7667e3fa01994e568c962ea77.json b/.sqlx/query-da6cb061da338d4ed4f0c0da995251f5dd1af2b7667e3fa01994e568c962ea77.json new file mode 100644 index 0000000..027cd5c --- /dev/null +++ b/.sqlx/query-da6cb061da338d4ed4f0c0da995251f5dd1af2b7667e3fa01994e568c962ea77.json @@ -0,0 +1,52 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n email,\n display_name,\n password_hash as \"password_hash!\",\n status,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"\n from users\n where email = $1\n limit 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash!", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "created_at!", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + true, + false, + null + ] + }, + "hash": "da6cb061da338d4ed4f0c0da995251f5dd1af2b7667e3fa01994e568c962ea77" +} diff --git a/Cargo.lock b/Cargo.lock index ed58ed6..807bdd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -226,6 +226,9 @@ name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] [[package]] name = "blake2" @@ -304,6 +307,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "cookie" version = "0.18.1" @@ -561,6 +570,17 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" @@ -577,6 +597,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -672,6 +693,17 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1216,6 +1248,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "leb128fmt" @@ -1229,6 +1264,12 @@ version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libredox" version = "0.1.15" @@ -1241,6 +1282,16 @@ dependencies = [ "redox_syscall 0.7.3", ] +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -1368,12 +1419,48 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1381,6 +1468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1444,6 +1532,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1493,6 +1590,33 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "plain" version = "0.2.3" @@ -1965,6 +2089,26 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-hash" version = "2.1.1" @@ -2210,6 +2354,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "slab" version = "0.4.12" @@ -2235,6 +2389,25 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sqlx" version = "0.8.6" @@ -2243,7 +2416,9 @@ checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", + "sqlx-mysql", "sqlx-postgres", + "sqlx-sqlite", ] [[package]] @@ -2312,12 +2487,56 @@ dependencies = [ "serde_json", "sha2", "sqlx-core", + "sqlx-mysql", "sqlx-postgres", + "sqlx-sqlite", "syn", "tokio", "url", ] +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + [[package]] name = "sqlx-postgres" version = "0.8.6" @@ -2355,6 +2574,30 @@ dependencies = [ "whoami", ] +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror", + "tracing", + "url", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -2896,6 +3139,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index fd0488f..f8c953c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" serde_yaml = "0.9" sha2 = "0.10" -sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio-rustls", "postgres"] } +sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio-rustls", "postgres", "macros", "json"] } thiserror = "2" time = { version = "0.3", features = ["formatting", "parsing"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/crates/crank-registry/src/postgres/auth.rs b/crates/crank-registry/src/postgres/auth.rs index 8a11da2..36e53fb 100644 --- a/crates/crank-registry/src/postgres/auth.rs +++ b/crates/crank-registry/src/postgres/auth.rs @@ -7,7 +7,8 @@ impl PostgresRegistry { display_name: &str, password_hash: &str, ) -> Result { - let row = sqlx::query( + let user_id = format!("user_{}", uuid::Uuid::now_v7().simple()); + let row = sqlx::query!( "insert into users ( id, email, @@ -23,15 +24,15 @@ impl PostgresRegistry { password_hash = excluded.password_hash, status = 'active' returning id", + user_id, + email, + display_name, + password_hash, ) - .bind(format!("user_{}", uuid::Uuid::now_v7().simple())) - .bind(email) - .bind(display_name) - .bind(password_hash) .fetch_one(&self.pool) .await?; - Ok(UserId::new(row.try_get::("id")?)) + Ok(UserId::new(row.id)) } pub async fn ensure_membership( @@ -65,46 +66,70 @@ impl PostgresRegistry { &self, email: &str, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select id, email, display_name, - password_hash, + password_hash as \"password_hash!\", status, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\" from users where email = $1 limit 1", + email, ) - .bind(email) .fetch_optional(&self.pool) .await?; - row.as_ref().map(map_auth_user_record).transpose() + row.map(|row| { + Ok(AuthUserRecord { + user: User { + id: UserId::new(row.id), + email: row.email, + display_name: row.display_name, + status: deserialize_enum_text(&row.status, "status")?, + created_at: row.created_at, + }, + password_hash: row.password_hash, + }) + }) + .transpose() } pub async fn get_auth_user_by_id( &self, user_id: &UserId, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select id, email, display_name, - password_hash, + password_hash as \"password_hash!\", status, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\" from users where id = $1 limit 1", + user_id.as_str(), ) - .bind(user_id.as_str()) .fetch_optional(&self.pool) .await?; - row.as_ref().map(map_auth_user_record).transpose() + row.map(|row| { + Ok(AuthUserRecord { + user: User { + id: UserId::new(row.id), + email: row.email, + display_name: row.display_name, + status: deserialize_enum_text(&row.status, "status")?, + created_at: row.created_at, + }, + password_hash: row.password_hash, + }) + }) + .transpose() } pub async fn update_user_profile( @@ -204,7 +229,7 @@ impl PostgresRegistry { session_id: &UserSessionId, secret_hash: &str, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select s.id, s.user_id, @@ -212,7 +237,7 @@ impl PostgresRegistry { u.email, u.display_name, u.status, - to_char(u.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at + to_char(u.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\" from user_sessions s join users u on u.id = s.user_id where s.id = $1 @@ -220,9 +245,9 @@ impl PostgresRegistry { and s.status = 'active' and s.expires_at > now() limit 1", + session_id.as_str(), + secret_hash, ) - .bind(session_id.as_str()) - .bind(secret_hash) .fetch_optional(&self.pool) .await?; @@ -230,18 +255,16 @@ impl PostgresRegistry { return Ok(None); }; - let user_id = UserId::new(row.try_get::("user_id")?); + let user_id = UserId::new(row.user_id); let user = User { id: user_id.clone(), - email: row.try_get("email")?, - display_name: row.try_get("display_name")?, - status: deserialize_enum_text(&row.try_get::("status")?, "status")?, - created_at: row.try_get("created_at")?, + email: row.email, + display_name: row.display_name, + status: deserialize_enum_text(&row.status, "status")?, + created_at: row.created_at, }; let memberships = self.list_workspaces_for_user(&user_id).await?; - let stored_workspace_id = row - .try_get::, _>("current_workspace_id")? - .map(WorkspaceId::new); + let stored_workspace_id = row.current_workspace_id.map(WorkspaceId::new); let current_workspace_id = stored_workspace_id .filter(|workspace_id| { memberships @@ -255,7 +278,7 @@ impl PostgresRegistry { }); Ok(Some(SessionRecord { - session_id: UserSessionId::new(row.try_get::("id")?), + session_id: UserSessionId::new(row.id), user, memberships, current_workspace_id, diff --git a/crates/crank-registry/src/postgres/mod.rs b/crates/crank-registry/src/postgres/mod.rs index a0c53c2..eda2f59 100644 --- a/crates/crank-registry/src/postgres/mod.rs +++ b/crates/crank-registry/src/postgres/mod.rs @@ -530,13 +530,6 @@ fn map_membership_record(row: &PgRow) -> Result }) } -fn map_auth_user_record(row: &PgRow) -> Result { - Ok(AuthUserRecord { - user: map_user(row)?, - password_hash: row.try_get("password_hash")?, - }) -} - fn map_user(row: &PgRow) -> Result { Ok(User { id: UserId::new(row.try_get::("id")?), @@ -593,40 +586,6 @@ fn map_platform_api_key_record(row: &PgRow) -> Result Result { - Ok(SecretRecord { - secret: Secret { - id: SecretId::new(row.try_get::("id")?), - workspace_id: WorkspaceId::new(row.try_get::("workspace_id")?), - name: row.try_get("name")?, - kind: deserialize_enum_text(&row.try_get::("kind")?, "kind")?, - status: deserialize_enum_text(&row.try_get::("status")?, "status")?, - current_version: from_db_version( - row.try_get::("current_version")?, - "current_version", - )?, - created_at: row.try_get("created_at")?, - updated_at: row.try_get("updated_at")?, - last_used_at: row.try_get("last_used_at")?, - }, - }) -} - -fn map_secret_version_record(row: &PgRow) -> Result { - Ok(SecretVersionRecord { - secret_version: SecretVersion { - secret_id: SecretId::new(row.try_get::("secret_id")?), - version: from_db_version(row.try_get::("version")?, "version")?, - ciphertext: row.try_get("ciphertext")?, - key_version: row.try_get("key_version")?, - created_at: row.try_get("created_at")?, - created_by: row - .try_get::, _>("created_by")? - .map(UserId::new), - }, - }) -} - fn map_stream_session(row: &PgRow) -> Result { Ok(StreamSession { id: StreamSessionId::new(row.try_get::("id")?), diff --git a/crates/crank-registry/src/postgres/secret.rs b/crates/crank-registry/src/postgres/secret.rs index 582f0ee..2aac7a6 100644 --- a/crates/crank-registry/src/postgres/secret.rs +++ b/crates/crank-registry/src/postgres/secret.rs @@ -5,7 +5,7 @@ impl PostgresRegistry { &self, workspace_id: &WorkspaceId, ) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select id, workspace_id, @@ -13,18 +13,34 @@ impl PostgresRegistry { kind, status, current_version, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, - to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at, + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", + to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\", to_char(last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at from secrets where workspace_id = $1 order by name asc", + workspace_id.as_str(), ) - .bind(workspace_id.as_str()) .fetch_all(&self.pool) .await?; - rows.iter().map(map_secret_record).collect() + rows.into_iter() + .map(|row| { + Ok(SecretRecord { + secret: Secret { + id: SecretId::new(row.id), + workspace_id: WorkspaceId::new(row.workspace_id), + name: row.name, + kind: deserialize_enum_text(&row.kind, "kind")?, + status: deserialize_enum_text(&row.status, "status")?, + current_version: from_db_version(row.current_version, "current_version")?, + created_at: row.created_at, + updated_at: row.updated_at, + last_used_at: row.last_used_at, + }, + }) + }) + .collect() } pub async fn get_secret( @@ -32,7 +48,7 @@ impl PostgresRegistry { workspace_id: &WorkspaceId, secret_id: &SecretId, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select id, workspace_id, @@ -40,18 +56,33 @@ impl PostgresRegistry { kind, status, current_version, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, - to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at, + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", + to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\", to_char(last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at from secrets where workspace_id = $1 and id = $2", + workspace_id.as_str(), + secret_id.as_str(), ) - .bind(workspace_id.as_str()) - .bind(secret_id.as_str()) .fetch_optional(&self.pool) .await?; - row.as_ref().map(map_secret_record).transpose() + row.map(|row| { + Ok(SecretRecord { + secret: Secret { + id: SecretId::new(row.id), + workspace_id: WorkspaceId::new(row.workspace_id), + name: row.name, + kind: deserialize_enum_text(&row.kind, "kind")?, + status: deserialize_enum_text(&row.status, "status")?, + current_version: from_db_version(row.current_version, "current_version")?, + created_at: row.created_at, + updated_at: row.updated_at, + last_used_at: row.last_used_at, + }, + }) + }) + .transpose() } pub async fn get_current_secret_version( @@ -59,25 +90,37 @@ impl PostgresRegistry { workspace_id: &WorkspaceId, secret_id: &SecretId, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select sv.secret_id, sv.version, sv.ciphertext, sv.key_version, - to_char(sv.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, + to_char(sv.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", sv.created_by from secrets s join secret_versions sv on sv.secret_id = s.id and sv.version = s.current_version where s.workspace_id = $1 and s.id = $2", + workspace_id.as_str(), + secret_id.as_str(), ) - .bind(workspace_id.as_str()) - .bind(secret_id.as_str()) .fetch_optional(&self.pool) .await?; - row.as_ref().map(map_secret_version_record).transpose() + row.map(|row| { + Ok(SecretVersionRecord { + secret_version: SecretVersion { + secret_id: SecretId::new(row.secret_id), + version: from_db_version(row.version, "version")?, + ciphertext: row.ciphertext, + key_version: row.key_version, + created_at: row.created_at, + created_by: row.created_by.map(UserId::new), + }, + }) + }) + .transpose() } pub async fn create_secret( diff --git a/justfile b/justfile index b69fb3c..0b30b9c 100644 --- a/justfile +++ b/justfile @@ -13,6 +13,12 @@ clippy: test: cargo test --workspace --all-targets +sqlx-prepare database_url: + DATABASE_URL={{database_url}} cargo sqlx prepare --workspace -- --all-targets + +sqlx-check: + SQLX_OFFLINE=true cargo check --workspace + verify: just fmt-check just clippy