mirror of
https://github.com/Jackzmc/storage.git
synced 2025-05-05 17:23:20 +00:00
Compare commits
3 commits
d1cf5d3038
...
ed5cbfa848
Author | SHA1 | Date | |
---|---|---|---|
ed5cbfa848 | |||
2f6c729dc3 | |||
4693a6c599 |
16 changed files with 1067 additions and 40 deletions
1
.env.sample
Normal file
1
.env.sample
Normal file
|
@ -0,0 +1 @@
|
|||
DATABASE_URL=postgresql://server:5432/database?user=user&password=password&connectTimeout=30¤tSchema=storage;
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,4 +1,4 @@
|
|||
**/.idea
|
||||
target
|
||||
config
|
||||
.env
|
||||
config.toml
|
||||
.env
|
||||
|
|
871
Cargo.lock
generated
871
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -22,4 +22,5 @@ humanize-bytes = "1.0.6"
|
|||
rocket-session-store = "0.2.1"
|
||||
uuid = { version = "1.16.0", features = ["v4"] }
|
||||
rand = { version = "0.9.0", features = ["thread_rng"] }
|
||||
bcrypt = "0.17.0"
|
||||
bcrypt = "0.17.0"
|
||||
openidconnect = "4.0.0"
|
16
config.sample.toml
Normal file
16
config.sample.toml
Normal file
|
@ -0,0 +1,16 @@
|
|||
[general]
|
||||
listen_ip = "0.0.0.0"
|
||||
listen_port = 80
|
||||
|
||||
[backends.local]
|
||||
path = "/var/tmp/test"
|
||||
|
||||
[auth]
|
||||
enable_registration = true
|
||||
openid_enabled = true
|
||||
# Where the .well-known/openid-configuration exists
|
||||
openid_issuer_url = "https://accounts.example.com"
|
||||
openid_client_id = ""
|
||||
openid_client_secret = ""
|
||||
[smtp]
|
||||
# TODO:
|
|
@ -1,12 +0,0 @@
|
|||
[general]
|
||||
listen_ip = "0.0.0.0"
|
||||
listen_port = 80
|
||||
|
||||
[backends.local]
|
||||
path = "/var/tmp/test"
|
||||
|
||||
[auth]
|
||||
enable_registration = true
|
||||
|
||||
[smtp]
|
||||
# TODO:
|
27
src/config.rs
Normal file
27
src/config.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use rocket::serde::{Serialize,Deserialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
general: GeneralConfig,
|
||||
auth: AuthConfig,
|
||||
smtp: EmailConfig
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct GeneralConfig {
|
||||
pub listen_ip: Option<String>,
|
||||
pub listen_port: Option<u32>
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AuthConfig {
|
||||
pub disable_registration: bool,
|
||||
pub openid_enabled: Option<bool>,
|
||||
pub openid_issuer_url: Option<String>,
|
||||
pub openid_client_id: Option<String>,
|
||||
pub openid_client_secret: Option<String>
|
||||
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct EmailConfig {
|
||||
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
use std::cell::OnceCell;
|
||||
use std::env;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
use rocket::data::ByteUnit;
|
||||
use rocket::serde::Serialize;
|
||||
|
@ -14,7 +16,6 @@ pub const SESSION_LIFETIME_SECONDS: u64 = 3600 * 24 * 14; // 14 days
|
|||
pub const SESSION_COOKIE_NAME: &'static str = "storage-session";
|
||||
|
||||
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct FileConstants<'a> {
|
||||
pub display_options: &'a[&'a str],
|
||||
|
@ -25,3 +26,10 @@ pub const FILE_CONSTANTS: FileConstants = FileConstants {
|
|||
sort_keys: &["name", "last_modified", "size"],
|
||||
};
|
||||
|
||||
|
||||
/// Disables CSRF & password verification for login
|
||||
/// Used for development due to no session persistence
|
||||
pub static DISABLE_LOGIN_CHECK: OnceLock<bool> = OnceLock::new();
|
||||
pub fn init_statics() {
|
||||
DISABLE_LOGIN_CHECK.set(env::var("DANGER_DISABLE_LOGIN_CHECKS").is_ok()).unwrap();
|
||||
}
|
18
src/main.rs
18
src/main.rs
|
@ -24,7 +24,7 @@ use crate::managers::repos::RepoManager;
|
|||
use crate::objs::library::Library;
|
||||
use crate::util::{setup_logger, JsonErrorResponse, ResponseError};
|
||||
use routes::api;
|
||||
use crate::consts::{SESSION_COOKIE_NAME, SESSION_LIFETIME_SECONDS};
|
||||
use crate::consts::{init_statics, SESSION_COOKIE_NAME, SESSION_LIFETIME_SECONDS};
|
||||
use crate::models::user::UserModel;
|
||||
use crate::routes::ui;
|
||||
|
||||
|
@ -39,6 +39,7 @@ mod objs;
|
|||
mod helpers;
|
||||
mod consts;
|
||||
mod guards;
|
||||
mod config;
|
||||
|
||||
pub type DB = Pool<Postgres>;
|
||||
|
||||
|
@ -69,6 +70,7 @@ pub struct GlobalMetadata {
|
|||
async fn rocket() -> _ {
|
||||
setup_logger();
|
||||
dotenvy::dotenv().ok();
|
||||
init_statics();
|
||||
|
||||
trace!("trace");
|
||||
debug!("debug");
|
||||
|
@ -145,17 +147,20 @@ async fn rocket() -> _ {
|
|||
ui::auth::forgot_password::page, ui::auth::forgot_password::handler,
|
||||
])
|
||||
.mount("/", routes![
|
||||
ui::user::index, ui::user::redirect_list_library_files, ui::user::list_library_files, ui::user::get_library_file,
|
||||
ui::user::user_settings, ui::user::index, ui::user::redirect_list_library_files, ui::user::list_library_files, ui::user::get_library_file,
|
||||
])
|
||||
.mount("/", routes![
|
||||
ui::help::about,
|
||||
ui::help::test_get
|
||||
])
|
||||
.mount("/admin", routes![
|
||||
ui::admin::index
|
||||
])
|
||||
.register("/api", catchers![
|
||||
not_found_api,
|
||||
])
|
||||
.register("/", catchers![
|
||||
not_found, not_authorized
|
||||
not_found, not_authorized, forbidden
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -165,6 +170,13 @@ pub fn not_authorized(req: &Request) -> Redirect {
|
|||
Redirect::to(format!("/auth/login?return_to={}", req.uri().path().percent_encode()))
|
||||
}
|
||||
|
||||
#[catch(403)]
|
||||
pub fn forbidden(req: &Request) -> Template {
|
||||
Template::render("errors/403", context! {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
#[catch(404)]
|
||||
fn not_found(req: &Request) -> Template {
|
||||
Template::render("errors/404", context! {
|
||||
|
|
|
@ -10,7 +10,7 @@ use rocket::response::Responder;
|
|||
use rocket::serde::Serialize;
|
||||
use rocket::serde::uuid::Uuid;
|
||||
use sqlx::{query_as, FromRow};
|
||||
use crate::consts::ENCRYPTION_ROUNDS;
|
||||
use crate::consts::{DISABLE_LOGIN_CHECK, ENCRYPTION_ROUNDS};
|
||||
use crate::{LoginSessionData, SessionData, DB};
|
||||
use crate::models::repo::RepoModel;
|
||||
use crate::util::JsonErrorResponse;
|
||||
|
@ -124,7 +124,7 @@ pub async fn validate_user(pool: &DB, email_or_usrname: &str, password: &str) ->
|
|||
return Err(UserAuthError::UserNotFound);
|
||||
};
|
||||
if let Some(db_password) = user.password {
|
||||
if bcrypt::verify(password, &db_password).map_err(|e| UserAuthError::EncryptionError(e))? {
|
||||
if !DISABLE_LOGIN_CHECK.get().unwrap() || bcrypt::verify(password, &db_password).map_err(|e| UserAuthError::EncryptionError(e))? {
|
||||
return Ok(UserModel {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod user;
|
||||
pub mod help;
|
||||
pub(crate) mod auth;
|
||||
pub(crate) mod auth;
|
||||
pub mod admin;
|
10
src/routes/ui/admin.rs
Normal file
10
src/routes/ui/admin.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use rocket::{get, Route};
|
||||
use rocket::http::Status;
|
||||
use rocket::response::status;
|
||||
use rocket_dyn_templates::{context, Template};
|
||||
use crate::guards::AuthUser;
|
||||
|
||||
#[get("/")]
|
||||
pub async fn index(user: AuthUser, route: &Route) -> Status {
|
||||
Status::Forbidden
|
||||
}
|
|
@ -6,6 +6,7 @@ use rocket::http::{Header, Status};
|
|||
use rocket_dyn_templates::{context, Template};
|
||||
use rocket_session_store::Session;
|
||||
use crate::{GlobalMetadata, LoginSessionData, SessionData, DB};
|
||||
use crate::consts::DISABLE_LOGIN_CHECK;
|
||||
use crate::models::user::validate_user_form;
|
||||
use crate::util::{set_csrf, validate_csrf_form};
|
||||
|
||||
|
@ -60,7 +61,9 @@ pub async fn handler(
|
|||
return_to: Option<String>,
|
||||
) -> Result<HackyRedirectBecauseRocketBug, Template> {
|
||||
trace!("handler");
|
||||
validate_csrf_form(&mut form.context, &session).await;
|
||||
if !DISABLE_LOGIN_CHECK.get().unwrap() {
|
||||
validate_csrf_form(&mut form.context, &session).await;
|
||||
}
|
||||
let user = validate_user_form(&mut form.context, &pool).await;
|
||||
trace!("check form");
|
||||
if form.context.status() == Status::Ok {
|
||||
|
|
|
@ -21,7 +21,10 @@ use crate::objs::library::ListOptions;
|
|||
use crate::routes::ui::auth;
|
||||
use crate::util::{JsonErrorResponse, ResponseError};
|
||||
|
||||
|
||||
#[get("/settings")]
|
||||
pub async fn user_settings(user: AuthUser, route: &Route) -> Template {
|
||||
Template::render("settings", context! { session: user.session, route: route.uri.path() })
|
||||
}
|
||||
#[get("/")]
|
||||
pub async fn index(user: AuthUser, route: &Route) -> Template {
|
||||
Template::render("index", context! { session: user.session, route: route.uri.path(), test: "value" })
|
||||
|
|
26
templates/errors/403.html.hbs
Normal file
26
templates/errors/403.html.hbs
Normal file
|
@ -0,0 +1,26 @@
|
|||
{{#> layouts/default body-class="has-background-white-ter login-bg" }}
|
||||
<br><br>
|
||||
<div class="container py-6" style="width:20%"> <!-- TODO: fix width on mobile -->
|
||||
<h1 class="title is-1 has-text-centered">storage-app</h1>
|
||||
<div class="box is-radiusless">
|
||||
<h4 class="title is-4 has-text-centered">403 Forbidden</h4>
|
||||
<p>You do not have permission to view the resource at <code></code></p>
|
||||
|
||||
<br>
|
||||
<!-- Hide go back unless javascript enabled -->
|
||||
<p><span id="backlink" style="display:none"><a href="">Go Back</a> | </span><a href="/">Return home</a></p>
|
||||
</div>
|
||||
</div>
|
||||
{{/layouts/default}}
|
||||
|
||||
<script>
|
||||
// Enable 'go back' link:
|
||||
const element = document.querySelector('#backlink');
|
||||
element.style.display = "inline"
|
||||
const elementLink = document.querySelector('#backlink a');
|
||||
elementLink.setAttribute('href', document.referrer);
|
||||
elementLink.onclick = function() {
|
||||
history.back();
|
||||
return false;
|
||||
}
|
||||
</script>
|
92
templates/settings.html.hbs
Normal file
92
templates/settings.html.hbs
Normal file
|
@ -0,0 +1,92 @@
|
|||
{{#> layouts/main body-class="" }}
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="box is-radiusless" id="account">
|
||||
<h4 class="title is-4 has-text-link">Account Settings</h4>
|
||||
<form method="post" action="/settings/account">
|
||||
<input type="hidden" name="_csrf" value="{{ csrf_token }}">
|
||||
<div class="field">
|
||||
<label class="label">Name</label>
|
||||
<div class="control has-icons-left">
|
||||
<input autofocus required name="username" value="{{ session.user.name }}"
|
||||
class="input {{#if errors.name}}is-danger{{/if}}" type="text" placeholder="Name">
|
||||
<span class="icon is-small is-left">
|
||||
<i class="fas fa-user"></i>
|
||||
</span>
|
||||
</div>
|
||||
{{#if errors.name }}
|
||||
<p class="help is-danger">{{errors.name}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Password</label>
|
||||
<div class="control">
|
||||
<a class="button is-small">Reset password</a>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="buttons">
|
||||
<button class="button is-success" type="submit">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="box is-radiusless" id="account">
|
||||
<h4 class="title is-4 has-text-link">UI Preferences</h4>
|
||||
<form method="post" action="/settings/ui">
|
||||
<input type="hidden" name="_csrf" value="{{ csrf_token }}">
|
||||
<div class="field">
|
||||
<label class="label">Language</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select>
|
||||
<option selected value="en-us">English</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="buttons">
|
||||
<button class="button is-success" type="submit">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="box is-radiusless" id="sessions">
|
||||
<h4 class="title is-4 has-text-link">Active Sessions</h4>
|
||||
<table class="table is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>IP</th>
|
||||
<th>Location</th>
|
||||
<th>Last Active</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UUID</td>
|
||||
<td>IP</td>
|
||||
<td>Location</td>
|
||||
<td>Now</td>
|
||||
<td><em>current</em></td>
|
||||
</tr>
|
||||
</tb>
|
||||
</table>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<div class="box">
|
||||
<aside class="sidebar pl-0 mb-0">
|
||||
<p class="sidebar-header">Sections</p>
|
||||
<ul class="sidebar-list mb-0">
|
||||
<li><a href="#account"><i class="fa fa-user"></i>Account</a></li>
|
||||
<li><a href="#ui"><i class="fa fa-cog"></i>UI Preferences</a></li>
|
||||
<li><a href="#sessions"><i class="fa fa-laptop"></i>Active Sessions</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/layouts/main}}
|
Loading…
Add table
Add a link
Reference in a new issue