use anyhow::Result; use at_crypto::jwt::JwtClaims; use at_crypto::ecdsa::P256Keypair; use at_shared::config::AppConfig; pub fn server_p256_keypair(cfg: &AppConfig) -> Result { use p256::elliptic_curve::sec1::ToEncodedPoint; let raw = hex::decode(cfg.pds_jwt_secret.trim_start_matches("0x"))?; if raw.len() < 32 { anyhow::bail!("PDS_JWT_SECRET must be ≥ 32 bytes for P-256 key"); } let mut bytes = [0u8; 32]; bytes.copy_from_slice(&raw[..32]); let sk = p256::SecretKey::from_bytes((&bytes).into()) .map_err(|e| anyhow::anyhow!("p256 sk: {e}"))?; let vk = sk.public_key(); let pt = vk.to_encoded_point(false); let mut mb_raw = vec![0x80u8, 0x12u8]; mb_raw.extend_from_slice(pt.x().unwrap()); mb_raw.extend_from_slice(pt.y().unwrap()); let secret_hex = hex::encode(sk.to_bytes()); let public_multibase = at_crypto::multibase_util::encode_b58btc(&mb_raw); Ok(P256Keypair { secret_hex, public_multibase, }) } pub fn server_p256_public_multibase(cfg: &AppConfig) -> Result { Ok(server_p256_keypair(cfg)?.public_multibase) } pub fn issue_access_jwt( cfg: &AppConfig, did: &str, _handle: &str, ) -> Result<(String, i64)> { let kp = server_p256_keypair(cfg)?; let now = chrono::Utc::now().timestamp(); let exp = now + 3600; let claims = JwtClaims { // Same derivation as `describeServer` and `/.well-known/did.json` // (`AppConfig::pds_did`), so a verifier can take `iss`, resolve // the did:web document and arrive at the key this token is // signed with. The previous inline version dropped the // percent-encoding of the port, producing an `iss` that no // did:web resolver could follow. iss: cfg.pds_did(), sub: did.to_string(), // The AppView this token is meant for. Derived from // `APPVIEW_PUBLIC_URL` rather than hardcoded, so the AppView can // check it against its own identity (`AppConfig::appview_did`). aud: cfg.appview_did(), iat: now, exp, jti: Some(uuid::Uuid::new_v4().to_string()), scope: Some("com.atproto.access".into()), }; let token = at_crypto::jwt::issue_jwt(&kp, &claims)?; Ok((token, exp)) } pub fn issue_refresh_jwt( cfg: &AppConfig, did: &str, ) -> Result<(String, i64)> { let kp = server_p256_keypair(cfg)?; let now = chrono::Utc::now().timestamp(); let exp = now + 90 * 24 * 3600; let claims = JwtClaims { iss: "did:web:refresh.maarcadetweet.local".into(), sub: did.to_string(), aud: "did:web:refresh.maarcadetweet.local".into(), iat: now, exp, jti: Some(uuid::Uuid::new_v4().to_string()), scope: Some("com.atproto.refresh".into()), }; let token = at_crypto::jwt::issue_jwt(&kp, &claims)?; Ok((token, exp)) }