From f2dbf6d20d76d25cfa499860366d1ed0360c9205 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Wed, 19 Nov 2025 00:03:35 +0100 Subject: [PATCH] count external jwts for telemetry * feat: count external jwts * nits --- ...9468e01c4bee4cd755322eb2a83353a279a2b.json | 14 +++++++++++++ ...02c8adee995eac05b5051e480f0a20fa6b7bb.json | 20 +++++++++++++++++++ backend/ee-repo-ref.txt | 2 +- .../20251118190643_unique_jwt_token.down.sql | 3 +++ .../20251118190643_unique_jwt_token.up.sql | 9 +++++++++ backend/windmill-api/src/auth.rs | 1 + backend/windmill-api/src/ee_oss.rs | 1 + backend/windmill-common/src/auth.rs | 15 ++++++++++++++ backend/windmill-common/src/utils.rs | 6 ++++++ 9 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 backend/.sqlx/query-778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b.json create mode 100644 backend/.sqlx/query-d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb.json create mode 100644 backend/migrations/20251118190643_unique_jwt_token.down.sql create mode 100644 backend/migrations/20251118190643_unique_jwt_token.up.sql diff --git a/backend/.sqlx/query-778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b.json b/backend/.sqlx/query-778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b.json new file mode 100644 index 0000000000..a35e6560e3 --- /dev/null +++ b/backend/.sqlx/query-778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO unique_ext_jwt_token (jwt_hash, last_used_at)\n VALUES ($1, NOW())\n ON CONFLICT (jwt_hash)\n DO UPDATE SET last_used_at = NOW()", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [] + }, + "hash": "778ab8ceb2a84978919ceb07f399468e01c4bee4cd755322eb2a83353a279a2b" +} diff --git a/backend/.sqlx/query-d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb.json b/backend/.sqlx/query-d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb.json new file mode 100644 index 0000000000..fda5b760b3 --- /dev/null +++ b/backend/.sqlx/query-d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM unique_ext_jwt_token WHERE last_used_at > NOW() - INTERVAL '30 days'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "d2732640f09ec029025ebdd4de502c8adee995eac05b5051e480f0a20fa6b7bb" +} diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index f885c61364..7dd5e2c77a 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -785ec89a5fb08d62b5ef59d1860089ed1266ae8b \ No newline at end of file +6694dfbc62ff69570743f028aa19c543ae846e6e \ No newline at end of file diff --git a/backend/migrations/20251118190643_unique_jwt_token.down.sql b/backend/migrations/20251118190643_unique_jwt_token.down.sql new file mode 100644 index 0000000000..8e1dca2f24 --- /dev/null +++ b/backend/migrations/20251118190643_unique_jwt_token.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here + +DROP TABLE IF EXISTS unique_ext_jwt_token; diff --git a/backend/migrations/20251118190643_unique_jwt_token.up.sql b/backend/migrations/20251118190643_unique_jwt_token.up.sql new file mode 100644 index 0000000000..bf7613c35d --- /dev/null +++ b/backend/migrations/20251118190643_unique_jwt_token.up.sql @@ -0,0 +1,9 @@ +-- Add up migration script here + +CREATE TABLE IF NOT EXISTS unique_ext_jwt_token ( + jwt_hash BIGINT PRIMARY KEY NOT NULL, + last_used_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_unique_ext_jwt_token_last_used_at ON unique_ext_jwt_token(last_used_at); + diff --git a/backend/windmill-api/src/auth.rs b/backend/windmill-api/src/auth.rs index 0765f701f9..7f2ccc6876 100644 --- a/backend/windmill-api/src/auth.rs +++ b/backend/windmill-api/src/auth.rs @@ -90,6 +90,7 @@ impl AuthCache { w_id.as_ref(), token.trim_start_matches("jwt_ext_"), self.ext_jwks.clone(), + &self.db, ) .await { diff --git a/backend/windmill-api/src/ee_oss.rs b/backend/windmill-api/src/ee_oss.rs index 30756af8ab..24eea5ab85 100644 --- a/backend/windmill-api/src/ee_oss.rs +++ b/backend/windmill-api/src/ee_oss.rs @@ -26,6 +26,7 @@ pub async fn jwt_ext_auth( _w_id: Option<&String>, _token: &str, _external_jwks: Option>>, + _db: &crate::db::DB, ) -> anyhow::Result<(crate::db::ApiAuthed, usize)> { // Implementation is not open source diff --git a/backend/windmill-common/src/auth.rs b/backend/windmill-common/src/auth.rs index e48cf3728e..89d4831e38 100644 --- a/backend/windmill-common/src/auth.rs +++ b/backend/windmill-common/src/auth.rs @@ -158,6 +158,21 @@ impl JWTAuthClaims { .as_ref() .is_some_and(|token_w_ids| token_w_ids.iter().any(|token_w_id| w_id == token_w_id)) } + + pub fn compute_ext_jwt_hash(&self) -> i64 { + let mut hasher = DefaultHasher::new(); + self.email.hash(&mut hasher); + self.username.hash(&mut hasher); + self.is_admin.hash(&mut hasher); + self.is_operator.hash(&mut hasher); + self.groups.hash(&mut hasher); + self.folders.hash(&mut hasher); + self.workspace_id.hash(&mut hasher); + self.workspace_ids.hash(&mut hasher); + self.label.hash(&mut hasher); + self.scopes.hash(&mut hasher); + hasher.finish() as i64 + } } #[derive(Deserialize, Debug)] diff --git a/backend/windmill-common/src/utils.rs b/backend/windmill-common/src/utils.rs index 174d3cd261..623486d19c 100644 --- a/backend/windmill-common/src/utils.rs +++ b/backend/windmill-common/src/utils.rs @@ -947,3 +947,9 @@ pub struct ExpiringCacheEntry { pub value: T, pub expiry: std::time::Instant, } + +impl ExpiringCacheEntry { + pub fn is_expired(&self) -> bool { + self.expiry < std::time::Instant::now() + } +}