|
1 | 1 | use chrono::NaiveDateTime;
|
| 2 | +use cookie::{Cookie, SameSite}; |
2 | 3 | use diesel::prelude::*;
|
3 | 4 | use ipnetwork::IpNetwork;
|
4 | 5 | use std::net::IpAddr;
|
| 6 | +use std::num::ParseIntError; |
| 7 | +use std::str::FromStr; |
5 | 8 | use thiserror::Error;
|
6 | 9 |
|
7 | 10 | use crate::schema::persistent_sessions;
|
@@ -67,15 +70,16 @@ impl PersistentSession {
|
67 | 70 | /// * `Ok(Some(...))` if a session matches the `token`.
|
68 | 71 | /// * `Ok(None)` if no session matches the `token`.
|
69 | 72 | /// * `Err(...)` for other errors such as invalid tokens or diesel errors.
|
70 |
| - pub fn find_from_token_and_update( |
| 73 | + pub fn find_and_update( |
71 | 74 | conn: &PgConnection,
|
72 |
| - token: &str, |
| 75 | + session_cookie: &SessionCookie, |
73 | 76 | ip_address: IpAddr,
|
74 | 77 | user_agent: &str,
|
75 | 78 | ) -> Result<Option<Self>, SessionError> {
|
76 |
| - let hashed_token = SecureToken::parse(SecureTokenKind::Session, token) |
| 79 | + let hashed_token = SecureToken::parse(SecureTokenKind::Session, &session_cookie.token) |
77 | 80 | .ok_or(SessionError::InvalidSessionToken)?;
|
78 | 81 | let sessions = persistent_sessions::table
|
| 82 | + .filter(persistent_sessions::id.eq(session_cookie.id)) |
79 | 83 | .filter(persistent_sessions::revoked.eq(false))
|
80 | 84 | .filter(persistent_sessions::hashed_token.eq(hashed_token));
|
81 | 85 |
|
@@ -131,3 +135,62 @@ impl NewPersistentSession<'_, '_> {
|
131 | 135 | .get_result(conn)
|
132 | 136 | }
|
133 | 137 | }
|
| 138 | + |
| 139 | +/// Holds the information needed for the session cookie. |
| 140 | +#[derive(Debug, PartialEq, Eq)] |
| 141 | +pub struct SessionCookie { |
| 142 | + /// The session ID in the database. |
| 143 | + id: i64, |
| 144 | + /// The token |
| 145 | + token: String, |
| 146 | +} |
| 147 | + |
| 148 | +impl SessionCookie { |
| 149 | + /// Name of the cookie used for session-based authentication. |
| 150 | + pub const SESSION_COOKIE_NAME: &'static str = "__Host-auth"; |
| 151 | + |
| 152 | + /// Creates a new `SessionCookie`. |
| 153 | + pub fn new(id: i64, token: String) -> Self { |
| 154 | + Self { id, token } |
| 155 | + } |
| 156 | + |
| 157 | + /// Returns the `[Cookie]`. |
| 158 | + pub fn build(&self, secure: bool) -> Cookie<'static> { |
| 159 | + Cookie::build( |
| 160 | + Self::SESSION_COOKIE_NAME, |
| 161 | + format!("{}:{}", self.id, &self.token), |
| 162 | + ) |
| 163 | + .http_only(true) |
| 164 | + .secure(secure) |
| 165 | + .same_site(SameSite::Strict) |
| 166 | + .path("/") |
| 167 | + .finish() |
| 168 | + } |
| 169 | +} |
| 170 | + |
| 171 | +/// Error returned when the session cookie couldn't be parsed. |
| 172 | +#[derive(Error, Debug, PartialEq)] |
| 173 | +pub enum ParseSessionCookieError { |
| 174 | + #[error("The session id wasn't in the cookie.")] |
| 175 | + MissingSessionId, |
| 176 | + #[error("The session id couldn't be parsed from the cookie.")] |
| 177 | + IdParseError(#[from] ParseIntError), |
| 178 | + #[error("The session token wasn't in the cookie.")] |
| 179 | + MissingToken, |
| 180 | +} |
| 181 | + |
| 182 | +impl FromStr for SessionCookie { |
| 183 | + type Err = ParseSessionCookieError; |
| 184 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 185 | + let mut id_and_token = s.split(':'); |
| 186 | + let id: i64 = id_and_token |
| 187 | + .next() |
| 188 | + .ok_or(ParseSessionCookieError::MissingSessionId)? |
| 189 | + .parse()?; |
| 190 | + let token = id_and_token |
| 191 | + .next() |
| 192 | + .ok_or(ParseSessionCookieError::MissingToken)?; |
| 193 | + |
| 194 | + Ok(Self::new(id, token.to_string())) |
| 195 | + } |
| 196 | +} |
0 commit comments