1use jerrycan_core::{Error, FromRequest, Headers, RequestCtx, Result};
5use serde::de::DeserializeOwned;
6
7pub struct Session<T>(pub T);
10
11impl<T: DeserializeOwned + Send> FromRequest for Session<T> {
12 async fn from_request(ctx: &mut RequestCtx) -> Result<Self> {
13 let auth = ctx.resolve::<crate::Auth>().await?;
14 let headers = Headers::from_request(ctx).await?;
15 let cookie_header = headers.get("cookie").ok_or_else(Error::unauthorized)?;
16 let token = auth
17 .sessions()
18 .read_cookie(cookie_header)
19 .ok_or_else(Error::unauthorized)?;
20 auth.sessions().decode::<T>(&token).map(Session)
21 }
22}
23
24pub struct Bearer<T>(pub T);
26
27impl<T: DeserializeOwned + Send> FromRequest for Bearer<T> {
28 async fn from_request(ctx: &mut RequestCtx) -> Result<Self> {
29 let auth = ctx.resolve::<crate::Auth>().await?;
30 let headers = Headers::from_request(ctx).await?;
31 let value = headers
32 .get("authorization")
33 .ok_or_else(Error::unauthorized)?;
34 let token = value
35 .strip_prefix("Bearer ")
36 .ok_or_else(Error::unauthorized)?;
37 crate::jwt::decode::<T>(token, auth.jwt_key()).map(Bearer)
38 }
39}
40
41pub fn require_role(actual: &str, required: &str) -> Result<()> {
43 if actual == required {
44 Ok(())
45 } else {
46 Err(Error::forbidden())
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use crate::Auth;
54 use jerrycan_core::{App, Dep, Json, get, post};
55 use serde::{Deserialize, Serialize};
56
57 #[derive(Serialize, Deserialize, Clone)]
58 struct User {
59 id: String,
61 role: String,
62 }
63
64 async fn login(auth: Dep<Auth>) -> Result<jerrycan_core::Response> {
65 let cookie = auth.sessions().set_cookie(&User {
67 id: "1".into(),
68 role: "admin".into(),
69 })?;
70 let mut res = jerrycan_core::IntoResponse::into_response("ok");
71 res.headers_mut().insert(
72 jerrycan_core::http::header::SET_COOKIE,
73 jerrycan_core::http::HeaderValue::from_str(&cookie).unwrap(),
74 );
75 Ok(res)
76 }
77
78 async fn whoami(Session(user): Session<User>) -> Json<String> {
79 Json(user.id)
80 }
81
82 fn app() -> App {
83 App::new()
84 .extend(Auth::with_secret("a-very-long-development-secret-string!!"))
85 .route("/login", post(login))
86 .route("/me", get(whoami))
87 }
88
89 #[tokio::test]
90 async fn no_cookie_is_401() {
91 let t = app().into_test();
92 assert_eq!(
93 t.get("/me").await.status(),
94 jerrycan_core::http::StatusCode::UNAUTHORIZED
95 );
96 }
97
98 #[tokio::test]
99 async fn login_then_authenticated_request_succeeds() {
100 let t = app().into_test();
101 let login = t.post_json("/login", &()).await;
102 let set_cookie = login.headers()["set-cookie"].to_str().unwrap().to_string();
103 let cookie = set_cookie.split(';').next().unwrap().to_string(); let res = t.get_with("/me", &[("cookie", &cookie)]).await;
105 assert_eq!(res.status(), jerrycan_core::http::StatusCode::OK);
106 assert_eq!(res.json::<String>(), "1");
107 }
108
109 #[tokio::test]
110 async fn require_role_rejects_wrong_role_with_403() {
111 async fn admin_only(Session(user): Session<User>) -> Result<&'static str> {
112 require_role(&user.role, "superadmin")?;
113 Ok("secret")
114 }
115 let t = App::new()
116 .extend(Auth::with_secret("a-very-long-development-secret-string!!"))
117 .route("/login", post(login))
118 .route("/admin", get(admin_only))
119 .into_test();
120 let login = t.post_json("/login", &()).await;
121 let cookie = login.headers()["set-cookie"]
122 .to_str()
123 .unwrap()
124 .split(';')
125 .next()
126 .unwrap()
127 .to_string();
128 let res = t.get_with("/admin", &[("cookie", &cookie)]).await;
129 assert_eq!(res.status(), jerrycan_core::http::StatusCode::FORBIDDEN);
130 }
131}