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

Move to tower-sessions #175

Merged
merged 5 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
308 changes: 119 additions & 189 deletions Cargo.lock

Large diffs are not rendered by default.

28 changes: 22 additions & 6 deletions crates/alexandrie/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,10 @@ alexandrie-rendering = { path = "../alexandrie-rendering", version = "0.1.0" }
tokio = { workspace = true, features = ["rt-multi-thread", "fs", "macros"] }
axum = { version = "0.6.19", features = ["http2", "headers"] }
axum-extra = "0.7.5"
axum-sessions = "0.5.0"

# command-line interface
clap = { version = "4.2.2", features = ["string", "derive"] }

# session handling
async-session = "3.0.0"

# data types
url = "2.3.1"
semver = { version = "1.0.17", features = ["serde"] }
Expand Down Expand Up @@ -61,7 +57,7 @@ tantivy = "0.20"
tantivy-analysis-contrib = { version = "0.9", default-features = false, features = ["commons"] }

# async primitives
futures-util = "0.3.28"
futures-util = { version = "0.3.28", features = ["io"] }
tower = "0.4.13"
tower-http = { version = "0.4.1", features = ["trace", "fs"] }

Expand All @@ -74,6 +70,7 @@ flate2 = "1.0.25"
tar = "0.4.38"

# frontend
tower-sessions = { version = "0.2.1", optional = true }
handlebars = { version = "4.3.6", features = ["dir_source"], optional = true }
time = { version = "0.3.20", optional = true }
num-format = { version = "0.4.4", optional = true }
Expand All @@ -91,12 +88,31 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
default = ["frontend", "sqlite"]
# default = ["frontend", "mysql"]
# default = ["frontend", "postgres"]

# database vendors
mysql = ["diesel/mysql", "diesel_migrations/mysql"]
sqlite = ["diesel/sqlite", "diesel_migrations/sqlite"]
postgres = ["diesel/postgres", "diesel_migrations/postgres"]

# crate index management strategies
git2 = ["alexandrie-index/git2"]

# crate stores
s3 = ["alexandrie-storage/s3"]
frontend = ["dep:handlebars", "dep:oauth2", "dep:once_cell", "dep:regex", "dep:reqwest", "dep:num-format", "dep:bigdecimal", "dep:time", "diesel/numeric"]

# integrated frontend
frontend = [
"dep:tower-sessions",
"dep:handlebars",
"dep:oauth2",
"dep:once_cell",
"dep:regex",
"dep:reqwest",
"dep:num-format",
"dep:bigdecimal",
"dep:time",
"diesel/numeric",
]

[build-dependencies]
built = { version = "0.6.0", features = ["git2", "chrono"] }
4 changes: 2 additions & 2 deletions crates/alexandrie/src/frontend/account/github/attach.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use axum::http::StatusCode;
use axum::response::Redirect;
use axum_extra::either::Either;
use axum_extra::response::Html;
use axum_sessions::extractors::WritableSession;
use oauth2::{CsrfToken, Scope};
use tower_sessions::Session;

use crate::config::AppState;
use crate::error::FrontendError;
Expand All @@ -17,7 +17,7 @@ use crate::utils::auth::frontend::Auth;
pub(crate) async fn get(
State(state): State<Arc<AppState>>,
maybe_author: Option<Auth>,
mut session: WritableSession,
session: Session,
) -> Result<Either<(StatusCode, Html<String>), Redirect>, FrontendError> {
let Some(Auth(author)) = maybe_author else {
return Ok(Either::E2(Redirect::to("/account/manage")));
Expand Down
12 changes: 5 additions & 7 deletions crates/alexandrie/src/frontend/account/github/callback.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use std::sync::Arc;
use std::time::Duration;

use axum::extract::{Query, State};
use axum::http::StatusCode;
use axum::response::Redirect;
use axum_extra::either::Either;
use axum_extra::response::Html;
use axum_sessions::extractors::WritableSession;
use diesel::prelude::*;
use oauth2::reqwest::async_http_client;
use oauth2::{AuthorizationCode, TokenResponse};
use ring::digest as hasher;
use ring::rand::{SecureRandom, SystemRandom};
use serde::{Deserialize, Serialize};
use tower_sessions::Session;

use crate::config::frontend::auth::github::GithubAuthOrganizationConfig;
use crate::config::AppState;
Expand Down Expand Up @@ -70,17 +69,16 @@ pub(crate) async fn get(
State(state): State<Arc<AppState>>,
Query(query): Query<CallbackQueryData>,
maybe_author: Option<Auth>,
mut session: WritableSession,
session: Session,
) -> Result<Either<(StatusCode, Html<String>), Redirect>, FrontendError> {
let Some(data): Option<GithubLoginState> = session.get(GITHUB_LOGIN_STATE_KEY) else {
let Some(data): Option<GithubLoginState> = session.remove(GITHUB_LOGIN_STATE_KEY)? else {
let rendered = utils::response::error_html(
state.as_ref(),
None,
"no authentication is currently being performed",
)?;
return Ok(Either::E1((StatusCode::BAD_REQUEST, Html(rendered))));
};
session.remove("login.github");

let current_author = match (data.attach, maybe_author) {
(true, Some(author)) => Some(author),
Expand Down Expand Up @@ -265,11 +263,11 @@ pub(crate) async fn get(
};

//? Get the maximum duration of the session.
let expiry = Duration::from_secs(86_400); // 1 day / 24 hours
let expiry = time::Duration::seconds(86_400); // 1 day / 24 hours

//? Set the user's session.
session.insert("author.id", author_id)?;
session.expire_in(expiry);
session.set_expiration_time_from_max_age(expiry);

return Ok(Either::E2(Redirect::to("/")));
});
Expand Down
4 changes: 2 additions & 2 deletions crates/alexandrie/src/frontend/account/github/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use axum::http::StatusCode;
use axum::response::Redirect;
use axum_extra::either::Either;
use axum_extra::response::Html;
use axum_sessions::extractors::WritableSession;
use oauth2::{CsrfToken, Scope};
use serde::{Deserialize, Serialize};
use tower_sessions::Session;

/// Endpoint to attach to an existing Alexandrie account.
pub mod attach;
Expand All @@ -33,7 +33,7 @@ struct GithubLoginState {
pub(crate) async fn get(
State(state): State<Arc<AppState>>,
maybe_author: Option<Auth>,
mut session: WritableSession,
session: Session,
) -> Result<Either<(StatusCode, Html<String>), Redirect>, FrontendError> {
if let Some(Auth(author)) = maybe_author {
let state = state.as_ref();
Expand Down
4 changes: 2 additions & 2 deletions crates/alexandrie/src/frontend/account/gitlab/attach.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use axum::http::StatusCode;
use axum::response::Redirect;
use axum_extra::either::Either;
use axum_extra::response::Html;
use axum_sessions::extractors::WritableSession;
use oauth2::{CsrfToken, Scope};
use tower_sessions::Session;

use crate::config::AppState;
use crate::error::FrontendError;
Expand All @@ -17,7 +17,7 @@ use crate::utils::auth::frontend::Auth;
pub(crate) async fn get(
State(state): State<Arc<AppState>>,
maybe_author: Option<Auth>,
mut session: WritableSession,
session: Session,
) -> Result<Either<(StatusCode, Html<String>), Redirect>, FrontendError> {
let Some(Auth(author)) = maybe_author else {
return Ok(Either::E2(Redirect::to("/account/manage")));
Expand Down
12 changes: 5 additions & 7 deletions crates/alexandrie/src/frontend/account/gitlab/callback.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use std::sync::Arc;
use std::time::Duration;

use axum::extract::{Query, State};
use axum::http::StatusCode;
use axum::response::Redirect;
use axum_extra::either::Either;
use axum_extra::response::Html;
use axum_sessions::extractors::WritableSession;
use diesel::prelude::*;
use oauth2::reqwest::async_http_client;
use oauth2::{AccessToken, AuthorizationCode, TokenResponse};
Expand All @@ -15,6 +13,7 @@ use regex::Regex;
use ring::digest as hasher;
use ring::rand::{SecureRandom, SystemRandom};
use serde::{Deserialize, Serialize};
use tower_sessions::Session;
use url::Url;

use crate::config::AppState;
Expand Down Expand Up @@ -58,17 +57,16 @@ pub(crate) async fn get(
State(state): State<Arc<AppState>>,
Query(query): Query<CallbackQueryData>,
maybe_author: Option<Auth>,
mut session: WritableSession,
session: Session,
) -> Result<Either<(StatusCode, Html<String>), Redirect>, FrontendError> {
let Some(data): Option<GitlabLoginState> = session.get(GITLAB_LOGIN_STATE_KEY) else {
let Some(data): Option<GitlabLoginState> = session.remove(GITLAB_LOGIN_STATE_KEY)? else {
let rendered = utils::response::error_html(
state.as_ref(),
None,
"no authentication is currently being performed",
)?;
return Ok(Either::E1((StatusCode::BAD_REQUEST, Html(rendered))));
};
session.remove("login.gitlab");

let current_author = match (data.attach, maybe_author) {
(true, Some(author)) => Some(author),
Expand Down Expand Up @@ -232,11 +230,11 @@ pub(crate) async fn get(
};

//? Get the maximum duration of the session.
let expiry = Duration::from_secs(86_400); // 1 day / 24 hours
let expiry = time::Duration::seconds(86_400); // 1 day / 24 hours

//? Set the user's session.
session.insert("author.id", author_id)?;
session.expire_in(expiry);
session.set_expiration_time_from_max_age(expiry);

return Ok(Either::E2(Redirect::to("/")));
});
Expand Down
4 changes: 2 additions & 2 deletions crates/alexandrie/src/frontend/account/gitlab/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use axum::http::StatusCode;
use axum::response::Redirect;
use axum_extra::either::Either;
use axum_extra::response::Html;
use axum_sessions::extractors::WritableSession;
use oauth2::{CsrfToken, Scope};
use serde::{Deserialize, Serialize};
use tower_sessions::Session;

/// Endpoint to attach to an existing Alexandrie account.
pub mod attach;
Expand All @@ -33,7 +33,7 @@ struct GitlabLoginState {
pub(crate) async fn get(
State(state): State<Arc<AppState>>,
maybe_author: Option<Auth>,
mut session: WritableSession,
session: Session,
) -> Result<Either<(StatusCode, Html<String>), Redirect>, FrontendError> {
if let Some(Auth(author)) = maybe_author {
let state = state.as_ref();
Expand Down
18 changes: 7 additions & 11 deletions crates/alexandrie/src/frontend/account/login.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use std::num::NonZeroU32;
use std::sync::Arc;
use std::time::Duration;

use axum::extract::State;
use axum::http::StatusCode;
use axum::response::Redirect;
use axum::Form;
use axum_extra::either::Either;
use axum_extra::response::Html;
use axum_sessions::extractors::WritableSession;
use diesel::prelude::*;
use json::json;
use ring::pbkdf2;
use serde::{Deserialize, Serialize};
use tower_sessions::Session;

use crate::config::AppState;
use crate::db::schema::*;
Expand Down Expand Up @@ -41,16 +40,13 @@ pub(crate) struct LoginForm {
pub(crate) async fn get(
State(state): State<Arc<AppState>>,
maybe_author: Option<Auth>,
mut session: WritableSession,
session: Session,
) -> Result<(StatusCode, Html<String>), FrontendError> {
if let Some(Auth(author)) = maybe_author {
return common::already_logged_in(state.as_ref(), author);
}

let flash_message: Option<LoginFlashMessage> = session.get(LOGIN_FLASH);
if flash_message.is_some() {
session.remove(LOGIN_FLASH);
}
let flash_message: Option<LoginFlashMessage> = session.remove(LOGIN_FLASH)?;

let engine = &state.frontend.handlebars;
let auth = &state.frontend.config.auth;
Expand Down Expand Up @@ -78,7 +74,7 @@ pub(crate) async fn get(
pub(crate) async fn post(
State(state): State<Arc<AppState>>,
maybe_author: Option<Auth>,
mut session: WritableSession,
session: Session,
Form(form): Form<LoginForm>,
) -> Result<Either<(StatusCode, Html<String>), Redirect>, FrontendError> {
if maybe_author.is_some() {
Expand Down Expand Up @@ -152,13 +148,13 @@ pub(crate) async fn post(

//? Get the maximum duration of the session.
let expiry = match form.remember.as_deref() {
Some("on") => Duration::from_secs(2_592_000), // 30 days
_ => Duration::from_secs(86_400), // 1 day / 24 hours
Some("on") => time::Duration::seconds(2_592_000), // 30 days
_ => time::Duration::seconds(86_400), // 1 day / 24 hours
};

//? Set the user's session.
session.insert("author.id", author_id)?;
session.expire_in(expiry);
session.set_expiration_time_from_max_age(expiry);

Ok(Either::E2(Redirect::to("/")))
});
Expand Down
6 changes: 3 additions & 3 deletions crates/alexandrie/src/frontend/account/logout.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use axum::response::Redirect;
use axum_sessions::extractors::WritableSession;
use tower_sessions::Session;

pub(crate) async fn get(mut session: WritableSession) -> Redirect {
session.remove("author.id");
pub(crate) async fn get(session: Session) -> Redirect {
session.remove_value("author.id");
Redirect::to("/")
}
9 changes: 3 additions & 6 deletions crates/alexandrie/src/frontend/account/manage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ use std::sync::Arc;
use axum::extract::State;
use axum::http::StatusCode;
use axum_extra::response::Html;
use axum_sessions::extractors::WritableSession;
use diesel::dsl as sql;
use diesel::prelude::*;
use json::json;
use serde::{Deserialize, Serialize};
use tower_sessions::Session;

/// Password management routes (eg. "/account/manage/password").
pub mod passwd;
Expand Down Expand Up @@ -43,7 +43,7 @@ enum ManageFlashMessage {
pub(crate) async fn get(
State(state): State<Arc<AppState>>,
maybe_author: Option<Auth>,
mut session: WritableSession,
session: Session,
) -> Result<(StatusCode, Html<String>), FrontendError> {
let Some(Auth(author)) = maybe_author else {
let state = state.as_ref();
Expand Down Expand Up @@ -71,10 +71,7 @@ pub(crate) async fn get(
.filter(author_tokens::author_id.eq(author.id))
.load::<AuthorToken>(conn)?;

let flash_message: Option<ManageFlashMessage> = session.get(ACCOUNT_MANAGE_FLASH);
if flash_message.is_some() {
session.remove(ACCOUNT_MANAGE_FLASH);
}
let flash_message: Option<ManageFlashMessage> = session.remove(ACCOUNT_MANAGE_FLASH)?;

let engine = &state.frontend.handlebars;
let context = json!({
Expand Down
4 changes: 2 additions & 2 deletions crates/alexandrie/src/frontend/account/manage/passwd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use std::sync::Arc;
use axum::extract::State;
use axum::response::Redirect;
use axum::Form;
use axum_sessions::extractors::WritableSession;
use diesel::prelude::*;
use ring::digest as hasher;
use ring::pbkdf2;
use serde::{Deserialize, Serialize};
use tower_sessions::Session;

use crate::config::AppState;
use crate::db::schema::*;
Expand All @@ -28,7 +28,7 @@ pub(crate) struct ChangePasswordForm {
pub(crate) async fn post(
State(state): State<Arc<AppState>>,
maybe_author: Option<Auth>,
mut session: WritableSession,
session: Session,
Form(form): Form<ChangePasswordForm>,
) -> Result<Redirect, FrontendError> {
let Some(Auth(author)) = maybe_author else {
Expand Down
Loading
Loading