Skip to content

Commit ae3ada6

Browse files
committed
Removed persisten_session hashed token index.
1 parent 0c1161e commit ae3ada6

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

-3
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

+9-19
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
@@ -123,12 +110,14 @@ pub fn authorize(req: &mut dyn RequestExt) -> EndpointResult {
123110
.unwrap_or_default();
124111

125112
let token = SecureToken::generate(crate::util::token::SecureTokenKind::Session);
126-
PersistentSession::create(user.id, &token, req.remote_addr().ip(), &user_agent)
113+
let session = PersistentSession::create(user.id, &token, req.remote_addr().ip(), &user_agent)
127114
.insert(&*req.db_conn()?)?;
128115

116+
let cookie = SessionCookie::new(session.id, token.plaintext().to_string());
117+
129118
// Setup persistent session cookie.
130119
let secure = req.app().config.env() == Env::Production;
131-
req.cookies_mut().add(session_cookie(&token, secure));
120+
req.cookies_mut().add(cookie.build(secure));
132121

133122
// TODO(adsnaider): Remove as part of https://github.com/rust-lang/crates.io/issues/2630.
134123
// Log in by setting a cookie and the middleware authentication.
@@ -176,10 +165,11 @@ pub fn logout(req: &mut dyn RequestExt) -> EndpointResult {
176165
// Remove persistent session from database.
177166
if let Some(session_token) = req
178167
.cookies()
179-
.get(SESSION_COOKIE_NAME)
168+
.get(SessionCookie::SESSION_COOKIE_NAME)
180169
.map(|cookie| cookie.value().to_string())
181170
{
182-
req.cookies_mut().remove(Cookie::named(SESSION_COOKIE_NAME));
171+
req.cookies_mut()
172+
.remove(Cookie::named(SessionCookie::SESSION_COOKIE_NAME));
183173

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

src/controllers/util.rs

+7-9
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

+1-1
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

+66-3
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

+4-2
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

+5-6
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

@@ -282,27 +281,27 @@ impl MockCookieUser {
282281

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

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

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

298297
pub struct MockSessionUser {
299298
app: TestApp,
300-
token: NewSecureToken,
299+
session_cookie: SessionCookie,
301300
}
302301

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

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

0 commit comments

Comments
 (0)