Skip to content

Commit

Permalink
Update README.md and doc strings
Browse files Browse the repository at this point in the history
Elaborate usage of env vars
Code restructure
  • Loading branch information
dormant-user committed Feb 20, 2024
1 parent e545d89 commit e6d0edf
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 57 deletions.
27 changes: 6 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,9 @@ curl -o RuStream-Windows-x86_64.zip -LH "Accept: application/octet-stream" "http
```
</details>

#### Config file

[//]: # (todo: update to use .env)

[RuStream][repo] requires a JSON file with secrets loaded as key-value paris.
#### Environment Variables
> Environment variables can (optionally) be loaded from any plain text file.<br>
> Refer the [wiki page][gh-wiki-env] for more information.
**Mandatory**
- **authorization**: Dictionary of key-value pairs with `username` as key and `password` as value.
Expand All @@ -72,30 +70,16 @@ curl -o RuStream-Windows-x86_64.zip -LH "Accept: application/octet-stream" "http
- **video_host**: IP address to host the video. Defaults to `127.0.0.1` / `localhost`
- **video_port**: Port number to host the application. Defaults to `8000`
- **session_duration**: Time _(in seconds)_ each authenticated session should last. Defaults to `3600`
- **file_formats**: Vector of supported video file formats. Defaults to `[.mp4, .mov]`
- **file_formats**: Vector of supported video file formats. Defaults to `[mp4, mov]`
- **workers**: Number of workers to spin up for the server. Defaults to the number of physical cores.
- **max_connections**: Maximum number of concurrent connections per worker. Defaults to `3`
- **websites**: Vector of websites (_supports regex_) to add to CORS configuration. _Required only if tunneled via CDN_
- **key_file**: Path to the private key file for SSL certificate. Defaults to `None`
- **cert_file**: Path to the full chain file for SSL certificate. Defaults to `None`
- **secure_session**: Boolean flag to secure the cookie `session_token`. Defaults to `false`
> If `SECURE_SESSION` to set to `true`, the cookie `session_token` will only be sent via HTTPS<br>
> If `SECURE_SESSION` is to set to `true`, the cookie `session_token` will only be sent via HTTPS<br>
> This means that the server can **ONLY** be hosted via `HTTPS` or `localhost`
<details>
<summary><i><strong>Sample content of JSON file</strong></i></summary>

```json
{
"authorization": {"rustic": "S0m3rAn0mP@ssW0rD"},
"video_source": "/Users/hannibal/Downloads/stream",
"video_port": 5883,
"file_formats": ["mov", "mp4", "mkv"],
"workers": 10
}
```
</details>

## Crate
[https://crates.io/crates/RuStream][crate]

Expand Down Expand Up @@ -138,4 +122,5 @@ Licensed under the [MIT License][license]
[gh-checks]: https://github.com/thevickypedia/RuStream/actions/workflows/rust.yml
[crates-logo]: https://img.shields.io/crates/v/RuStream.svg
[gh-wiki]: https://github.com/thevickypedia/RuStream/wiki
[gh-wiki-env]: https://github.com/thevickypedia/RuStream/wiki/Environment-Variables
[docs]: https://docs.rs/RuStream/latest/rustream/
16 changes: 0 additions & 16 deletions src/jinja.rs

This file was deleted.

10 changes: 4 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@ use rand::prelude::SliceRandom;

/// Module to load all the static values and required structs during startup.
mod constant;
/// Module to read the HTML files and load as Jinja templates.
mod jinja;
/// Module for all the API entry points.
mod routes;
/// Module to store all the helper functions.
mod squire;
/// Module to load all the templates for the UI.
mod templates;

/// Contains entrypoint and initializer settings to trigger the asynchronous HTTPServer
/// Contains entrypoint and initializer settings to trigger the asynchronous `HTTPServer`
///
/// # Examples
///
Expand Down Expand Up @@ -52,10 +50,10 @@ pub async fn start() -> io::Result<()> {
"Secure session is turned on! This means that the server can ONLY be hosted via HTTPS or localhost"
);
}
let template = jinja::environment();
let jinja_env = templates::environment();
// Create a dedicated clone, since it will be used within closure
let config_clone = config.clone();
let template_clone = template.clone();
let jinja_clone = jinja_env.clone();
let host = format!("{}:{}", config.video_host, config.video_port);
log::info!("{} [workers:{}] running on http://{} (Press CTRL+C to quit)",
&cargo.pkg_name, &config.workers, &host);
Expand All @@ -67,7 +65,7 @@ pub async fn start() -> io::Result<()> {
let application = move || {
App::new() // Creates a new Actix web application
.app_data(web::Data::new(config_clone.clone()))
.app_data(web::Data::new(template_clone.clone()))
.app_data(web::Data::new(jinja_clone.clone()))
.wrap(squire::middleware::get_cors(config_clone.websites.clone()))
.wrap(middleware::Logger::default()) // Adds a default logger middleware to the application
.service(routes::basics::health) // Registers a service for handling requests
Expand Down
18 changes: 8 additions & 10 deletions src/routes/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,12 @@ pub async fn login(config: web::Data<Arc<squire::settings::Config>>, request: Ht
.max_age(cookie_duration)
.expires(expiration);

let cookie;
if config.secure_session {
let cookie = if config.secure_session {
log::info!("Marking 'session_token' cookie as secure!!");
cookie = base_cookie.secure(true).finish();
base_cookie.secure(true).finish()
} else {
cookie = base_cookie.finish();
}
base_cookie.finish()
};
log::info!("Session for '{}' will be valid until {}", mapped.get("username").unwrap(), expiration);

let mut response = HttpResponse::Ok().json(RedirectResponse {
Expand Down Expand Up @@ -207,13 +206,12 @@ pub fn failed_auth(auth_response: squire::authenticator::AuthToken,
.http_only(true)
.same_site(SameSite::Strict)
.max_age(age);
let cookie;
if config.secure_session {
let cookie = if config.secure_session {
log::debug!("Marking 'detail' cookie as secure!!");
cookie = base_cookie.secure(true).finish();
base_cookie.secure(true).finish()
} else {
cookie = base_cookie.finish();
}
base_cookie.finish()
};
response.cookie(cookie);
response.append_header(("Location", "/error"));
response.finish()
Expand Down
73 changes: 69 additions & 4 deletions src/squire/startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ pub fn init_logger(debug: bool, crate_name: &String) {
env_logger::init();
}

/// Extracts the mandatory env vars by key and parses it as `HashMap<String, String>` and `PathBuf`
///
/// # Arguments
///
/// * `key` - Key for the environment variable.
///
/// # Returns
///
/// Returns a tuple of `HashMap<String, String>` and `PathBuf`.
///
/// # Panics
///
/// If the value is missing or if there is an error parsing the `HashMap`
fn mandatory_vars() -> (std::collections::HashMap<String, String>, std::path::PathBuf) {
let authorization_str = match std::env::var("authorization") {
Ok(val) => val,
Expand Down Expand Up @@ -53,6 +66,19 @@ fn mandatory_vars() -> (std::collections::HashMap<String, String>, std::path::Pa
(authorization, std::path::PathBuf::from(video_source_str))
}

/// Extracts the env var by key and parses it as a `bool`
///
/// # Arguments
///
/// * `key` - Key for the environment variable.
///
/// # Returns
///
/// Returns an `Option<bool>` if the value is available.
///
/// # Panics
///
/// If the value is present, but it is an invalid data-type.
fn parse_bool(key: &str) -> Option<bool> {
match std::env::var(key) {
Ok(val) => match val.parse() {
Expand All @@ -65,6 +91,19 @@ fn parse_bool(key: &str) -> Option<bool> {
}
}

/// Extracts the env var by key and parses it as a `i32`
///
/// # Arguments
///
/// * `key` - Key for the environment variable.
///
/// # Returns
///
/// Returns an `Option<i32>` if the value is available.
///
/// # Panics
///
/// If the value is present, but it is an invalid data-type.
fn parse_i32(key: &str) -> Option<i32> {
match std::env::var(key) {
Ok(val) => match val.parse() {
Expand All @@ -77,6 +116,19 @@ fn parse_i32(key: &str) -> Option<i32> {
}
}

/// Extracts the env var by key and parses it as a `Vec<String>`
///
/// # Arguments
///
/// * `key` - Key for the environment variable.
///
/// # Returns
///
/// Returns an `Option<Vec<String>>` if the value is available.
///
/// # Panics
///
/// If the value is present, but it is an invalid data-type.
fn parse_vec(key: &str) -> Option<Vec<String>> {
match std::env::var(key) {
Ok(val) => match serde_json::from_str::<Vec<String>>(&val) {
Expand All @@ -89,6 +141,15 @@ fn parse_vec(key: &str) -> Option<Vec<String>> {
}
}

/// Extracts the env var by key and parses it as a `PathBuf`
///
/// # Arguments
///
/// * `key` - Key for the environment variable.
///
/// # Returns
///
/// Returns an option of `PathBuf` if the value is available.
fn parse_path(key: &str) -> Option<std::path::PathBuf> {
match std::env::var(key) {
Ok(value) => {
Expand All @@ -100,6 +161,11 @@ fn parse_path(key: &str) -> Option<std::path::PathBuf> {
}
}

/// Handler that's responsible to parse all the env vars.
///
/// # Returns
///
/// `Config` struct containing the required parameters.
fn load_env_vars() -> settings::Config {
let (authorization, video_source) = mandatory_vars();
let debug = parse_bool("debug").unwrap_or(settings::default_debug());
Expand Down Expand Up @@ -130,13 +196,12 @@ fn load_env_vars() -> settings::Config {
}
}

/// Retrieves the configuration from the provided command-line arguments.
/// Retrieves the environment variables and parses as the data-type specified in Config struct.
///
/// # Returns
///
/// An `Arc` of the Config struct containing the application configuration.
/// Returns an `Arc` of the `Config` struct containing the required parameters.
pub fn get_config() -> std::sync::Arc<settings::Config> {
// todo: Update docs to be explicit about what `env_file` means
let env_file = std::env::var("env_file").unwrap_or(".env".to_string());
let env_file_path = std::env::current_dir()
.unwrap_or_default()
Expand Down Expand Up @@ -171,7 +236,7 @@ pub fn get_config() -> std::sync::Arc<settings::Config> {
if !errors.is_empty() {
panic!("{}", errors);
}
return std::sync::Arc::new(config);
std::sync::Arc::new(config)
}
Err(err) => panic!("Error loading environment variables: {}", err)
}
Expand Down
15 changes: 15 additions & 0 deletions src/templates/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::sync::{Arc, Mutex};

/// Index page template that is served as HTML response for the root endpoint.
pub mod index;
/// Landing page template that is served as HTML response while streaming videos.
Expand All @@ -10,3 +12,16 @@ pub mod logout;
pub mod session;
/// Unauthorized page template that is served as HTML response after failed authentication.
pub mod unauthorized;

/// Loads all the HTML templates' content into a Jinja Environment
pub fn environment() -> Arc<Mutex<minijinja::Environment<'static>>> {
let mut env = minijinja::Environment::new();
env.add_template_owned("index", index::get_content()).unwrap();
env.add_template_owned("landing", landing::get_content()).unwrap();
env.add_template_owned("listing", listing::get_content()).unwrap();
env.add_template_owned("logout", logout::get_content()).unwrap();
env.add_template_owned("session", session::get_content()).unwrap();
env.add_template_owned("unauthorized", unauthorized::get_content()).unwrap();
let mutex = Mutex::new(env.to_owned());
Arc::new(mutex)
}

0 comments on commit e6d0edf

Please sign in to comment.