Compare commits
1 commit
main
...
feat/admin
Author | SHA1 | Date | |
---|---|---|---|
48dbb79323 |
13 changed files with 143 additions and 4 deletions
|
@ -1,2 +1,4 @@
|
|||
[default]
|
||||
secret_key = "8STDFCStGMYGoOq8RJf3JJXsg4p6wZVAph50R3Fbq6U="
|
||||
[default.databases.dolltags]
|
||||
url = "postgres://postgres:woofwoof@localhost/dolltags"
|
||||
|
|
|
@ -300,7 +300,7 @@ input#ident {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.profile .ident,
|
||||
.ident,
|
||||
.handler {
|
||||
text-transform: uppercase;
|
||||
font-size: .8em;
|
||||
|
|
2
migrations/6_admin_status.sql
Normal file
2
migrations/6_admin_status.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
alter table users
|
||||
add column is_admin boolean not null default false;
|
|
@ -2,10 +2,12 @@ use std::str::FromStr;
|
|||
|
||||
use rocket::{
|
||||
http::{CookieJar, Status},
|
||||
outcome::try_outcome,
|
||||
request::{FromRequest, Outcome},
|
||||
response::Redirect,
|
||||
Request,
|
||||
};
|
||||
use rocket_dyn_templates::{context, Template};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::db::{
|
||||
|
@ -88,8 +90,33 @@ impl<'a> FromRequest<'a> for User {
|
|||
}
|
||||
}
|
||||
|
||||
/// A specialization of User as a [`FromRequest`] guard to only trigger when the user is logged in,
|
||||
/// checked as okay in DB, and is marked as an admin user.
|
||||
#[derive(Debug)]
|
||||
pub struct Admin(pub User);
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'a> FromRequest<'a> for Admin {
|
||||
type Error = SessionInternalFailure;
|
||||
|
||||
async fn from_request(req: &'a Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
let user = try_outcome!(req.guard::<User>().await);
|
||||
|
||||
if user.is_admin {
|
||||
Outcome::Success(Admin(user))
|
||||
} else {
|
||||
Outcome::Forward(Status::Forbidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[catch(401)]
|
||||
pub fn unauthorized(req: &Request) -> Redirect {
|
||||
let next = req.uri().to_string();
|
||||
Redirect::to(uri!(crate::routes::form::accounts::show_login(Some(&next))))
|
||||
}
|
||||
|
||||
#[catch(403)]
|
||||
pub fn forbidden(_: &Request) -> Template {
|
||||
Template::render("error/forbidden", context! {})
|
||||
}
|
||||
|
|
21
src/db/admin.rs
Normal file
21
src/db/admin.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use super::schema::{ServiceStatus, TrxHook};
|
||||
|
||||
/// Aggregates info on the current service status, incl. accounts and tags
|
||||
pub async fn get_service_status(trx: &mut TrxHook<'_>) -> sqlx::Result<ServiceStatus> {
|
||||
let active_accounts_count =
|
||||
sqlx::query_scalar!("select count(id) from users where id != '00000000-0000-0000-0000-000000000000' and enabled is true")
|
||||
.fetch_one(&mut **trx)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
let active_tags_count =
|
||||
sqlx::query_scalar!("select count(id) from doll_profiles where archived_at is null")
|
||||
.fetch_one(&mut **trx)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
Ok(ServiceStatus {
|
||||
active_accounts_count,
|
||||
active_tags_count,
|
||||
})
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod admin;
|
||||
pub mod doll;
|
||||
pub mod migrate;
|
||||
pub mod schema;
|
||||
|
|
|
@ -95,4 +95,13 @@ pub struct User {
|
|||
pub email: Option<String>,
|
||||
|
||||
pub enabled: bool,
|
||||
pub is_admin: bool,
|
||||
}
|
||||
|
||||
/// The service status aggregate
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct ServiceStatus {
|
||||
pub active_accounts_count: i64,
|
||||
pub active_tags_count: i64,
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use rocket::fairing::AdHoc;
|
|||
use rocket::fs::{relative, FileServer};
|
||||
use rocket_db_pools::Database;
|
||||
use routes::form::accounts;
|
||||
use routes::{account, error_handlers, form, public};
|
||||
use routes::{account, admin, error_handlers, form, public};
|
||||
|
||||
pub mod auth;
|
||||
pub mod db;
|
||||
|
@ -50,7 +50,11 @@ fn rocket() -> _ {
|
|||
.attach(AdHoc::try_on_ignite("SQLx migrations", run_migrations))
|
||||
.register(
|
||||
"/",
|
||||
catchers![error_handlers::not_found, session::unauthorized],
|
||||
catchers![
|
||||
error_handlers::not_found,
|
||||
session::unauthorized,
|
||||
session::forbidden
|
||||
],
|
||||
)
|
||||
.mount("/assets", FileServer::from(assets_path))
|
||||
.mount(
|
||||
|
@ -71,6 +75,7 @@ fn rocket() -> _ {
|
|||
account::export_data,
|
||||
],
|
||||
)
|
||||
.mount("/admin", routes![admin::index,])
|
||||
.mount(
|
||||
"/",
|
||||
routes![
|
||||
|
|
16
src/pages.rs
16
src/pages.rs
|
@ -10,7 +10,7 @@ use rocket::{
|
|||
use rocket_dyn_templates::{tera::try_get_value, Template};
|
||||
use serde_json::{to_value, Value};
|
||||
|
||||
use crate::{auth::session::Session, ids};
|
||||
use crate::{auth::session::Session, db::schema::User, ids};
|
||||
|
||||
pub fn init_templates() -> impl Fairing {
|
||||
Template::custom(|engines| {
|
||||
|
@ -32,10 +32,23 @@ pub fn init_templates() -> impl Fairing {
|
|||
pub struct CommonTemplateState {
|
||||
/// true if the user is logged in (doesn't check the DB, instead uses [`Session`])
|
||||
pub logged_in: bool,
|
||||
/// true if the user is an admin (defaults to false, call [`CommonTemplateState::for_user`] to add the additional data)
|
||||
pub is_admin: bool,
|
||||
/// feature flag - disables the UI for it since it's not implemeted yet.
|
||||
pub forgot_password_implemented: bool,
|
||||
}
|
||||
|
||||
impl CommonTemplateState {
|
||||
/// Populates the state struct with the additional knowledge provided by the [`User`] passed in parameter,
|
||||
/// such as if the current context is an admin-privileges-enabled one (which need a DB lookup).
|
||||
pub fn for_user(self, user: &User) -> Self {
|
||||
CommonTemplateState {
|
||||
is_admin: user.is_admin,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for CommonTemplateState {
|
||||
type Error = ();
|
||||
|
@ -45,6 +58,7 @@ impl<'r> FromRequest<'r> for CommonTemplateState {
|
|||
|
||||
Outcome::Success(CommonTemplateState {
|
||||
logged_in: session_state.0.is_some(),
|
||||
is_admin: false,
|
||||
forgot_password_implemented: false,
|
||||
})
|
||||
}
|
||||
|
|
26
src/routes/admin.rs
Normal file
26
src/routes/admin.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use rocket_dyn_templates::{context, Template};
|
||||
use sqlx::Acquire;
|
||||
|
||||
use crate::{
|
||||
auth::session::Admin,
|
||||
db::{admin, schema::DollTagsDb},
|
||||
pages::CommonTemplateState,
|
||||
};
|
||||
|
||||
use super::error_handlers::PageResult;
|
||||
|
||||
#[get("/")]
|
||||
pub async fn index(meta: CommonTemplateState, mut db: DollTagsDb, user: Admin) -> PageResult {
|
||||
let mut trx = db.begin().await?;
|
||||
let service_status = admin::get_service_status(&mut trx).await?;
|
||||
trx.commit().await?;
|
||||
|
||||
Ok(Template::render(
|
||||
"admin/index",
|
||||
context! {
|
||||
meta: meta.for_user(&user.0),
|
||||
service_status,
|
||||
},
|
||||
)
|
||||
.into())
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
pub mod account;
|
||||
pub mod admin;
|
||||
pub mod error_handlers;
|
||||
pub mod form;
|
||||
pub mod public;
|
||||
|
|
25
templates/admin/index.html.tera
Normal file
25
templates/admin/index.html.tera
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% extends "base" %}
|
||||
{% block title %}Admin panel - {% endblock title %}
|
||||
{% block main %}
|
||||
<aside>
|
||||
<p class="subnav">
|
||||
<a href="/account">Back to account</a>
|
||||
<a href="/logout">Log out</a>
|
||||
</p>
|
||||
</aside>
|
||||
|
||||
<section>
|
||||
<h2>Service status</h2>
|
||||
|
||||
<div class="dual-fields raised center">
|
||||
<div>
|
||||
<p class="ident">Active accounts</p>
|
||||
<p class="b">{{service_status.active_accounts_count}}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="ident">Active tag profiles</p>
|
||||
<p class="b">{{service_status.active_tags_count}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock main %}
|
6
templates/error/forbidden.html.tera
Normal file
6
templates/error/forbidden.html.tera
Normal file
|
@ -0,0 +1,6 @@
|
|||
{% extends "base" %}
|
||||
{% block title %}Admin page access forbidden - {% endblock title %}
|
||||
{% block main %}
|
||||
<h1>This page can only be accessed by an admin, which you don't seem to be, sorry.</h1>
|
||||
<p>You can go <a href="/account">back to your account</a> or <a href="/logout">log out</a> if you wish.</p>
|
||||
{% endblock main %}
|
Loading…
Add table
Reference in a new issue