mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Create society service with basic functionality and auth #118
This commit is contained in:
65
rc_society/src/web/dashboard.rs
Normal file
65
rc_society/src/web/dashboard.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use actix_web::{get, web::Data, Responder, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
const FORM_NAME: &str = "dashboard";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RenderData {
|
||||
// TODO
|
||||
display_name: String,
|
||||
public_id: String,
|
||||
debug: DebugData,
|
||||
perms: PermissionData,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DebugData {
|
||||
user_id: i32,
|
||||
creation_time_unix: i64,
|
||||
creation_time_iso: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct PermissionData {
|
||||
r#mod: bool,
|
||||
admin: bool,
|
||||
dev: bool,
|
||||
royal: bool,
|
||||
banned: bool,
|
||||
}
|
||||
|
||||
#[get("/dashboard")]
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match super::try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
super::LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
super::LoginReturn::Success(user) => {
|
||||
// TODO
|
||||
let creation_time = user.creation();
|
||||
let creation_time_chrono = chrono::DateTime::<chrono::Utc>::from_timestamp_secs(creation_time).unwrap_or_default();
|
||||
Ok(super::render_ok(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
debug: DebugData {
|
||||
user_id: user.account_id(),
|
||||
creation_time_unix: creation_time,
|
||||
creation_time_iso: creation_time_chrono.to_rfc3339(),
|
||||
},
|
||||
perms: PermissionData {
|
||||
r#mod: user.is_mod(),
|
||||
admin: user.is_admin(),
|
||||
dev: user.is_dev(),
|
||||
royal: user.is_royal(),
|
||||
banned: user.is_banned(),
|
||||
}
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
11
rc_society/src/web/favicon.rs
Normal file
11
rc_society/src/web/favicon.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use actix_web::{get, web::Data, Responder};
|
||||
|
||||
async fn favicon_impl(cli_args: Data<crate::cli::LoadedArgs>) -> impl Responder {
|
||||
let path = std::path::PathBuf::from(&cli_args.assets).join("favicon.jpg");
|
||||
actix_files::NamedFile::open_async(path).await
|
||||
}
|
||||
|
||||
#[get("/favicon.ico")]
|
||||
pub async fn favicon_standard(cli_args: Data<crate::cli::LoadedArgs>) -> impl Responder {
|
||||
favicon_impl(cli_args).await
|
||||
}
|
||||
77
rc_society/src/web/index.rs
Normal file
77
rc_society/src/web/index.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use actix_web::{get, web::Data, Responder, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
pub const FORM_NAME: &str = "index";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RenderData {
|
||||
is_logged_in: bool,
|
||||
display_name: Option<String>,
|
||||
server: ServerDetails,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ServerDetails {
|
||||
domain: String,
|
||||
cdn: String,
|
||||
auth: String,
|
||||
min_version: i32,
|
||||
server_version: String,
|
||||
start_time_iso: String,
|
||||
start_time_unix: i64,
|
||||
}
|
||||
|
||||
fn server_details(conf: &oj_rc_core::persist::config::ServerConfig) -> ServerDetails {
|
||||
let start_time_unix = crate::START_TIME.load(std::sync::atomic::Ordering::Relaxed);
|
||||
let start_time_chrono = chrono::DateTime::<chrono::Utc>::from_timestamp_secs(start_time_unix).unwrap_or_default();
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
let git_version = git_version::git_version!(args = ["--always", "--dirty=+"]);
|
||||
let server_version = format!("{}:{}", version, git_version);
|
||||
ServerDetails {
|
||||
domain: conf.domain.clone(),
|
||||
cdn: conf.cdn_url.clone(),
|
||||
auth: conf.auth_url.clone(),
|
||||
min_version: conf.minimum_version,
|
||||
server_version,
|
||||
start_time_unix,
|
||||
start_time_iso: start_time_chrono.to_rfc3339(),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, server_config: Data<oj_rc_core::persist::config::ServerConfig>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
let server_info = server_details(server_config.as_ref());
|
||||
if let Some(user) = user_opt {
|
||||
match super::try_auth_user(Some(user), auth.as_ref(), &req).await? {
|
||||
super::LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
super::LoginReturn::Success(user) => {
|
||||
Ok(super::render_ok(
|
||||
RenderData {
|
||||
is_logged_in: true,
|
||||
display_name: Some(user.display_name().to_owned()),
|
||||
server: server_info,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(super::render_ok(
|
||||
RenderData {
|
||||
is_logged_in: false,
|
||||
display_name: None,
|
||||
server: server_info,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
66
rc_society/src/web/login.rs
Normal file
66
rc_society/src/web/login.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use actix_web::{get, post, web::{Data, Form, Redirect}, Responder, HttpRequest, HttpMessage};
|
||||
use actix_identity::Identity;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
const FORM_NAME: &str = "login";
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
struct LoginForm {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[post("/login")]
|
||||
pub async fn form_submit(form: Form<LoginForm>, auth: Data<Box<oj_rc_core::UserImpl>>, handlebars_ref: Data<handlebars::Handlebars<'_>>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
use oj_rc_core::UserAuthenticator;
|
||||
let auth_result = auth.login(oj_rc_core::persist::user::UserAuthInfo::Username {
|
||||
username: form.username.to_owned(),
|
||||
password: form.password.to_owned(),
|
||||
}).await;
|
||||
match auth_result {
|
||||
Ok(user) => {
|
||||
let resp = Redirect::to("/dashboard")
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body();
|
||||
Identity::login(&req.extensions(), user.response.token)?;
|
||||
Ok(resp)
|
||||
},
|
||||
Err(e) => {
|
||||
Ok(super::render_err(form.0, e.message, handlebars_ref.as_ref(), FORM_NAME)
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/login")]
|
||||
pub async fn form_load(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
if let Some(user) = user_opt {
|
||||
let user_id = user.id()?;
|
||||
use oj_rc_core::UserAuthenticator;
|
||||
if auth.verify(user_id).await.is_ok() {
|
||||
Ok(Redirect::to("/dashboard")
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body())
|
||||
} else {
|
||||
Ok(super::render_err(
|
||||
LoginForm::default(),
|
||||
"Invalid login token".to_owned(),
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Ok(super::render_ok(
|
||||
LoginForm::default(),
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
)
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
71
rc_society/src/web/mod.rs
Normal file
71
rc_society/src/web/mod.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
pub mod dashboard;
|
||||
pub mod login;
|
||||
pub mod favicon;
|
||||
pub mod index;
|
||||
|
||||
use serde::Serialize;
|
||||
use actix_web::{web::{Html, Redirect}, Responder};
|
||||
|
||||
fn version_string() -> String {
|
||||
let name = env!("CARGO_PKG_NAME");
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
//let license = env!("CARGO_PKG_LICENSE");
|
||||
//let repo = env!("CARGO_PKG_REPOSITORY");
|
||||
format!("OpenJam {} {}", name, version)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Context<T: Serialize> {
|
||||
form: T,
|
||||
error: Option<String>,
|
||||
version: String,
|
||||
source_url: String,
|
||||
}
|
||||
|
||||
fn render_ok<T: Serialize>(form: T, renderer: &handlebars::Handlebars<'_>, form_name: &str) -> Html {
|
||||
let rendered = renderer.render(form_name, &Context {
|
||||
form,
|
||||
error: None,
|
||||
version: version_string(),
|
||||
source_url: env!("CARGO_PKG_REPOSITORY").to_owned(),
|
||||
}).unwrap();
|
||||
Html::new(rendered)
|
||||
}
|
||||
|
||||
fn render_err<T: Serialize>(form: T, error: String , renderer: &handlebars::Handlebars<'_>, form_name: &str) -> Html {
|
||||
let rendered = renderer.render(form_name, &Context {
|
||||
form,
|
||||
error: Some(error),
|
||||
version: version_string(),
|
||||
source_url: env!("CARGO_PKG_REPOSITORY").to_owned(),
|
||||
}).unwrap();
|
||||
Html::new(rendered)
|
||||
}
|
||||
|
||||
enum LoginReturn {
|
||||
Success(Box<dyn oj_rc_core::persist::user::WebUser>),
|
||||
AuthFail(actix_web::HttpResponse<actix_web::body::BoxBody>),
|
||||
}
|
||||
|
||||
async fn try_auth_user(user_opt: Option<actix_identity::Identity>, auth: &oj_rc_core::UserImpl, req: &actix_web::HttpRequest) -> Result<LoginReturn, actix_web::Error> {
|
||||
if let Some(user) = user_opt {
|
||||
let user_id = user.id()?;
|
||||
match <oj_rc_core::UserImpl as oj_rc_core::UserProvider<()>>::web_authenticate(auth, user_id.clone()).await {
|
||||
Ok(user) => Ok(LoginReturn::Success(user)),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to login with token {}: {} ({:?})", user_id, e.message, e.code);
|
||||
Ok(LoginReturn::AuthFail(
|
||||
Redirect::to("/login")
|
||||
.respond_to(req)
|
||||
.map_into_boxed_body()
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(LoginReturn::AuthFail(
|
||||
Redirect::to("/login")
|
||||
.respond_to(req)
|
||||
.map_into_boxed_body()
|
||||
))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user