Skip to content

Commit

Permalink
remove expiration for auth tokens
Browse files Browse the repository at this point in the history
Before, auth tokens expires after a week. After this change, they are valid until they are explicitly regenerated.
  • Loading branch information
dominikks committed Nov 6, 2023
1 parent 5bf6d63 commit 0649e79
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 44 deletions.
94 changes: 52 additions & 42 deletions backend/src/api/auth.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,37 @@
use crate::api::utils::AvatarOrDefault;
use crate::api::Snowflake;
use crate::api::DISCORD_CLIENT_ID;
use crate::api::DISCORD_CLIENT_SECRET;
use crate::db::models;
use crate::db::DbConn;
use crate::discord::management::get_guilds_for_user;
use crate::discord::management::UserPermission;
use crate::CacheHttp;
use crate::BASE_URL;
use std::error::Error;
use std::iter;
use std::time::{Duration, SystemTime};

use bigdecimal::BigDecimal;
use bigdecimal::FromPrimitive;
use bigdecimal::ToPrimitive;
use diesel::prelude::*;
use diesel::result::Error as DieselError;
use oauth2::basic::BasicClient;
use oauth2::reqwest::async_http_client;
use oauth2::AuthUrl;
use oauth2::AuthorizationCode;
use oauth2::ClientId;
use oauth2::ClientSecret;
use oauth2::CsrfToken;
use oauth2::PkceCodeChallenge;
use oauth2::PkceCodeVerifier;
use oauth2::RedirectUrl;
use oauth2::RequestTokenError;
use oauth2::Scope;
use oauth2::TokenResponse;
use oauth2::TokenUrl;
use oauth2::{
AuthUrl, AuthorizationCode, CsrfToken, PkceCodeChallenge, PkceCodeVerifier, Scope,
TokenResponse,
};
use rand::distributions::Alphanumeric;
use rand::rngs::OsRng;
use rand::Rng;
use rocket::get;
use rocket::http::Cookie;
use rocket::http::CookieJar;
use rocket::http::SameSite;
use rocket::http::Status;
use rocket::http::{Cookie, SameSite};
use rocket::outcome::try_outcome;
use rocket::outcome::IntoOutcome;
use rocket::request;
use rocket::request::FromRequest;
use rocket::request::Outcome;
use rocket::response::status;
use rocket::response::Redirect;
use rocket::response::Responder;
use rocket::response::{status, Redirect};
use rocket::serde::json::Json;
use rocket::time::OffsetDateTime;
use rocket::Request;
Expand All @@ -53,10 +43,17 @@ use serde::Serializer;
use serde_with::serde_as;
use serde_with::TimestampSeconds;
use serenity::model::id::UserId as SerenityUserId;
use std::error::Error;
use std::iter;
use std::time::Duration;
use std::time::SystemTime;

use crate::api::utils::AvatarOrDefault;
use crate::api::Snowflake;
use crate::api::DISCORD_CLIENT_ID;
use crate::api::DISCORD_CLIENT_SECRET;
use crate::db::models;
use crate::db::DbConn;
use crate::discord::management::get_guilds_for_user;
use crate::discord::management::UserPermission;
use crate::CacheHttp;
use crate::BASE_URL;

static SESSION_COOKIE: &str = "auth_session";
static LOGIN_COOKIE: &str = "auth_login";
Expand Down Expand Up @@ -158,6 +155,7 @@ impl<'r> FromRequest<'r> for TokenUserId {
type Error = ();

/// Protected api endpoints can inject `TokenUserId` to be accessible via auth token or session.
/// Tokens are valid indefinitely.
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
const TOKEN_PREFIX: &str = "Bearer ";
let db = try_outcome!(request.guard::<DbConn>().await);
Expand All @@ -178,19 +176,6 @@ impl<'r> FromRequest<'r> for TokenUserId {
})
.await
.ok()
.and_then(|auth_token| {
let diff = SystemTime::now()
.duration_since(auth_token.creation_time)
.ok()?
.as_secs();

// Ignore the token if it is more than a week old
if diff > 60 * 60 * 24 * 7 {
None
} else {
Some(auth_token)
}
})
.and_then(|auth_token| auth_token.user_id.to_u64())
.map(TokenUserId);

Expand All @@ -213,6 +198,8 @@ enum AuthError {
CsrfMissmatch(String),
#[response(status = 403)]
MissingLoginCookie(String),
#[response(status = 404)]
NotFound(String),
#[response(status = 500)]
RequestTokenError(String),
#[response(status = 500)]
Expand Down Expand Up @@ -243,8 +230,8 @@ impl From<reqwest::Error> for AuthError {
}
}

impl From<diesel::result::Error> for AuthError {
fn from(err: diesel::result::Error) -> Self {
impl From<DieselError> for AuthError {
fn from(err: DieselError) -> Self {
error!(?err, "Diesel error in API call");
Self::InternalError(String::from("Database operation failed."))
}
Expand Down Expand Up @@ -458,9 +445,10 @@ fn logout(cookies: &CookieJar<'_>) -> String {
}

/// Beware: this replaces the current auth token with a new one. The old one becomes invalid.
#[post("/auth/gettoken")]
async fn get_auth_token(user: UserId, db: DbConn) -> Result<String, AuthError> {
#[post("/auth/token")]
async fn create_auth_token(user: UserId, db: DbConn) -> Result<String, AuthError> {
let uid = BigDecimal::from_u64(user.0).ok_or_else(AuthError::bigdecimal_error)?;

let auth_token: String = iter::repeat(())
.map(|_| OsRng.sample(Alphanumeric))
.map(char::from)
Expand Down Expand Up @@ -488,3 +476,25 @@ async fn get_auth_token(user: UserId, db: DbConn) -> Result<String, AuthError> {

Ok(auth_token)
}

#[get("/auth/token")]
async fn get_auth_token(user: UserId, db: DbConn) -> Result<String, AuthError> {
let uid = BigDecimal::from_u64(user.0).ok_or_else(AuthError::bigdecimal_error)?;

let token = db
.run(move |c| {
use crate::db::schema::authtokens::dsl::*;

authtokens.find(uid).first::<models::AuthToken>(c)
})
.await
.map_err(|err| {
if err == DieselError::NotFound {
AuthError::NotFound(String::from("No auth token found"))
} else {
err
}
})?;

Ok(token.token)
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class KeybindGeneratorComponent {
}

generateAutohotkey() {
this.apiService.getAuthToken().subscribe({
this.apiService.generateAuthToken().subscribe({
next: authtoken => {
const script = this.generateAutohotkeyScript(authtoken, this.keybinds);
this.downloadText(script, 'soundboard.ahk');
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/app/services/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ export class ApiService {
return this.http.post('/api/auth/logout', {}, { responseType: 'text' }).pipe(tap(() => this.user.set(null)));
}

generateAuthToken() {
return this.http.post('/api/auth/token', {}, { responseType: 'text' });
}

getAuthToken() {
return this.http.post('/api/auth/gettoken', {}, { responseType: 'text' });
return this.http.get('/api/auth/token', { responseType: 'text' });
}
}

0 comments on commit 0649e79

Please sign in to comment.