mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-07 08:27:12 +00:00
test(auth): share cli test jwt helpers
This commit is contained in:
parent
891b7f90ae
commit
0b36ed985b
4 changed files with 135 additions and 119 deletions
|
|
@ -17,17 +17,16 @@ use fabro_client::{AuthEntry, AuthStore, ServerTarget, StoredSubject};
|
|||
use fabro_config::{Storage, envfile};
|
||||
use fabro_store::EventEnvelope;
|
||||
use fabro_test::{apply_test_isolation, expect_reqwest_json, isolated_storage_dir, test_context};
|
||||
use fabro_types::{IdpIdentity, RunAuthMethod};
|
||||
use hkdf::Hkdf;
|
||||
use jsonwebtoken::{Algorithm, EncodingKey, Header};
|
||||
use sha2::Sha256;
|
||||
|
||||
use super::support::{find_run_dir, output_stderr, output_stdout};
|
||||
use crate::support::{parse_event_envelopes, unique_run_id};
|
||||
use crate::support::{
|
||||
TEST_SESSION_SECRET, issue_test_github_jwt, parse_event_envelopes, unique_run_id,
|
||||
};
|
||||
|
||||
const COMMAND_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
const TEST_SESSION_SECRET: &str =
|
||||
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
const TEST_GITHUB_CLIENT_SECRET: &str = "github-client-secret";
|
||||
const WORKER_TOKEN_ISSUER: &str = "fabro-server-worker";
|
||||
const WORKER_TOKEN_SCOPE: &str = "run:worker";
|
||||
|
|
@ -164,24 +163,6 @@ struct WorkerTokenClaims {
|
|||
jti: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct TestJwtClaims {
|
||||
iss: String,
|
||||
aud: String,
|
||||
sub: String,
|
||||
exp: u64,
|
||||
iat: u64,
|
||||
jti: String,
|
||||
idp_issuer: String,
|
||||
idp_subject: String,
|
||||
login: String,
|
||||
name: String,
|
||||
email: String,
|
||||
avatar_url: String,
|
||||
user_url: String,
|
||||
auth_method: RunAuthMethod,
|
||||
}
|
||||
|
||||
fn reserve_port() -> u16 {
|
||||
std::net::TcpListener::bind("127.0.0.1:0")
|
||||
.unwrap()
|
||||
|
|
@ -190,48 +171,6 @@ fn reserve_port() -> u16 {
|
|||
.port()
|
||||
}
|
||||
|
||||
fn issue_test_github_jwt(issuer: &str) -> String {
|
||||
let key = derived_jwt_key();
|
||||
let identity = IdpIdentity::new("https://github.com", "12345").unwrap();
|
||||
let now = Utc::now();
|
||||
let claims = TestJwtClaims {
|
||||
iss: issuer.to_string(),
|
||||
aud: "fabro-cli".to_string(),
|
||||
sub: identity.subject().to_string(),
|
||||
exp: (now + ChronoDuration::minutes(10))
|
||||
.timestamp()
|
||||
.try_into()
|
||||
.expect("expiration time should be positive"),
|
||||
iat: now
|
||||
.timestamp()
|
||||
.try_into()
|
||||
.expect("issued-at time should be positive"),
|
||||
jti: format!("{:032x}", rand::random::<u128>()),
|
||||
idp_issuer: identity.issuer().to_string(),
|
||||
idp_subject: identity.subject().to_string(),
|
||||
login: "octocat".to_string(),
|
||||
name: "The Octocat".to_string(),
|
||||
email: "octocat@example.com".to_string(),
|
||||
avatar_url: "https://example.com/octocat.png".to_string(),
|
||||
user_url: "https://github.com/octocat".to_string(),
|
||||
auth_method: RunAuthMethod::Github,
|
||||
};
|
||||
jsonwebtoken::encode(
|
||||
&Header::new(Algorithm::HS256),
|
||||
&claims,
|
||||
&EncodingKey::from_secret(&key),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn derived_jwt_key() -> [u8; 32] {
|
||||
let hkdf = Hkdf::<Sha256>::new(None, TEST_SESSION_SECRET.as_bytes());
|
||||
let mut key = [0_u8; 32];
|
||||
hkdf.expand(b"fabro-jwt-hs256-v1", &mut key)
|
||||
.expect("jwt hkdf output should fit");
|
||||
key
|
||||
}
|
||||
|
||||
fn write_submitter_auth(home_dir: &Path, target: &str, access_token: &str) {
|
||||
let auth_store = AuthStore::new(home_dir.join(".fabro").join("auth.json"));
|
||||
let target = ServerTarget::http_url(target).unwrap();
|
||||
|
|
|
|||
|
|
@ -26,19 +26,16 @@ use fabro_server::server::{
|
|||
create_app_state_with_env_lookup_and_server_secret_env,
|
||||
};
|
||||
use fabro_test::{GitHubAppState, TestContext, apply_test_isolation};
|
||||
use fabro_types::RunAuthMethod;
|
||||
use hkdf::Hkdf;
|
||||
use jsonwebtoken::{Algorithm, EncodingKey, Header};
|
||||
use serde_json::Value;
|
||||
use sha2::Sha256;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::task::JoinHandle;
|
||||
use ulid::Ulid;
|
||||
|
||||
use super::auth_tokens::{
|
||||
TEST_SESSION_SECRET, TestGithubJwtSubject, issue_expired_test_github_jwt,
|
||||
};
|
||||
|
||||
const LOGIN_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const TEST_SESSION_SECRET: &str =
|
||||
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
pub(crate) const TEST_DEV_TOKEN: &str =
|
||||
"fabro_dev_abababababababababababababababababababababababababababababababab";
|
||||
|
||||
|
|
@ -325,22 +322,6 @@ async fn record_request(
|
|||
next.run(req).await
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct TestJwtClaims {
|
||||
iss: String,
|
||||
aud: String,
|
||||
sub: String,
|
||||
exp: u64,
|
||||
iat: u64,
|
||||
jti: String,
|
||||
idp_issuer: String,
|
||||
idp_subject: String,
|
||||
login: String,
|
||||
name: String,
|
||||
email: String,
|
||||
auth_method: RunAuthMethod,
|
||||
}
|
||||
|
||||
async fn bind_listener() -> (TcpListener, String) {
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
|
|
@ -479,43 +460,15 @@ fn auth_store_path(context: &TestContext) -> std::path::PathBuf {
|
|||
}
|
||||
|
||||
fn expired_access_token(issuer: &str, subject: &serde_json::Map<String, Value>) -> String {
|
||||
let key = derived_jwt_key();
|
||||
let now = Utc::now();
|
||||
let claims = TestJwtClaims {
|
||||
iss: issuer.to_string(),
|
||||
aud: "fabro-cli".to_string(),
|
||||
sub: subject_value(subject, "idp_subject"),
|
||||
exp: (now - ChronoDuration::minutes(10))
|
||||
.timestamp()
|
||||
.try_into()
|
||||
.expect("expired timestamp should be positive"),
|
||||
iat: (now - ChronoDuration::minutes(20))
|
||||
.timestamp()
|
||||
.try_into()
|
||||
.expect("issued-at timestamp should be positive"),
|
||||
jti: Ulid::new().to_string(),
|
||||
issue_expired_test_github_jwt(issuer, TestGithubJwtSubject {
|
||||
idp_issuer: subject_value(subject, "idp_issuer"),
|
||||
idp_subject: subject_value(subject, "idp_subject"),
|
||||
login: subject_value(subject, "login"),
|
||||
name: subject_value(subject, "name"),
|
||||
email: subject_value(subject, "email"),
|
||||
auth_method: RunAuthMethod::Github,
|
||||
};
|
||||
|
||||
jsonwebtoken::encode(
|
||||
&Header::new(Algorithm::HS256),
|
||||
&claims,
|
||||
&EncodingKey::from_secret(&key),
|
||||
)
|
||||
.expect("expired JWT should encode")
|
||||
}
|
||||
|
||||
fn derived_jwt_key() -> [u8; 32] {
|
||||
let hkdf = Hkdf::<Sha256>::new(None, TEST_SESSION_SECRET.as_bytes());
|
||||
let mut key = [0_u8; 32];
|
||||
hkdf.expand(b"fabro-jwt-hs256-v1", &mut key)
|
||||
.expect("HKDF should derive the fixed-size JWT key");
|
||||
key
|
||||
avatar_url: String::new(),
|
||||
user_url: String::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn read_stderr_and_capture_url(
|
||||
|
|
|
|||
122
lib/crates/fabro-cli/tests/it/support/auth_tokens.rs
Normal file
122
lib/crates/fabro-cli/tests/it/support/auth_tokens.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
use chrono::{Duration as ChronoDuration, Utc};
|
||||
use fabro_types::RunAuthMethod;
|
||||
use hkdf::Hkdf;
|
||||
use jsonwebtoken::{Algorithm, EncodingKey, Header};
|
||||
use sha2::Sha256;
|
||||
use ulid::Ulid;
|
||||
|
||||
pub(crate) const TEST_SESSION_SECRET: &str =
|
||||
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
|
||||
const JWT_AUDIENCE: &str = "fabro-cli";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TestGithubJwtSubject {
|
||||
pub(crate) idp_issuer: String,
|
||||
pub(crate) idp_subject: String,
|
||||
pub(crate) login: String,
|
||||
pub(crate) name: String,
|
||||
pub(crate) email: String,
|
||||
pub(crate) avatar_url: String,
|
||||
pub(crate) user_url: String,
|
||||
}
|
||||
|
||||
impl TestGithubJwtSubject {
|
||||
pub(crate) fn octocat() -> Self {
|
||||
Self {
|
||||
idp_issuer: "https://github.com".to_string(),
|
||||
idp_subject: "12345".to_string(),
|
||||
login: "octocat".to_string(),
|
||||
name: "The Octocat".to_string(),
|
||||
email: "octocat@example.com".to_string(),
|
||||
avatar_url: "https://example.com/octocat.png".to_string(),
|
||||
user_url: "https://github.com/octocat".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct TestJwtClaims {
|
||||
iss: String,
|
||||
aud: String,
|
||||
sub: String,
|
||||
exp: u64,
|
||||
iat: u64,
|
||||
jti: String,
|
||||
idp_issuer: String,
|
||||
idp_subject: String,
|
||||
login: String,
|
||||
name: String,
|
||||
email: String,
|
||||
avatar_url: String,
|
||||
user_url: String,
|
||||
auth_method: RunAuthMethod,
|
||||
}
|
||||
|
||||
pub(crate) fn issue_test_github_jwt(issuer: &str) -> String {
|
||||
let now = Utc::now();
|
||||
issue_github_jwt(
|
||||
issuer,
|
||||
TestGithubJwtSubject::octocat(),
|
||||
now,
|
||||
now + ChronoDuration::minutes(10),
|
||||
format!("{:032x}", rand::random::<u128>()),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn issue_expired_test_github_jwt(issuer: &str, subject: TestGithubJwtSubject) -> String {
|
||||
let now = Utc::now();
|
||||
issue_github_jwt(
|
||||
issuer,
|
||||
subject,
|
||||
now - ChronoDuration::minutes(20),
|
||||
now - ChronoDuration::minutes(10),
|
||||
Ulid::new().to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
fn issue_github_jwt(
|
||||
issuer: &str,
|
||||
subject: TestGithubJwtSubject,
|
||||
issued_at: chrono::DateTime<Utc>,
|
||||
expires_at: chrono::DateTime<Utc>,
|
||||
jti: String,
|
||||
) -> String {
|
||||
let key = derived_jwt_key();
|
||||
let claims = TestJwtClaims {
|
||||
iss: issuer.to_string(),
|
||||
aud: JWT_AUDIENCE.to_string(),
|
||||
sub: subject.idp_subject.clone(),
|
||||
exp: expires_at
|
||||
.timestamp()
|
||||
.try_into()
|
||||
.expect("expiration time should be positive"),
|
||||
iat: issued_at
|
||||
.timestamp()
|
||||
.try_into()
|
||||
.expect("issued-at time should be positive"),
|
||||
jti,
|
||||
idp_issuer: subject.idp_issuer,
|
||||
idp_subject: subject.idp_subject,
|
||||
login: subject.login,
|
||||
name: subject.name,
|
||||
email: subject.email,
|
||||
avatar_url: subject.avatar_url,
|
||||
user_url: subject.user_url,
|
||||
auth_method: RunAuthMethod::Github,
|
||||
};
|
||||
jsonwebtoken::encode(
|
||||
&Header::new(Algorithm::HS256),
|
||||
&claims,
|
||||
&EncodingKey::from_secret(&key),
|
||||
)
|
||||
.expect("test GitHub JWT should encode")
|
||||
}
|
||||
|
||||
fn derived_jwt_key() -> [u8; 32] {
|
||||
let hkdf = Hkdf::<Sha256>::new(None, TEST_SESSION_SECRET.as_bytes());
|
||||
let mut key = [0_u8; 32];
|
||||
hkdf.expand(b"fabro-jwt-hs256-v1", &mut key)
|
||||
.expect("HKDF should derive the fixed-size JWT key");
|
||||
key
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod auth_harness;
|
||||
mod auth_tokens;
|
||||
|
||||
use assert_cmd::Command;
|
||||
use fabro_store::EventEnvelope;
|
||||
|
|
@ -50,6 +51,7 @@ pub(crate) use auth_harness::{
|
|||
RealAuthHarness, TEST_DEV_TOKEN, complete_login_via_browser, expire_saved_access_token,
|
||||
no_redirect_browser_client, run_detached, saved_auth_entry,
|
||||
};
|
||||
pub(crate) use auth_tokens::{TEST_SESSION_SECRET, issue_test_github_jwt};
|
||||
pub(crate) use fabro_json_snapshot;
|
||||
|
||||
pub(crate) fn run_output_filters(context: &TestContext) -> Vec<(String, String)> {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue