From 471748ec903789095279436ebbc41c77c5785411 Mon Sep 17 00:00:00 2001 From: bodymindarts Date: Fri, 12 Jan 2024 19:56:09 +0100 Subject: [PATCH] perf(api-keys): move from encrypted to hashed api key --- ...22456b1025b26b1d3ee5fcc4b51b16f59c4f.json} | 4 +-- ...a94dc74d493856497ed2e4f6cd140d51eab8.json} | 4 +-- ...cc4975977cf0220c7fad78e53c8ea6c08afce.json | 34 ++++++++++++++++++ ...22_add_hashed_key_to_identity_api_keys.sql | 2 ++ core/api-keys/src/identity/mod.rs | 35 ++++++++++++++++--- 5 files changed, 71 insertions(+), 8 deletions(-) rename core/api-keys/.sqlx/{query-dd4b4d230f88e9bdd2829a6a79f5c3e0d47b8795823a8676a7588e4ed9d03108.json => query-4e3129274ff721a1ee88f2ed169122456b1025b26b1d3ee5fcc4b51b16f59c4f.json} (68%) rename core/api-keys/.sqlx/{query-d0f9efb8a6ab2c7b36cee50f664b0dec28654d37c9812ddd7a6245cf26cf64ec.json => query-5057154185b03f6ae8e66a1dab81a94dc74d493856497ed2e4f6cd140d51eab8.json} (59%) create mode 100644 core/api-keys/.sqlx/query-89a4ce6e68b5a8ee259effe93c1cc4975977cf0220c7fad78e53c8ea6c08afce.json create mode 100644 core/api-keys/migrations/20240112183422_add_hashed_key_to_identity_api_keys.sql diff --git a/core/api-keys/.sqlx/query-dd4b4d230f88e9bdd2829a6a79f5c3e0d47b8795823a8676a7588e4ed9d03108.json b/core/api-keys/.sqlx/query-4e3129274ff721a1ee88f2ed169122456b1025b26b1d3ee5fcc4b51b16f59c4f.json similarity index 68% rename from core/api-keys/.sqlx/query-dd4b4d230f88e9bdd2829a6a79f5c3e0d47b8795823a8676a7588e4ed9d03108.json rename to core/api-keys/.sqlx/query-4e3129274ff721a1ee88f2ed169122456b1025b26b1d3ee5fcc4b51b16f59c4f.json index d0b950232a..c8fb530e5a 100644 --- a/core/api-keys/.sqlx/query-dd4b4d230f88e9bdd2829a6a79f5c3e0d47b8795823a8676a7588e4ed9d03108.json +++ b/core/api-keys/.sqlx/query-4e3129274ff721a1ee88f2ed169122456b1025b26b1d3ee5fcc4b51b16f59c4f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "WITH updated_key AS (\n UPDATE identity_api_keys k\n SET last_used_at = NOW()\n FROM identities i\n WHERE k.identity_id = i.id\n AND k.revoked = false\n AND k.encrypted_key = crypt($1, k.encrypted_key)\n AND (k.expires_at > NOW() OR k.expires_at IS NULL)\n RETURNING k.id, i.subject_id, k.read_only\n )\n SELECT id, subject_id, read_only FROM updated_key", + "query": "WITH updated_key AS (\n UPDATE identity_api_keys k\n SET last_used_at = NOW()\n FROM identities i\n WHERE k.identity_id = i.id\n AND k.revoked = false\n AND k.hashed_key = digest($1, 'sha256')\n AND (k.expires_at > NOW() OR k.expires_at IS NULL)\n RETURNING k.id, i.subject_id, k.read_only\n )\n SELECT id, subject_id, read_only FROM updated_key", "describe": { "columns": [ { @@ -30,5 +30,5 @@ false ] }, - "hash": "dd4b4d230f88e9bdd2829a6a79f5c3e0d47b8795823a8676a7588e4ed9d03108" + "hash": "4e3129274ff721a1ee88f2ed169122456b1025b26b1d3ee5fcc4b51b16f59c4f" } diff --git a/core/api-keys/.sqlx/query-d0f9efb8a6ab2c7b36cee50f664b0dec28654d37c9812ddd7a6245cf26cf64ec.json b/core/api-keys/.sqlx/query-5057154185b03f6ae8e66a1dab81a94dc74d493856497ed2e4f6cd140d51eab8.json similarity index 59% rename from core/api-keys/.sqlx/query-d0f9efb8a6ab2c7b36cee50f664b0dec28654d37c9812ddd7a6245cf26cf64ec.json rename to core/api-keys/.sqlx/query-5057154185b03f6ae8e66a1dab81a94dc74d493856497ed2e4f6cd140d51eab8.json index 955fc96b79..1819e993b1 100644 --- a/core/api-keys/.sqlx/query-d0f9efb8a6ab2c7b36cee50f664b0dec28654d37c9812ddd7a6245cf26cf64ec.json +++ b/core/api-keys/.sqlx/query-5057154185b03f6ae8e66a1dab81a94dc74d493856497ed2e4f6cd140d51eab8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO identity_api_keys (encrypted_key, identity_id, name, expires_at, read_only)\n VALUES (crypt($1, gen_salt('bf')), $2, $3, $4, $5) RETURNING id, created_at", + "query": "INSERT INTO identity_api_keys (hashed_key, encrypted_key, identity_id, name, expires_at, read_only)\n VALUES (digest($1, 'sha256'), crypt($1, gen_salt('bf')), $2, $3, $4, $5)\n RETURNING id, created_at", "describe": { "columns": [ { @@ -28,5 +28,5 @@ false ] }, - "hash": "d0f9efb8a6ab2c7b36cee50f664b0dec28654d37c9812ddd7a6245cf26cf64ec" + "hash": "5057154185b03f6ae8e66a1dab81a94dc74d493856497ed2e4f6cd140d51eab8" } diff --git a/core/api-keys/.sqlx/query-89a4ce6e68b5a8ee259effe93c1cc4975977cf0220c7fad78e53c8ea6c08afce.json b/core/api-keys/.sqlx/query-89a4ce6e68b5a8ee259effe93c1cc4975977cf0220c7fad78e53c8ea6c08afce.json new file mode 100644 index 0000000000..848dc73c39 --- /dev/null +++ b/core/api-keys/.sqlx/query-89a4ce6e68b5a8ee259effe93c1cc4975977cf0220c7fad78e53c8ea6c08afce.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH updated_key AS (\n UPDATE identity_api_keys k\n SET last_used_at = NOW(), hashed_key = digest($1, 'sha256')\n FROM identities i\n WHERE k.identity_id = i.id\n AND k.revoked = false\n AND k.encrypted_key = crypt($1, k.encrypted_key)\n AND (k.expires_at > NOW() OR k.expires_at IS NULL)\n RETURNING k.id, i.subject_id, k.read_only\n )\n SELECT id, subject_id, read_only FROM updated_key", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "subject_id", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "read_only", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "89a4ce6e68b5a8ee259effe93c1cc4975977cf0220c7fad78e53c8ea6c08afce" +} diff --git a/core/api-keys/migrations/20240112183422_add_hashed_key_to_identity_api_keys.sql b/core/api-keys/migrations/20240112183422_add_hashed_key_to_identity_api_keys.sql new file mode 100644 index 0000000000..f548146fa4 --- /dev/null +++ b/core/api-keys/migrations/20240112183422_add_hashed_key_to_identity_api_keys.sql @@ -0,0 +1,2 @@ +ALTER TABLE identity_api_keys +ADD COLUMN hashed_key BYTEA UNIQUE DEFAULT gen_random_bytes(256); diff --git a/core/api-keys/src/identity/mod.rs b/core/api-keys/src/identity/mod.rs index 25863ceec7..d3e871f8eb 100644 --- a/core/api-keys/src/identity/mod.rs +++ b/core/api-keys/src/identity/mod.rs @@ -67,8 +67,9 @@ impl Identities { ) -> Result<(IdentityApiKey, ApiKeySecret), IdentityError> { let code = Alphanumeric.sample_string(&mut rand::thread_rng(), 64); let record = sqlx::query!( - r#"INSERT INTO identity_api_keys (encrypted_key, identity_id, name, expires_at, read_only) - VALUES (crypt($1, gen_salt('bf')), $2, $3, $4, $5) RETURNING id, created_at"#, + r#"INSERT INTO identity_api_keys (hashed_key, encrypted_key, identity_id, name, expires_at, read_only) + VALUES (digest($1, 'sha256'), crypt($1, gen_salt('bf')), $2, $3, $4, $5) + RETURNING id, created_at"#, code, identity_id as IdentityId, name, @@ -111,7 +112,7 @@ impl Identities { FROM identities i WHERE k.identity_id = i.id AND k.revoked = false - AND k.encrypted_key = crypt($1, k.encrypted_key) + AND k.hashed_key = digest($1, 'sha256') AND (k.expires_at > NOW() OR k.expires_at IS NULL) RETURNING k.id, i.subject_id, k.read_only ) @@ -128,7 +129,33 @@ impl Identities { record.read_only, )) } else { - Err(IdentityError::NoActiveKeyFound) + // look up by encrypted key and set hashed_key + let record = sqlx::query!( + r#"WITH updated_key AS ( + UPDATE identity_api_keys k + SET last_used_at = NOW(), hashed_key = digest($1, 'sha256') + FROM identities i + WHERE k.identity_id = i.id + AND k.revoked = false + AND k.encrypted_key = crypt($1, k.encrypted_key) + AND (k.expires_at > NOW() OR k.expires_at IS NULL) + RETURNING k.id, i.subject_id, k.read_only + ) + SELECT id, subject_id, read_only FROM updated_key"#, + code + ) + .fetch_optional(&self.pool) + .await?; + + if let Some(record) = record { + Ok(( + IdentityApiKeyId::from(record.id), + record.subject_id, + record.read_only, + )) + } else { + Err(IdentityError::NoActiveKeyFound) + } } }