Lines
98.8 %
Functions
100 %
Branches
//! Slice B: per-user JWT signing keys, end to end against real Postgres.
//!
//! Verifies the security-critical properties of the cutover: a user's token
//! signed with their own private key verifies against their public key, and a
//! token is REJECTED when checked against a different user's key (forgery /
//! cross-user isolation). Mirrors the web layer's mint → verify path using the
//! same `server::auth_keys` lookups and PEM wire form.
//! Run via:
//! DATABASE_URL=postgres://… cargo test -p tests-integration --features db
#![cfg(feature = "db")]
use base64::{Engine as _, engine::general_purpose::STANDARD};
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
use serde::{Deserialize, Serialize};
use sqlx::postgres::PgPoolOptions;
#[derive(Serialize, Deserialize)]
struct Claims {
sub: String,
exp: i64,
}
fn decode_b64_pem(b64: &str) -> Vec<u8> {
STANDARD.decode(b64).expect("base64 pem")
fn sign(private_pem_b64: &str, sub: &str) -> String {
let pem = decode_b64_pem(private_pem_b64);
let claims = Claims {
sub: sub.to_string(),
exp: 9_999_999_999,
};
encode(
&Header::new(Algorithm::RS256),
&claims,
&EncodingKey::from_rsa_pem(&pem).expect("encoding key"),
)
.expect("sign")
fn verifies_against(public_pem_b64: &str, token: &str) -> bool {
let pem = decode_b64_pem(public_pem_b64);
let key = match DecodingKey::from_rsa_pem(&pem) {
Ok(k) => k,
Err(_) => return false,
decode::<Claims>(token, &key, &Validation::new(Algorithm::RS256)).is_ok()
// One `#[tokio::test]` so the process-static admin pool keeps a stable lifetime
// across scenarios (see the provisioning test for the rationale).
#[tokio::test]
async fn per_user_signing_keys_end_to_end() -> anyhow::Result<()> {
sign_verify_and_reject_cross_user().await?;
stored_key_round_trips_through_lookup().await?;
Ok(())
/// A token verifies against its own signer's public key and is REJECTED against
/// a different user's key (cross-user isolation / forgery guarantee).
async fn sign_verify_and_reject_cross_user() -> anyhow::Result<()> {
let user_a = server::auth_keys::generate().await?;
let user_b = server::auth_keys::generate().await?;
let token_a = sign(&user_a.private_pem_b64, "user-a");
assert!(
verifies_against(&user_a.public_pem_b64, &token_a),
"token must verify against its own signer's public key"
);
!verifies_against(&user_b.public_pem_b64, &token_a),
"token must NOT verify against a different user's public key"
/// Provision a real per-user DB, store a generated private key, read it back via
/// the same lookup the mint path uses, and prove a token it signs verifies
/// against the public half.
async fn stored_key_round_trips_through_lookup() -> anyhow::Result<()> {
let user_id = uuid::Uuid::new_v4();
let db_name = format!("nomi_user_{}", user_id.simple());
let keypair = server::auth_keys::generate().await?;
let dsn = server::provision::create_user_database(user_id).await?;
server::provision::store_user_private_key(&dsn, &keypair.private_pem_b64).await?;
// private_key_for routes through userpool, which resolves the per-user DB
// from the global users.db_name — so the user must exist in the directory.
let admin_url = std::env::var("DATABASE_URL")?;
let directory = PgPoolOptions::new()
.max_connections(1)
.connect(&admin_url)
.await?;
sqlx::query(
"INSERT INTO users (id, user_name, email, user_password, db_name, jwt_public_key) \
VALUES ($1, 'authkey-test', $2, 'x', $3, $4)",
.bind(user_id)
.bind(format!("authkey-{user_id}@example.test"))
.bind(&dsn)
.bind(&keypair.public_pem_b64)
.execute(&directory)
let fetched_private = server::auth_keys::private_key_for(user_id).await?;
assert_eq!(fetched_private, keypair.private_pem_b64);
let fetched_public = server::auth_keys::public_key_for(user_id).await?;
assert_eq!(
fetched_public.as_deref(),
Some(keypair.public_pem_b64.as_str())
let token = sign(&fetched_private, &user_id.to_string());
assert!(verifies_against(&keypair.public_pem_b64, &token));
sqlx::query("DELETE FROM users WHERE id = $1")
sqlx::query(&format!(
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity \
WHERE datname = '{db_name}' AND pid <> pg_backend_pid()"
))
.await
.ok();
sqlx::query(&format!("DROP DATABASE IF EXISTS \"{db_name}\""))
directory.close().await;