//! Deployment smoke against a REAL Clerk instance (CM_LIVE_CLERK=1 + //! CLERK_SECRET_KEY + CLERK_PUBLISHABLE_KEY): discovery and JWKS come //! from Clerk's live infrastructure, the session JWT is minted by //! Clerk's Backend API for a real user, and `AuthService::authenticate` //! verifies it and JIT-provisions — the exact production path. use std::sync::Arc; use base64::engine::general_purpose::STANDARD; use base64::Engine; use cm_auth::{AuthService, JwtVerifier}; use cm_domain::{Role, Workspace, WorkspaceId}; use serde_json::{json, Value}; const BAPI: &str = "https://api.clerk.com/v1"; fn live() -> Option<(String, String)> { if std::env::var("CM_LIVE_CLERK").as_deref() != Ok("1") { eprintln!("skipped: set CM_LIVE_CLERK=1 to run"); return None; } let (Ok(secret), Ok(publishable)) = ( std::env::var("CLERK_SECRET_KEY"), std::env::var("CLERK_PUBLISHABLE_KEY"), ) else { eprintln!("skipped: CLERK_SECRET_KEY / CLERK_PUBLISHABLE_KEY not set"); return None; }; // The publishable key embeds the instance domain: // pk_test_.clerk.accounts.dev$")>. let encoded = publishable .trim_start_matches("pk_test_") .trim_start_matches("pk_live_"); let domain = STANDARD .decode(encoded) .ok() .and_then(|bytes| String::from_utf8(bytes).ok()) .map(|s| s.trim_end_matches('$').to_owned()) .expect("publishable key decodes to the instance domain"); Some((secret, format!("https://{domain}"))) } #[tokio::test] async fn a_real_clerk_session_token_authenticates_and_provisions() { let Some((secret, issuer)) = live() else { return; }; let pool = cm_testkit::test_pool().await; let ws = Workspace { id: WorkspaceId::new(), name: "Acme".into(), plan: "team".into(), }; cm_db::repo::workspaces::insert(&pool, &ws).await.unwrap(); // Real discovery + JWKS from the live instance. let verifier = JwtVerifier::discover(&issuer) .await .expect("clerk discovery"); let auth = AuthService::new(pool.clone()).with_verifier(Arc::new(verifier)); // Mint a REAL session token via Clerk's Backend API. let client = reqwest::Client::new(); let email = format!("smoke+{}@clawmates.work", uuid::Uuid::now_v7().simple()); let user: Value = client .post(format!("{BAPI}/users")) .bearer_auth(&secret) .json(&json!({ "email_address": [email], "skip_password_requirement": true, "first_name": "Smoke", })) .send() .await .unwrap() .json() .await .unwrap(); let user_id = user["id"].as_str().expect("user created").to_owned(); let session: Value = client .post(format!("{BAPI}/sessions")) .bearer_auth(&secret) .json(&json!({"user_id": user_id})) .send() .await .unwrap() .json() .await .unwrap(); let session_id = session["id"].as_str().expect("session created"); let token: Value = client .post(format!("{BAPI}/sessions/{session_id}/tokens")) .bearer_auth(&secret) .json(&json!({})) .send() .await .unwrap() .json() .await .unwrap(); let jwt = token["jwt"].as_str().expect("session jwt minted"); // THE smoke: the production verify path accepts Clerk's real JWT and // JIT-provisions the user (no custom claims configured on a fresh // instance, so email synthesizes and role defaults to Member). let authed = auth.authenticate(jwt).await.expect("real token verifies"); assert_eq!(authed.workspace_id, ws.id); assert_eq!(authed.role, Role::Member); let subject: String = sqlx::query_scalar("SELECT auth_subject FROM users WHERE id = $1") .bind(authed.user_id.as_uuid()) .fetch_one(&pool) .await .unwrap(); assert_eq!(subject, user_id); // Same token again: same user, no duplicate. let again = auth.authenticate(jwt).await.unwrap(); assert_eq!(again.user_id, authed.user_id); // A tampered token is refused even with the real JWKS. let mut forged = jwt.to_owned(); forged.replace_range(forged.len() - 4.., "AAAA"); assert!(auth.authenticate(&forged).await.is_err()); // Clean the instance up. client .delete(format!("{BAPI}/users/{user_id}")) .bearer_auth(&secret) .send() .await .unwrap(); }