Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add db table for login tokens which allows for invalidation #3818

Merged
merged 21 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions crates/api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use actix_web::{cookie::SameSite, http::header::Header, HttpRequest};
use actix_web::{http::header::Header, HttpRequest};
use actix_web_httpauth::headers::authorization::{Authorization, Bearer};
use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine};
use captcha::Captcha;
Expand Down Expand Up @@ -76,14 +76,12 @@ pub fn read_auth_token(req: &HttpRequest) -> Result<Option<String>, LemmyError>
if let Ok(header) = Authorization::<Bearer>::parse(req) {
Ok(Some(header.as_ref().token().to_string()))
}
// If that fails, try auth cookie. Dont use the `jwt` cookie from lemmy-ui because
// its not http-only.
// If that fails, try to read from cookie
else if let Some(cookie) = &req.cookie(AUTH_COOKIE_NAME) {
// ensure that its marked as httponly and secure
let secure = cookie.secure().unwrap_or_default();
let http_only = cookie.http_only().unwrap_or_default();
let same_site = cookie.same_site();
if !secure || !http_only || same_site != Some(SameSite::Strict) {
if !secure || !http_only {
Err(LemmyError::from(LemmyErrorType::AuthCookieInsecure))
} else {
Ok(Some(cookie.value().to_string()))
Expand Down
4 changes: 2 additions & 2 deletions crates/api_common/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ use rosetta_i18n::{Language, LanguageId};
use tracing::warn;
use url::{ParseError, Url};

pub static AUTH_COOKIE_NAME: &str = "auth";
pub static AUTH_COOKIE_NAME: &str = "jwt";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change the cookie name back to jwt, just to make it more backward compatible?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest its up to you and @SleeplessOne1917 which way its easier to handle the cookie. So let me know if you prefer to use the same name (jwt, means you need to mark the existing cookie as secure and httponly), or rename it (then you can set a new cookie and dont worry about the old one). Though if you use header for auth it doesnt matter at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't matter to me. Thoughts @SleeplessOne1917 ?

Copy link
Collaborator

@phiresky phiresky Oct 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo the hasJwtCookie and setJwtCookie functions should be removed from lemmy-ui, and with the cookie being named jwt here it shouldn't require any further changes there (I think). lemmy-ui shouldn't need to worry about the cookie at all - lemmy_server now sets it and removes it, and with it being HttpOnly it will also make session exfiltration via XSS impossible

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm with phiresky on this one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the breaking changes post says that the cookie is called auth. So I renamed it back to that.

https://lemmy.ml/post/5711722

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess it doesn't matter, lemmy-ui should still be able to remove their cookie logic regardless of what the new cookie is named


#[tracing::instrument(skip_all)]
pub async fn is_mod_or_admin(
Expand Down Expand Up @@ -754,7 +754,7 @@ pub fn sanitize_html_federation_opt(data: &Option<String>) -> Option<String> {
pub fn create_login_cookie(jwt: Sensitive<String>) -> Cookie<'static> {
let mut cookie = Cookie::new(AUTH_COOKIE_NAME, jwt.into_inner());
cookie.set_secure(true);
cookie.set_same_site(SameSite::Strict);
cookie.set_same_site(SameSite::Lax);
cookie.set_http_only(true);
phiresky marked this conversation as resolved.
Show resolved Hide resolved
cookie
}
Expand Down
5 changes: 5 additions & 0 deletions crates/db_schema/src/source/login_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@ use crate::schema::login_token;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

/// Stores data related to a specific user login session.
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "full", derive(Queryable, Identifiable))]
#[cfg_attr(feature = "full", diesel(table_name = login_token))]
pub struct LoginToken {
pub id: i32,
/// Jwt token for this login
#[serde(skip)]
pub token: String,
pub user_id: LocalUserId,
/// Time of login
pub published: DateTime<Utc>,
/// IP address where login was made from, allows invalidating logins by IP address.
/// Could be stored in truncated format, or store derived information for better privacy.
pub ip: Option<String>,
pub user_agent: Option<String>,
}
Copy link
Collaborator

@phiresky phiresky Sep 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually i think you would want to add a few other fields here:

  1. created timestamp
  2. ip address of the request that created the token
  3. user agent of the request that created the token
  4. invalidated timestamp (instead of deleting rows, use invalidated is null to filter validity)

this is all info you can (a) use to display a list of known sessions to the user and (b) so you can invalidate a session in some cases (e.g. geoip country of the session changes, user agent changes too much) (c) invalidate other sessions remotely

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we'd want to store IP addresses for privacy reasons.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@phiresky Good suggestions, I added them. Though Im doubtful about using invalidated timestamp, it means the table will grow forever and I dont see the benefit of keeping years old login info around. Adding another scheduled task for cleanup also doesnt seem worth the trouble.

Its true that storing IPs could cause some problems with privacy legislation. But I think its definitely worth it to fight abuse. We just need to be careful that IPs are not leaked to other users.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of IP you could also store derived info ( city+country, ASIN) that's a bit less private and closer to what you'd want to use for session invalidation/display. But that would need an external service for the geolocation which is a pain.

or you can store it truncated (e.g. the /24 for ipv4 and the /52 for ipv6)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes geoip seems to complicated and error prone. Truncating would require the ip first. Im going to leave it like this and we can make changes in the future if necessary.

Expand Down
1 change: 1 addition & 0 deletions crates/utils/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ pub enum LemmyErrorType {
CouldntSendWebmention,
ContradictingFilters,
InstanceBlockAlreadyExists,
#[serde(rename = "`jwt` cookie must be marked secure and httponly")]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as the other PR: i don't think using "rename" is the correct way to add messages to this enum. (see #4007 (comment) )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this to a comment instead.

AuthCookieInsecure,
Unknown(String),
}
Expand Down