1use std::pin::Pin;
11use std::task::{Context, Poll};
12
13use actix_web::{Error, ResponseError};
14pub use actix_web::dev::{ServiceRequest,ServiceResponse};
15use std::future::{Future, Ready, ready};
16use actix_web::dev::{Transform, Service};
17use actix_web::body::MessageBody;
18use jwks_client::keyset::KeyStore;
19use std::env;
20use thiserror::Error;
21use std::env::VarError;
22pub use jwks_client::jwt::{Jwt, Payload, Header};
23use std::rc::Rc;
24
25use actix_web::http::StatusCode;
26
27
28type JwtValidator = fn(&ServiceRequest,&Option<Jwt>)->bool;
35
36#[allow(non_snake_case)]
42pub fn CheckJwtValid(req: &ServiceRequest, jwt: &Option<Jwt>) -> bool {
43 log::debug!("Default JWT validator called {:?} / {:?}", req, jwt);
44
45 match jwt {
46 None => {
47 false
48 },
49 Some(_) => {
50 true
51 }
52 }
53}
54
55pub struct JwtAuth {
59 jwks_url: String,
60 validator: Rc<JwtValidator>
61}
62
63pub struct JwtAuthService<S> {
64 service: S,
65 jwks: KeyStore,
66 validator: Rc<JwtValidator>
67}
68
69#[derive(Error,Debug)]
70pub enum JwtAuthError {
71 #[error("No JWKS keystore address specified")]
72 NoKeystoreSpecified,
73
74 #[error("Failed to load JWKS keystore from {0:?}")]
75 FailedToLoadKeystore(jwks_client::error::Error),
76
77 #[error("Bearer authentication token invalid: {0:?}")]
78 InvalidBearerAuth(jwks_client::error::Error),
79
80 #[error("Access to this resource is not authorised")]
81 Unauthorised
82}
83
84impl JwtAuth
86{
87 pub fn new_from_env(validator: JwtValidator) -> Result<Self,JwtAuthError> {
97 let jwks_url = env::var("JWKS_URL")?;
98
99 JwtAuth::new_from_url(validator, jwks_url)
100 }
101
102 pub fn new_from_url(validator: JwtValidator, jwks_url: String) -> Result<Self,JwtAuthError> {
112
113 let _jwks = KeyStore::new_from(&jwks_url)?;
116
117 Ok(JwtAuth {
118 jwks_url,
119 validator: Rc::new(validator)
120 })
121 }
122}
123
124impl <S,B> Transform<S, ServiceRequest> for JwtAuth
125where
126 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error=Error>,
127 B: MessageBody,
128 B: 'static,
129 S::Future: 'static
130{
131 type Response = S::Response;
132 type Error = S::Error;
133 type Transform = JwtAuthService<S>;
134 type InitError = ();
135 type Future = Ready<Result<Self::Transform, Self::InitError>>;
136
137 fn new_transform(&self, service: S) -> Self::Future {
138 let jwks_url = self.jwks_url.clone();
139
140 ready(match KeyStore::new_from(&jwks_url) {
141 Ok(jwks) => {
142 Ok(JwtAuthService {
143 service,
144 jwks,
145 validator: self.validator.clone()
146 })
147 }
148 Err(e) => {
149 log::error!("Cannot load JWKS keystore from {}: {:?}", jwks_url, e);
150 Err(())
151 }
152 })
153
154
155 }
156}
157
158impl <S, B> Service<ServiceRequest> for JwtAuthService<S>
159where
160 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
161 S::Future: 'static,
162 B: MessageBody,
163 B: 'static
164{
165 type Response = S::Response;
166 type Error = S::Error;
167 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
168
169 fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
170 self.service.poll_ready(ctx)
171 }
172
173 fn call(&self, req: ServiceRequest) -> Self::Future {
174 let authorization = req.headers().get(actix_web::http::header::AUTHORIZATION);
175
176 let jwt = {
177 match authorization {
178 Some(value) => {
179
180 let value_str = value.to_str().unwrap().to_string();
181
182 match value_str.strip_prefix("Bearer ") {
183 Some(token) => {
184 match self.jwks.verify(&token) {
185 Ok(jwt) => {
186 Some(jwt)
187 }
188 Err(e) => {
189 return Box::pin(ready(Err(JwtAuthError::InvalidBearerAuth(e).into())))
190 }
191 }
192 }
193 _ => {
194 None
195 }
196 }
197 },
198 None => {
199 None
200 }
201 }
202 };
203
204 if (self.validator)(&req, &jwt) {
207 let fut = self.service.call(req);
208 Box::pin(async move {
209 let res = fut.await?;
210
211 Ok(res)
212 })
213 } else {
214 Box::pin(ready(Err(JwtAuthError::Unauthorised.into())))
215 }
216 }
217}
218
219
220impl From<jwks_client::error::Error> for JwtAuthError {
221 fn from(e: jwks_client::error::Error) -> Self {
222 JwtAuthError::FailedToLoadKeystore(e)
223 }
224}
225
226impl From<VarError> for JwtAuthError {
227 fn from(_: VarError) -> Self {
228 JwtAuthError::NoKeystoreSpecified
229 }
230}
231
232impl ResponseError for JwtAuthError {
233 fn status_code(&self) -> StatusCode {
234 match self {
235 JwtAuthError::NoKeystoreSpecified => StatusCode::INTERNAL_SERVER_ERROR,
236 JwtAuthError::FailedToLoadKeystore(_) => StatusCode::INTERNAL_SERVER_ERROR,
237 JwtAuthError::InvalidBearerAuth(_) => StatusCode::UNAUTHORIZED,
238 JwtAuthError::Unauthorised => StatusCode::UNAUTHORIZED
239 }
240 }
241}
242
243#[cfg(test)]
245mod tests {
246 use super::*;
247
248 const TEST_KEYSET: &str = "https://snowgoons.eu.auth0.com/.well-known/jwks.json";
249
250 #[actix_rt::test]
251 async fn test_jwks_url() {
252 let _middleware = JwtAuth::new_from_url(CheckJwtValid, String::from(TEST_KEYSET)).unwrap();
253 }
254
255 #[actix_rt::test]
256 #[should_panic]
257 async fn test_jwks_url_fail() {
258 let _middleware = JwtAuth::new_from_url(CheckJwtValid, String::from("https://not.here/")).unwrap();
259 }
260
261 #[actix_rt::test]
262 async fn test_jwks_env() {
263 env::set_var("JWKS_URL", String::from(TEST_KEYSET));
264
265 let _middleware = JwtAuth::new_from_env(CheckJwtValid).unwrap();
266 }
267
268 #[actix_rt::test]
269 #[should_panic]
270 async fn test_jwks_env_fail() {
271 env::remove_var("JWKS_URL");
272
273 let _middleware = JwtAuth::new_from_env(CheckJwtValid).unwrap();
274 }
275}