basic support for user authentication (currently does nothing)
This commit is contained in:
parent
812eee1a5f
commit
56064c6259
9 changed files with 279 additions and 8 deletions
138
Cargo.lock
generated
138
Cargo.lock
generated
|
@ -241,6 +241,17 @@ version = "1.0.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.86"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atomic-waker"
|
name = "atomic-waker"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
|
@ -634,6 +645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
|
checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum-core",
|
"axum-core",
|
||||||
|
"axum-macros",
|
||||||
"bytes",
|
"bytes",
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
@ -681,6 +693,17 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-macros"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.74"
|
version = "0.3.74"
|
||||||
|
@ -869,6 +892,17 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
"time",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -1191,6 +1225,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tera",
|
"tera",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower-sessions",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
@ -1204,6 +1239,20 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
|
@ -1211,6 +1260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1219,6 +1269,23 @@ version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
|
@ -1238,9 +1305,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1910,6 +1980,7 @@ checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3189,6 +3260,22 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-cookies"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36"
|
||||||
|
dependencies = [
|
||||||
|
"axum-core",
|
||||||
|
"cookie",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.2.0",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -3201,6 +3288,57 @@ version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43a05911f23e8fae446005fe9b7b97e66d95b6db589dc1c4d59f6a2d4d4927d3"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"http 1.2.0",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tower-cookies",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tower-sessions-core",
|
||||||
|
"tower-sessions-memory-store",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions-core"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce8cce604865576b7751b7a6bc3058f754569a60d689328bb74c52b1d87e355b"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"axum-core",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"futures",
|
||||||
|
"http 1.2.0",
|
||||||
|
"parking_lot",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.9",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions-memory-store"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb05909f2e1420135a831dd5df9f5596d69196d0a64c3499ca474c4bd3d33242"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tower-sessions-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.41"
|
version = "0.1.41"
|
||||||
|
|
|
@ -14,7 +14,7 @@ url = "2"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
axum = { version = "0.8", features = ["form"] }
|
axum = { version = "0.8", features = ["form", "macros"] }
|
||||||
tera = "1"
|
tera = "1"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
actix-files = "0.6"
|
actix-files = "0.6"
|
||||||
|
@ -24,6 +24,7 @@ include_dir = "0.7"
|
||||||
aws-config = { version = "1.1.7", features = ["behavior-version-latest"] }
|
aws-config = { version = "1.1.7", features = ["behavior-version-latest"] }
|
||||||
aws-sdk-s3 = "1.66.0"
|
aws-sdk-s3 = "1.66.0"
|
||||||
new_mime_guess = "4"
|
new_mime_guess = "4"
|
||||||
|
tower-sessions = "0.14"
|
||||||
|
|
||||||
[profile.profiling]
|
[profile.profiling]
|
||||||
inherits = "dev"
|
inherits = "dev"
|
||||||
|
|
|
@ -60,3 +60,4 @@ Environment variables read when `STORAGE_BACKEND=s3`:
|
||||||
- auth: add support for connecting to the forge using oauth?
|
- auth: add support for connecting to the forge using oauth?
|
||||||
- improve error handling? currently the app will panic if writing to the storage
|
- improve error handling? currently the app will panic if writing to the storage
|
||||||
backend fails. Can we do better?
|
backend fails. Can we do better?
|
||||||
|
- error reporting when the auth token is not working/outdated and background jobs fail
|
||||||
|
|
File diff suppressed because one or more lines are too long
59
src/main.rs
59
src/main.rs
|
@ -3,7 +3,7 @@ use axum::{
|
||||||
extract::{OriginalUri, Path, Query, State},
|
extract::{OriginalUri, Path, Query, State},
|
||||||
http::{header, StatusCode},
|
http::{header, StatusCode},
|
||||||
response::{Html, IntoResponse},
|
response::{Html, IntoResponse},
|
||||||
routing::get,
|
routing::{get, post},
|
||||||
Form, Router,
|
Form, Router,
|
||||||
};
|
};
|
||||||
use forgejo_api::{Auth, Forgejo};
|
use forgejo_api::{Auth, Forgejo};
|
||||||
|
@ -14,6 +14,7 @@ use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
|
use tower_sessions::{MemoryStore, Session, SessionManagerLayer};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
mod classifier;
|
mod classifier;
|
||||||
|
@ -21,6 +22,7 @@ mod data;
|
||||||
mod db;
|
mod db;
|
||||||
mod email;
|
mod email;
|
||||||
mod scrape;
|
mod scrape;
|
||||||
|
mod session;
|
||||||
mod storage;
|
mod storage;
|
||||||
mod userdb;
|
mod userdb;
|
||||||
mod util;
|
mod util;
|
||||||
|
@ -29,6 +31,7 @@ mod workers;
|
||||||
use data::*;
|
use data::*;
|
||||||
use db::Db;
|
use db::Db;
|
||||||
use email::SmtpConfig;
|
use email::SmtpConfig;
|
||||||
|
use session::AuthenticatedUser;
|
||||||
use storage::Storage;
|
use storage::Storage;
|
||||||
use userdb::{IsSpam, UserDb};
|
use userdb::{IsSpam, UserDb};
|
||||||
use util::env_var;
|
use util::env_var;
|
||||||
|
@ -280,9 +283,11 @@ fn approx_score(score: f32) -> ApproxScore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
async fn get_index(
|
async fn get_index(
|
||||||
State(data): State<Arc<AppState>>,
|
State(data): State<Arc<AppState>>,
|
||||||
Query(q): Query<SortSetting>,
|
Query(q): Query<SortSetting>,
|
||||||
|
_user: AuthenticatedUser,
|
||||||
OriginalUri(uri): OriginalUri,
|
OriginalUri(uri): OriginalUri,
|
||||||
) -> Result<Html<String>, AppError> {
|
) -> Result<Html<String>, AppError> {
|
||||||
eprintln!("GET {}", uri);
|
eprintln!("GET {}", uri);
|
||||||
|
@ -347,8 +352,9 @@ async fn get_index(
|
||||||
|
|
||||||
async fn post_classified(
|
async fn post_classified(
|
||||||
State(data): State<Arc<AppState>>,
|
State(data): State<Arc<AppState>>,
|
||||||
Form(form): Form<HashMap<i64, String>>,
|
_user: AuthenticatedUser,
|
||||||
OriginalUri(uri): OriginalUri,
|
OriginalUri(uri): OriginalUri,
|
||||||
|
Form(form): Form<HashMap<i64, String>>,
|
||||||
overwrite: bool,
|
overwrite: bool,
|
||||||
) -> Result<impl IntoResponse, AppError> {
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
eprintln!("POST {}", uri);
|
eprintln!("POST {}", uri);
|
||||||
|
@ -381,22 +387,25 @@ async fn post_classified(
|
||||||
|
|
||||||
async fn post_classified_index(
|
async fn post_classified_index(
|
||||||
data: State<Arc<AppState>>,
|
data: State<Arc<AppState>>,
|
||||||
|
user: AuthenticatedUser,
|
||||||
uri: OriginalUri,
|
uri: OriginalUri,
|
||||||
form: Form<HashMap<i64, String>>,
|
form: Form<HashMap<i64, String>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
post_classified(data, form, uri, false).await
|
post_classified(data, user, uri, form, false).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post_classified_edit(
|
async fn post_classified_edit(
|
||||||
data: State<Arc<AppState>>,
|
data: State<Arc<AppState>>,
|
||||||
|
user: AuthenticatedUser,
|
||||||
uri: OriginalUri,
|
uri: OriginalUri,
|
||||||
form: Form<HashMap<i64, String>>,
|
form: Form<HashMap<i64, String>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
post_classified(data, form, uri, true).await
|
post_classified(data, user, uri, form, true).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_classified(
|
async fn get_classified(
|
||||||
State(data): State<Arc<AppState>>,
|
State(data): State<Arc<AppState>>,
|
||||||
|
_user: AuthenticatedUser,
|
||||||
OriginalUri(uri): OriginalUri,
|
OriginalUri(uri): OriginalUri,
|
||||||
) -> Result<Html<String>, AppError> {
|
) -> Result<Html<String>, AppError> {
|
||||||
eprintln!("GET {}", uri);
|
eprintln!("GET {}", uri);
|
||||||
|
@ -442,6 +451,41 @@ async fn get_static_(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_auth(
|
||||||
|
State(data): State<Arc<AppState>>,
|
||||||
|
OriginalUri(uri): OriginalUri,
|
||||||
|
) -> Result<Html<String>, AppError> {
|
||||||
|
eprintln!("GET {}", uri);
|
||||||
|
let mut context = tera::Context::new();
|
||||||
|
context.insert("forge_url", &data.config.forge_url.to_string());
|
||||||
|
let page = TEMPLATES.render("auth.html", &context)?;
|
||||||
|
Ok(Html::from(page))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct AuthForm {
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_auth(
|
||||||
|
OriginalUri(uri): OriginalUri,
|
||||||
|
session: Session,
|
||||||
|
Form(form): Form<AuthForm>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
eprintln!("POST {}", uri);
|
||||||
|
session
|
||||||
|
.insert(session::TOKEN_KEY, &form.token)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
(StatusCode::SEE_OTHER, [(header::LOCATION, "/")])
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_logout(OriginalUri(uri): OriginalUri, session: Session) -> impl IntoResponse {
|
||||||
|
eprintln!("POST {}", uri);
|
||||||
|
session.flush().await.unwrap();
|
||||||
|
(StatusCode::SEE_OTHER, [(header::LOCATION, "/auth")])
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let config = Arc::new(Config::from_env().await?);
|
let config = Arc::new(Config::from_env().await?);
|
||||||
|
@ -491,6 +535,8 @@ async fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
println!("Listening on http://{}", &config.bind_addr);
|
println!("Listening on http://{}", &config.bind_addr);
|
||||||
|
|
||||||
|
let session_store = MemoryStore::default();
|
||||||
|
let session_layer = SessionManagerLayer::new(session_store);
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(get_index).post(post_classified_index))
|
.route("/", get(get_index).post(post_classified_index))
|
||||||
.route(
|
.route(
|
||||||
|
@ -498,7 +544,10 @@ async fn main() -> anyhow::Result<()> {
|
||||||
get(get_classified).post(post_classified_edit),
|
get(get_classified).post(post_classified_edit),
|
||||||
)
|
)
|
||||||
.route("/static/{*filename}", get(get_static_))
|
.route("/static/{*filename}", get(get_static_))
|
||||||
.with_state(shared_state);
|
.route("/auth", get(get_auth).post(post_auth))
|
||||||
|
.route("/logout", post(post_logout))
|
||||||
|
.with_state(shared_state)
|
||||||
|
.layer(session_layer);
|
||||||
|
|
||||||
let webserver = {
|
let webserver = {
|
||||||
let listener = tokio::net::TcpListener::bind(&config.bind_addr)
|
let listener = tokio::net::TcpListener::bind(&config.bind_addr)
|
||||||
|
|
46
src/session.rs
Normal file
46
src/session.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use axum::extract::FromRequestParts;
|
||||||
|
use axum::http::{header, request::Parts, HeaderMap, HeaderValue, StatusCode};
|
||||||
|
use tower_sessions::Session;
|
||||||
|
|
||||||
|
#[allow(dead_code)] // TODO: WIP
|
||||||
|
pub struct AuthenticatedUser {
|
||||||
|
session: Session,
|
||||||
|
api_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const TOKEN_KEY: &str = "API_TOKEN";
|
||||||
|
|
||||||
|
impl<S> FromRequestParts<S> for AuthenticatedUser
|
||||||
|
where
|
||||||
|
S: Send + Sync,
|
||||||
|
{
|
||||||
|
type Rejection = (StatusCode, HeaderMap, String);
|
||||||
|
|
||||||
|
async fn from_request_parts(req: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
let session = match Session::from_request_parts(req, state).await {
|
||||||
|
Ok(session) => session,
|
||||||
|
Err((status, body)) => return Err((status, HeaderMap::new(), body.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let api_token: Option<String> = session.get(TOKEN_KEY).await.unwrap(); // TODO: is it safe to [unwrap] here?
|
||||||
|
|
||||||
|
match api_token {
|
||||||
|
Some(api_token) => Ok(Self { session, api_token }),
|
||||||
|
None => {
|
||||||
|
// Unclear that this case can actually happen, but in doubt,
|
||||||
|
// clear the session and force to re-auth.
|
||||||
|
// It is safe to [unwrap] the result here because we use
|
||||||
|
// [MemoryStore] as the underlying store, on which [delete]
|
||||||
|
// never fails.
|
||||||
|
session.flush().await.unwrap();
|
||||||
|
Err((
|
||||||
|
StatusCode::SEE_OTHER,
|
||||||
|
HeaderMap::from_iter(
|
||||||
|
[(header::LOCATION, HeaderValue::from_static("/auth"))].into_iter(),
|
||||||
|
),
|
||||||
|
"client is not authenticated, redirecting to /auth".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -83,3 +83,14 @@ input.radio-legit:checked + label {
|
||||||
.score-Low {
|
.score-Low {
|
||||||
background: #5fd770;
|
background: #5fd770;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.aslink {
|
||||||
|
display: inline;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font: inherit;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
background: transparent;
|
||||||
|
color: currentColor;
|
||||||
|
}
|
||||||
|
|
22
templates/auth.html
Normal file
22
templates/auth.html
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Authentication{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Authentication required</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To obtain a suitable API token, you must login to Forgejo using an
|
||||||
|
administrator account and go to <a href="{{forge_url}}user/settings/applications">{{forge_url}}user/settings/applications</a>.
|
||||||
|
Then, create an API token with access to all organizations and repos, allowing
|
||||||
|
read and writet access to: "admin", "misc", "organization", "issue", "repository" and "user". Store
|
||||||
|
the resulting token safely (e.g. in your password manager).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
<label for="token">Forgejo API token:</label>
|
||||||
|
<input id="token" name="token" type="password" required/>
|
||||||
|
|
||||||
|
<input type="submit" value="Submit" class="button"/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock content %}
|
|
@ -8,7 +8,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<a href="/classified">Edit classified users</a>
|
<a href="/classified">Edit classified users</a> |
|
||||||
|
<form action="/logout" method="post" style="display: inline">
|
||||||
|
<button name="logout" value="logout" class="aslink">Log out</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sort-options">
|
<div class="sort-options">
|
||||||
|
|
Loading…
Add table
Reference in a new issue