(_server: &str, identity: &Identity)
| 81 | // --------------------------------------------------------------------------- |
| 82 | |
| 83 | fn mint_token(_server: &str, identity: &Identity) -> CliResult<String> { |
| 84 | // The token is keyed by the identity's own public key — no server-assigned |
| 85 | // identifier to look up. |
| 86 | let public_key_b32 = identity.public_key_base32(); |
| 87 | |
| 88 | // Load the keypair (needs the secret key to sign). |
| 89 | let store = IdentityStore::open_default() |
| 90 | .map_err(|e| CliError::Internal(anyhow::anyhow!("Failed to open identity store: {e}")))?; |
| 91 | let keypair = store.load_keypair(&identity.id, None).map_err(|e| { |
| 92 | CliError::Internal(anyhow::anyhow!( |
| 93 | "Failed to load keypair for '{}': {e}", |
| 94 | identity.name |
| 95 | )) |
| 96 | })?; |
| 97 | |
| 98 | let now = Utc::now(); |
| 99 | let claims = Claims { |
| 100 | sub: public_key_b32.clone(), |
| 101 | iat: now.timestamp(), |
| 102 | exp: (now + TOKEN_TTL).timestamp(), |
| 103 | jti: Uuid::new_v4().to_string(), |
| 104 | }; |
| 105 | |
| 106 | let header = JwtHeader { |
| 107 | alg: "EdDSA", |
| 108 | typ: "JWT", |
| 109 | kid: public_key_b32, |
| 110 | }; |
| 111 | |
| 112 | let header_json = serde_json::to_vec(&header) |
| 113 | .map_err(|e| CliError::Internal(anyhow::anyhow!("Failed to encode JWT header: {e}")))?; |
| 114 | let claims_json = serde_json::to_vec(&claims) |
| 115 | .map_err(|e| CliError::Internal(anyhow::anyhow!("Failed to encode JWT claims: {e}")))?; |
| 116 | |
| 117 | let header_b64 = BASE64URL_NOPAD.encode(&header_json); |
| 118 | let claims_b64 = BASE64URL_NOPAD.encode(&claims_json); |
| 119 | let signing_input = format!("{header_b64}.{claims_b64}"); |
| 120 | |
| 121 | let signature = keypair.sign(signing_input.as_bytes()); |
| 122 | let sig_b64 = BASE64URL_NOPAD.encode(&signature); |
| 123 | |
| 124 | log::debug!("Minted self-signed EdDSA JWT for '{}'", identity.name); |
| 125 | Ok(format!("{signing_input}.{sig_b64}")) |
| 126 | } |
| 127 | |
| 128 | #[cfg(test)] |
| 129 | mod tests { |
no test coverage detected