Skip to content

Commit 14ad3cc

Browse files
committed
Removed persisten_session hashed token index.
1 parent 1280232 commit 14ad3cc

File tree

7 files changed

+92
-43
lines changed

7 files changed

+92
-43
lines changed

migrations/2022-02-21-211645_create_sessions/up.sql

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,3 @@ CREATE TABLE persistent_sessions
1313
);
1414

1515
COMMENT ON TABLE persistent_sessions IS 'This table contains the hashed tokens for all of the cookie-based persistent sessions';
16-
17-
CREATE INDEX persistent_sessions_token_index
18-
ON persistent_sessions (hashed_token);

src/controllers/user/session.rs

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,19 @@
11
use crate::controllers::frontend_prelude::*;
22

33
use conduit_cookie::{RequestCookies, RequestSession};
4-
use cookie::{Cookie, SameSite};
4+
use cookie::Cookie;
55
use oauth2::reqwest::http_client;
66
use oauth2::{AuthorizationCode, Scope, TokenResponse};
77

88
use crate::email::Emails;
99
use crate::github::GithubUser;
10+
use crate::models::persistent_session::SessionCookie;
1011
use crate::models::{NewUser, PersistentSession, User};
1112
use crate::schema::users;
1213
use crate::util::errors::ReadOnlyMode;
13-
use crate::util::token::NewSecureToken;
1414
use crate::util::token::SecureToken;
1515
use crate::Env;
1616

17-
/// Name of the cookie used for session-based authentication.
18-
pub const SESSION_COOKIE_NAME: &str = "__Host-auth";
19-
20-
/// Creates a session cookie with the given token.
21-
pub fn session_cookie(token: &NewSecureToken, secure: bool) -> Cookie<'static> {
22-
Cookie::build(SESSION_COOKIE_NAME, token.plaintext().to_string())
23-
.http_only(true)
24-
.secure(secure)
25-
.same_site(SameSite::Strict)
26-
.path("/")
27-
.finish()
28-
}
29-
3017
/// Handles the `GET /api/private/session/begin` route.
3118
///
3219
/// This route will return an authorization URL for the GitHub OAuth flow including the crates.io
@@ -128,12 +115,14 @@ pub fn authorize(req: &mut dyn RequestExt) -> EndpointResult {
128115
.unwrap_or_default();
129116

130117
let token = SecureToken::generate(crate::util::token::SecureTokenKind::Session);
131-
PersistentSession::create(user.id, &token, req.remote_addr().ip(), &user_agent)
118+
let session = PersistentSession::create(user.id, &token, req.remote_addr().ip(), &user_agent)
132119
.insert(&*req.db_conn()?)?;
133120

121+
let cookie = SessionCookie::new(session.id, token.plaintext().to_string());
122+
134123
// Setup persistent session cookie.
135124
let secure = req.app().config.env() == Env::Production;
136-
req.cookies_mut().add(session_cookie(&token, secure));
125+
req.cookies_mut().add(cookie.build(secure));
137126

138127
// TODO(adsnaider): Remove as part of https://github.com/rust-lang/crates.io/issues/2630.
139128
// Log in by setting a cookie and the middleware authentication.
@@ -181,10 +170,11 @@ pub fn logout(req: &mut dyn RequestExt) -> EndpointResult {
181170
// Remove persistent session from database.
182171
if let Some(session_token) = req
183172
.cookies()
184-
.get(SESSION_COOKIE_NAME)
173+
.get(SessionCookie::SESSION_COOKIE_NAME)
185174
.map(|cookie| cookie.value().to_string())
186175
{
187-
req.cookies_mut().remove(Cookie::named(SESSION_COOKIE_NAME));
176+
req.cookies_mut()
177+
.remove(Cookie::named(SessionCookie::SESSION_COOKIE_NAME));
188178

189179
if let Ok(conn) = req.db_conn() {
190180
// TODO(adsnaider): Maybe log errors somehow?

src/controllers/util.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use chrono::Utc;
22
use conduit_cookie::RequestCookies;
33

44
use super::prelude::*;
5-
use crate::controllers::user::session::SESSION_COOKIE_NAME;
65
use crate::middleware::log_request;
6+
use crate::models::persistent_session::SessionCookie;
77
use crate::models::{ApiToken, PersistentSession, User};
88
use crate::util::errors::{
99
account_locked, forbidden, internal, AppError, AppResult, InsecurelyGeneratedTokenRevoked,
@@ -84,10 +84,11 @@ fn authenticate_user(req: &dyn RequestExt) -> AppResult<AuthenticatedUser> {
8484
}
8585

8686
// Log in with persistent session token.
87-
if let Some(session_token) = req
87+
if let Some(Ok(session_cookie)) = req
8888
.cookies()
89-
.get(SESSION_COOKIE_NAME)
89+
.get(SessionCookie::SESSION_COOKIE_NAME)
9090
.map(|cookie| cookie.value())
91+
.map(|cookie| cookie.parse::<SessionCookie>())
9192
{
9293
let ip_addr = req.remote_addr().ip();
9394

@@ -97,12 +98,9 @@ fn authenticate_user(req: &dyn RequestExt) -> AppResult<AuthenticatedUser> {
9798
.and_then(|value| value.to_str().ok())
9899
.unwrap_or_default();
99100

100-
if let Some(session) = PersistentSession::find_from_token_and_update(
101-
&conn,
102-
session_token,
103-
ip_addr,
104-
user_agent,
105-
)? {
101+
if let Some(session) =
102+
PersistentSession::find_and_update(&conn, &session_cookie, ip_addr, user_agent)?
103+
{
106104
let user = User::find(&conn, session.user_id)
107105
.map_err(|e| e.chain(internal("user_id from session not found in the database")))?;
108106
return Ok(AuthenticatedUser {

src/models.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ mod follow;
2929
mod keyword;
3030
pub mod krate;
3131
mod owner;
32-
mod persistent_session;
32+
pub mod persistent_session;
3333
mod rights;
3434
mod team;
3535
mod token;

src/models/persistent_session.rs

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use chrono::NaiveDateTime;
2+
use cookie::{Cookie, SameSite};
23
use diesel::prelude::*;
34
use ipnetwork::IpNetwork;
45
use std::net::IpAddr;
6+
use std::num::ParseIntError;
7+
use std::str::FromStr;
58
use thiserror::Error;
69

710
use crate::schema::persistent_sessions;
@@ -67,15 +70,16 @@ impl PersistentSession {
6770
/// * `Ok(Some(...))` if a session matches the `token`.
6871
/// * `Ok(None)` if no session matches the `token`.
6972
/// * `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(
7174
conn: &PgConnection,
72-
token: &str,
75+
session_cookie: &SessionCookie,
7376
ip_address: IpAddr,
7477
user_agent: &str,
7578
) -> Result<Option<Self>, SessionError> {
76-
let hashed_token = SecureToken::parse(SecureTokenKind::Session, token)
79+
let hashed_token = SecureToken::parse(SecureTokenKind::Session, &session_cookie.token)
7780
.ok_or(SessionError::InvalidSessionToken)?;
7881
let sessions = persistent_sessions::table
82+
.filter(persistent_sessions::id.eq(session_cookie.id))
7983
.filter(persistent_sessions::revoked.eq(false))
8084
.filter(persistent_sessions::hashed_token.eq(hashed_token));
8185

@@ -131,3 +135,62 @@ impl NewPersistentSession<'_, '_> {
131135
.get_result(conn)
132136
}
133137
}
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+
}

src/tests/authentication.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::util::{RequestHelper, Response};
22
use crate::TestApp;
33

44
use crate::util::encode_session_header;
5-
use cargo_registry::controllers::user::session::session_cookie;
5+
use cargo_registry::models::persistent_session::SessionCookie;
66
use cargo_registry::util::token::SecureToken;
77
use cargo_registry::util::token::SecureTokenKind;
88
use conduit::{header, Body, Method, StatusCode};
@@ -27,7 +27,9 @@ fn incorrect_session_is_forbidden() {
2727

2828
let token = SecureToken::generate(SecureTokenKind::Session);
2929
// Create a cookie that isn't in the database.
30-
let cookie = session_cookie(&token, false).to_string();
30+
let cookie = SessionCookie::new(123, token.plaintext().to_string())
31+
.build(false)
32+
.to_string();
3133
let mut request = anon.request_builder(Method::GET, URL);
3234
request.header(header::COOKIE, &cookie);
3335
let response: Response<Body> = anon.run(request);

src/tests/util.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@ use crate::{
2323
builders::PublishBuilder, CategoryListResponse, CategoryResponse, CrateList, CrateResponse,
2424
GoodCrate, OkBool, OwnersResponse, VersionResponse,
2525
};
26-
use cargo_registry::controllers::user::session::session_cookie;
26+
use cargo_registry::models::persistent_session::SessionCookie;
2727
use cargo_registry::models::PersistentSession;
2828
use cargo_registry::models::{ApiToken, CreatedApiToken, User};
29-
use cargo_registry::util::token::NewSecureToken;
3029
use cargo_registry::util::token::SecureToken;
3130
use cargo_registry::util::token::SecureTokenKind;
3231

@@ -283,27 +282,27 @@ impl MockCookieUser {
283282

284283
let token = SecureToken::generate(SecureTokenKind::Session);
285284

286-
self.app.db(|conn| {
285+
let session = self.app.db(|conn| {
287286
PersistentSession::create(self.user.id, &token, ip_addr.parse().unwrap(), user_agent)
288287
.insert(conn)
289288
.unwrap()
290289
});
291290

292291
MockSessionUser {
293292
app: self.app.clone(),
294-
token,
293+
session_cookie: SessionCookie::new(session.id, token.plaintext().to_string()),
295294
}
296295
}
297296
}
298297

299298
pub struct MockSessionUser {
300299
app: TestApp,
301-
token: NewSecureToken,
300+
session_cookie: SessionCookie,
302301
}
303302

304303
impl RequestHelper for MockSessionUser {
305304
fn request_builder(&self, method: Method, path: &str) -> MockRequest {
306-
let cookie = session_cookie(&self.token, false).to_string();
305+
let cookie = self.session_cookie.build(false).to_string();
307306

308307
let mut request = req(method, path);
309308
request.header(header::COOKIE, &cookie);

0 commit comments

Comments
 (0)