mod support; use axum::{ body::Body, http::{Request, header::CONTENT_TYPE}, }; use cpds::oauth::challenge_for_verifier; use support::{TestApp, VERIFIER, form, json}; use url::Url; #[tokio::test] async fn authorization_code_flow_issues_a_working_bearer_token() { let app = TestApp::new(); let challenge = challenge_for_verifier(VERIFIER).unwrap(); let code = authorize(&app, &challenge).await; let wrong_verifier_body = form(&[ ("grant_type", "authorization_code"), ("code", &code), ("client_id", "http://client.test/metadata.json"), ("redirect_uri", "http://client.test/callback"), ( "code_verifier", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ), ]); let rejected = app .send( Request::post("/oauth/token") .header(CONTENT_TYPE, "application/x-www-form-urlencoded") .body(Body::from(wrong_verifier_body)) .unwrap(), ) .await; assert_eq!(rejected.status(), 403); let token_body = form(&[ ("grant_type", "authorization_code"), ("code", &code), ("client_id", "http://client.test/metadata.json"), ("redirect_uri", "http://client.test/callback"), ("code_verifier", VERIFIER), ]); let token_response = app .send( Request::post("/oauth/token") .header(CONTENT_TYPE, "application/x-www-form-urlencoded") .body(Body::from(token_body.clone())) .unwrap(), ) .await; assert_eq!(token_response.status(), 200); let token = json(token_response).await; assert_eq!(token["token_type"], "Bearer"); assert_eq!(token["sub"], "did:web:alice.test"); let session = app .send( Request::get("/xrpc/com.atproto.server.getSession") .header( "authorization", format!("Bearer {}", token["access_token"].as_str().unwrap()), ) .body(Body::empty()) .unwrap(), ) .await; assert_eq!(session.status(), 200); assert_eq!(json(session).await["handle"], "alice.test"); let replay = app .send( Request::post("/oauth/token") .header(CONTENT_TYPE, "application/x-www-form-urlencoded") .body(Body::from(token_body)) .unwrap(), ) .await; assert_eq!(replay.status(), 403); } async fn authorize(app: &TestApp, challenge: &str) -> String { let authorize_query = form(&[ ("response_type", "code"), ("client_id", "http://client.test/metadata.json"), ("redirect_uri", "http://client.test/callback"), ("state", "remember-me"), ("scope", "atproto"), ("code_challenge", challenge), ("code_challenge_method", "S256"), ]); let page = app .send( Request::get(format!("/oauth/authorize?{authorize_query}")) .body(Body::empty()) .unwrap(), ) .await; assert_eq!(page.status(), 200); let authorize_body = form(&[ ("client_id", "http://client.test/metadata.json"), ("redirect_uri", "http://client.test/callback"), ("state", "remember-me"), ("scope", "atproto"), ("code_challenge", challenge), ("identifier", "alice.test"), ("password", "correct horse battery staple"), ]); let approved = app .send( Request::post("/oauth/authorize") .header(CONTENT_TYPE, "application/x-www-form-urlencoded") .body(Body::from(authorize_body)) .unwrap(), ) .await; assert_eq!(approved.status(), 303); let redirect = Url::parse(approved.headers()["location"].to_str().unwrap()).unwrap(); assert_eq!( redirect .query_pairs() .find(|(name, _)| name == "state") .unwrap() .1, "remember-me" ); redirect .query_pairs() .find(|(name, _)| name == "code") .unwrap() .1 .into_owned() }