#![feature(proc_macro_hygiene, decl_macro)] extern crate oxide_auth; extern crate oxide_auth_rocket; #[macro_use] extern crate rocket; #[rustfmt::skip] #[path = "../../examples/support/rocket.rs"] mod support; use std::io; use std::sync::Mutex; use oxide_auth::endpoint::{OwnerConsent, Solicitation}; use oxide_auth::frontends::simple::endpoint::{FnSolicitor, Generic, Vacant}; use oxide_auth::primitives::prelude::*; use oxide_auth::primitives::registrar::RegisteredUrl; use oxide_auth_rocket::{OAuthResponse, OAuthRequest, OAuthFailure}; use rocket::{Data, State, Response, http}; use rocket::http::ContentType; use rocket::response::Responder; struct MyState { registrar: Mutex, authorizer: Mutex>, issuer: Mutex>, } #[get("/authorize")] fn authorize<'r>( oauth: OAuthRequest<'r>, state: State, ) -> Result, OAuthFailure> { state .endpoint() .with_solicitor(FnSolicitor(consent_form)) .authorization_flow() .execute(oauth) .map_err(|err| err.pack::()) } #[post("/authorize?")] fn authorize_consent<'r>( oauth: OAuthRequest<'r>, allow: Option, state: State, ) -> Result, OAuthFailure> { let allowed = allow.unwrap_or(false); state .endpoint() .with_solicitor(FnSolicitor(move |_: &mut _, grant: Solicitation<'_>| { consent_decision(allowed, grant) })) .authorization_flow() .execute(oauth) .map_err(|err| err.pack::()) } #[post("/token", data = "")] fn token<'r>( mut oauth: OAuthRequest<'r>, body: Data, state: State, ) -> Result, OAuthFailure> { oauth.add_body(body); state .endpoint() .access_token_flow() .execute(oauth) .map_err(|err| err.pack::()) } #[post("/refresh", data = "")] fn refresh<'r>( mut oauth: OAuthRequest<'r>, body: Data, state: State, ) -> Result, OAuthFailure> { oauth.add_body(body); state .endpoint() .refresh_flow() .execute(oauth) .map_err(|err| err.pack::()) } #[get("/")] fn protected_resource<'r>(oauth: OAuthRequest<'r>, state: State) -> impl Responder<'r> { const DENY_TEXT: &str = " This page should be accessed via an oauth token from the client in the example. Click here to begin the authorization process. "; let protect = state .endpoint() .with_scopes(vec!["default-scope".parse().unwrap()]) .resource_flow() .execute(oauth); match protect { Ok(_grant) => Ok("Hello, world"), Err(Ok(response)) => { let error: OAuthResponse = Response::build_from(response.into()) .header(ContentType::HTML) .sized_body(io::Cursor::new(DENY_TEXT)) .finalize() .into(); Err(Ok(error)) } Err(Err(err)) => Err(Err(err.pack::())), } } fn main() { rocket::ignite() .mount( "/", routes![authorize, authorize_consent, token, protected_resource, refresh,], ) // We only attach the test client here because there can only be one rocket. .attach(support::ClientFairing) .manage(MyState::preconfigured()) .launch(); } impl MyState { pub fn preconfigured() -> Self { MyState { registrar: Mutex::new( vec![Client::public( "LocalClient", RegisteredUrl::Semantic( "http://localhost:8000/clientside/endpoint".parse().unwrap(), ), "default-scope".parse().unwrap(), )] .into_iter() .collect(), ), // Authorization tokens are 16 byte random keys to a memory hash map. authorizer: Mutex::new(AuthMap::new(RandomGenerator::new(16))), // Bearer tokens are also random generated but 256-bit tokens, since they live longer // and this example is somewhat paranoid. // // We could also use a `TokenSigner::ephemeral` here to create signed tokens which can // be read and parsed by anyone, but not maliciously created. However, they can not be // revoked and thus don't offer even longer lived refresh tokens. issuer: Mutex::new(TokenMap::new(RandomGenerator::new(16))), } } pub fn endpoint(&self) -> Generic { Generic { registrar: self.registrar.lock().unwrap(), authorizer: self.authorizer.lock().unwrap(), issuer: self.issuer.lock().unwrap(), // Solicitor configured later. solicitor: Vacant, // Scope configured later. scopes: Vacant, // `rocket::Response` is `Default`, so we don't need more configuration. response: Vacant, } } } fn consent_form<'r>( _: &mut OAuthRequest<'r>, solicitation: Solicitation, ) -> OwnerConsent> { OwnerConsent::InProgress( Response::build() .status(http::Status::Ok) .header(http::ContentType::HTML) .sized_body(io::Cursor::new(support::consent_page_html( "/authorize", solicitation, ))) .finalize() .into(), ) } fn consent_decision<'r>(allowed: bool, _: Solicitation) -> OwnerConsent> { if allowed { OwnerConsent::Authorized("dummy user".into()) } else { OwnerConsent::Denied } }