actix_middleware_rfc7662/
lib.rs1use actix_web::{dev, FromRequest, HttpRequest};
43use futures_util::future::LocalBoxFuture;
44use oauth2::basic::BasicErrorResponseType;
45use oauth2::url::Url;
46use oauth2::{
47 reqwest, AccessToken, AuthUrl, ClientId, ClientSecret, IntrospectionUrl, StandardErrorResponse,
48 StandardRevocableToken, StandardTokenResponse, TokenIntrospectionResponse,
49};
50use std::future::ready;
51use std::marker::PhantomData;
52use std::sync::Arc;
53
54pub use oauth2::{
56 basic::BasicTokenType, EmptyExtraTokenFields as StandardToken, ExtraTokenFields,
57 StandardTokenIntrospectionResponse,
58};
59
60mod error;
61
62#[cfg(feature = "indieauth")]
63pub mod indieauth;
64
65pub use error::Error;
66
67const BEARER_TOKEN_PREFIX: &str = "Bearer ";
68
69pub type IntrospectionResponse<T> = StandardTokenIntrospectionResponse<T, BasicTokenType>;
70
71pub trait AuthorizationRequirements<T>
72where
73 T: ExtraTokenFields,
74{
75 fn authorized(introspection: &IntrospectionResponse<T>) -> Result<bool, Error>;
76}
77
78pub trait RequireScope {
79 fn scope() -> &'static str;
80}
81
82impl<T, S> AuthorizationRequirements<T> for S
83where
84 S: RequireScope,
85 T: ExtraTokenFields,
86{
87 fn authorized(introspection: &IntrospectionResponse<T>) -> Result<bool, Error> {
88 Ok(introspection
89 .scopes()
90 .map(|s| s.iter().find(|s| s.as_ref() == S::scope()).is_some())
91 .unwrap_or(false))
92 }
93}
94
95pub struct AnyScope;
96
97impl<T> AuthorizationRequirements<T> for AnyScope
98where
99 T: ExtraTokenFields,
100{
101 fn authorized(_: &IntrospectionResponse<T>) -> Result<bool, Error> {
102 Ok(true)
103 }
104}
105
106pub struct RequireAuthorization<R, T = StandardToken>
107where
108 R: AuthorizationRequirements<T>,
109 T: ExtraTokenFields,
110{
111 introspection: IntrospectionResponse<T>,
112 _auth_marker: PhantomData<R>,
113}
114
115impl<R, T> RequireAuthorization<R, T>
116where
117 R: AuthorizationRequirements<T>,
118 T: ExtraTokenFields,
119{
120 pub fn introspection(&self) -> &IntrospectionResponse<T> {
121 &self.introspection
122 }
123}
124
125impl<R, T> FromRequest for RequireAuthorization<R, T>
126where
127 R: AuthorizationRequirements<T> + 'static,
128 T: ExtraTokenFields + 'static + Clone,
129{
130 type Error = Error;
131 type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
132
133 fn from_request(req: &actix_web::HttpRequest, _: &mut dev::Payload) -> Self::Future {
134 let my_req2 = req.clone();
135
136 let verifier = if let Some(verifier) = my_req2.app_data::<RequireAuthorizationConfig<T>>() {
137 verifier.clone()
138 } else {
139 return Box::pin(ready(Err(Error::ConfigurationError)));
140 };
141
142 let my_req = req.clone();
143
144 Box::pin(async move {
145 verifier
146 .verify_request(my_req)
147 .await
148 .and_then(|introspection| {
149 if R::authorized(&introspection)? {
150 Ok(RequireAuthorization {
151 introspection,
152 _auth_marker: PhantomData::default(),
153 })
154 } else {
155 Err(Error::AccessDenied)
156 }
157 })
158 })
159 }
160}
161
162#[derive(Clone)]
163struct RequireAuthorizationConfigInner<T>
164where
165 T: ExtraTokenFields,
166{
167 client: oauth2::Client<
168 StandardErrorResponse<BasicErrorResponseType>,
169 StandardTokenResponse<T, BasicTokenType>,
170 BasicTokenType,
171 StandardTokenIntrospectionResponse<T, BasicTokenType>,
172 StandardRevocableToken,
173 StandardErrorResponse<BasicErrorResponseType>,
174 >,
175}
176
177#[derive(Clone)]
178pub struct RequireAuthorizationConfig<T>(Arc<RequireAuthorizationConfigInner<T>>)
179where
180 T: ExtraTokenFields;
181
182impl<T> RequireAuthorizationConfig<T>
183where
184 T: ExtraTokenFields,
185{
186 pub fn new(
187 client_id: String,
188 client_secret: Option<String>,
189 auth_url: Url,
190 introspection_url: Url,
191 ) -> Self {
192 let client = oauth2::Client::new(
193 ClientId::new(client_id),
194 client_secret.map(|s| ClientSecret::new(s)),
195 AuthUrl::from_url(auth_url),
196 None,
197 )
198 .set_introspection_uri(IntrospectionUrl::from_url(introspection_url));
199 RequireAuthorizationConfig(Arc::new(RequireAuthorizationConfigInner { client }))
200 }
201
202 async fn verify_request(&self, req: HttpRequest) -> Result<IntrospectionResponse<T>, Error> {
203 let access_token = req
204 .headers()
205 .get("Authorization")
206 .and_then(|value| value.to_str().ok())
207 .filter(|value| value.starts_with(BEARER_TOKEN_PREFIX))
208 .map(|value| AccessToken::new(value.split_at(BEARER_TOKEN_PREFIX.len()).1.to_string()))
209 .ok_or(Error::MissingToken)?;
210
211 self.0
212 .client
213 .introspect(&access_token)
214 .map_err(|e| {
215 log::error!("OAuth2 client configuration error: {}", e);
216 Error::ConfigurationError
217 })?
218 .request_async(reqwest::async_http_client)
219 .await
220 .map_err(|e| {
221 log::warn!("Error from token introspection service: {}", e);
222 Error::IntrospectionServerError
223 })
224 .and_then(|resp| {
225 if resp.active() {
226 Ok(resp)
227 } else {
228 Err(Error::InvalidToken)
229 }
230 })
231 }
232}