add jwt auth
This commit is contained in:
parent
cac4e25620
commit
2c940ca41c
39
Cargo.lock
generated
39
Cargo.lock
generated
@ -19,6 +19,17 @@ dependencies = [
|
|||||||
"tokio-util 0.7.2",
|
"tokio-util 0.7.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix-grants-proc-macro"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48df3c1e0019e6f46bf3277e9b339688f476ccd9f5d5161419fdd305648fcac2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "3.0.4"
|
version = "3.0.4"
|
||||||
@ -181,6 +192,31 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix-web-grants"
|
||||||
|
version = "3.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef8ecad01ac8f2e4d0355a825e141c2077bba4d4e7bf740da34810a4aa872aea"
|
||||||
|
dependencies = [
|
||||||
|
"actix-grants-proc-macro",
|
||||||
|
"actix-web",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix-web-httpauth"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08c25a48b4684f90520183cd1a688e5f4f7e9905835fa75d02c0fe4f60fcdbe6"
|
||||||
|
dependencies = [
|
||||||
|
"actix-service",
|
||||||
|
"actix-utils",
|
||||||
|
"actix-web",
|
||||||
|
"base64",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler"
|
name = "adler"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@ -627,6 +663,8 @@ name = "ffplayout-engine"
|
|||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"actix-web-grants",
|
||||||
|
"actix-web-httpauth",
|
||||||
"argon2",
|
"argon2",
|
||||||
"chrono 0.4.19 (git+https://github.com/sbrocket/chrono?branch=parse-error-kind-public)",
|
"chrono 0.4.19 (git+https://github.com/sbrocket/chrono?branch=parse-error-kind-public)",
|
||||||
"clap",
|
"clap",
|
||||||
@ -639,6 +677,7 @@ dependencies = [
|
|||||||
"lettre",
|
"lettre",
|
||||||
"log",
|
"log",
|
||||||
"notify",
|
"notify",
|
||||||
|
"once_cell",
|
||||||
"openssl",
|
"openssl",
|
||||||
"rand",
|
"rand",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
|
@ -10,6 +10,8 @@ default-run = "ffplayout"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4"
|
actix-web = "4"
|
||||||
|
actix-web-grants = "3"
|
||||||
|
actix-web-httpauth = "0.6"
|
||||||
argon2 = "0.4"
|
argon2 = "0.4"
|
||||||
chrono = { git = "https://github.com/sbrocket/chrono", branch = "parse-error-kind-public" }
|
chrono = { git = "https://github.com/sbrocket/chrono", branch = "parse-error-kind-public" }
|
||||||
clap = { version = "3.1", features = ["derive"] }
|
clap = { version = "3.1", features = ["derive"] }
|
||||||
@ -22,6 +24,7 @@ jsonwebtoken = "8"
|
|||||||
lettre = "0.10.0-rc.6"
|
lettre = "0.10.0-rc.6"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
notify = "4.0"
|
notify = "4.0"
|
||||||
|
once_cell = "1.10"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rand_core = { version = "0.6", features = ["std"] }
|
rand_core = { version = "0.6", features = ["std"] }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
46
src/api/auth.rs
Normal file
46
src/api/auth.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use actix_web::error::ErrorUnauthorized;
|
||||||
|
use actix_web::Error;
|
||||||
|
use chrono::{Duration, Utc};
|
||||||
|
use jsonwebtoken::{self, DecodingKey, EncodingKey, Header, Validation};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::api::utils::GlobalSettings;
|
||||||
|
|
||||||
|
// Token lifetime and Secret key are hardcoded for clarity
|
||||||
|
const JWT_EXPIRATION_MINUTES: i64 = 15;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
|
pub struct Claims {
|
||||||
|
pub id: i64,
|
||||||
|
pub username: String,
|
||||||
|
pub permissions: Vec<String>,
|
||||||
|
exp: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Claims {
|
||||||
|
pub fn new(id: i64, username: String, permissions: Vec<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
username,
|
||||||
|
permissions,
|
||||||
|
exp: (Utc::now() + Duration::minutes(JWT_EXPIRATION_MINUTES)).timestamp(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a json web token (JWT)
|
||||||
|
pub fn create_jwt(claims: Claims) -> Result<String, Error> {
|
||||||
|
let config = GlobalSettings::global();
|
||||||
|
let encoding_key = EncodingKey::from_secret(config.secret.as_bytes());
|
||||||
|
jsonwebtoken::encode(&Header::default(), &claims, &encoding_key)
|
||||||
|
.map_err(|e| ErrorUnauthorized(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode a json web token (JWT)
|
||||||
|
pub async fn decode_jwt(token: &str) -> Result<Claims, Error> {
|
||||||
|
let config = GlobalSettings::global();
|
||||||
|
let decoding_key = DecodingKey::from_secret(config.secret.as_bytes());
|
||||||
|
jsonwebtoken::decode::<Claims>(token, &decoding_key, &Validation::default())
|
||||||
|
.map(|data| data.claims)
|
||||||
|
.map_err(|e| ErrorUnauthorized(e.to_string()))
|
||||||
|
}
|
@ -6,6 +6,12 @@ use simplelog::*;
|
|||||||
use sqlx::{migrate::MigrateDatabase, sqlite::SqliteQueryResult, Pool, Sqlite, SqlitePool};
|
use sqlx::{migrate::MigrateDatabase, sqlite::SqliteQueryResult, Pool, Sqlite, SqlitePool};
|
||||||
|
|
||||||
use crate::api::models::User;
|
use crate::api::models::User;
|
||||||
|
use crate::api::utils::GlobalSettings;
|
||||||
|
|
||||||
|
#[derive(Debug, sqlx::FromRow)]
|
||||||
|
struct Role {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn db_path() -> Result<String, Box<dyn std::error::Error>> {
|
pub fn db_path() -> Result<String, Box<dyn std::error::Error>> {
|
||||||
let sys_path = Path::new("/usr/share/ffplayout");
|
let sys_path = Path::new("/usr/share/ffplayout");
|
||||||
@ -23,7 +29,13 @@ pub fn db_path() -> Result<String, Box<dyn std::error::Error>> {
|
|||||||
async fn cretea_schema() -> Result<SqliteQueryResult, sqlx::Error> {
|
async fn cretea_schema() -> Result<SqliteQueryResult, sqlx::Error> {
|
||||||
let conn = db_connection().await?;
|
let conn = db_connection().await?;
|
||||||
let query = "PRAGMA foreign_keys = ON;
|
let query = "PRAGMA foreign_keys = ON;
|
||||||
CREATE TABLE IF NOT EXISTS groups
|
CREATE TABLE IF NOT EXISTS global
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
secret TEXT NOT NULL,
|
||||||
|
UNIQUE(secret)
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS roles
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
@ -36,7 +48,6 @@ async fn cretea_schema() -> Result<SqliteQueryResult, sqlx::Error> {
|
|||||||
preview_url TEXT NOT NULL,
|
preview_url TEXT NOT NULL,
|
||||||
settings_path TEXT NOT NULL,
|
settings_path TEXT NOT NULL,
|
||||||
extra_extensions TEXT NOT NULL,
|
extra_extensions TEXT NOT NULL,
|
||||||
secret TEXT NOT NULL,
|
|
||||||
UNIQUE(channel_name)
|
UNIQUE(channel_name)
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS user
|
CREATE TABLE IF NOT EXISTS user
|
||||||
@ -46,8 +57,8 @@ async fn cretea_schema() -> Result<SqliteQueryResult, sqlx::Error> {
|
|||||||
username TEXT NOT NULL,
|
username TEXT NOT NULL,
|
||||||
password TEXT NOT NULL,
|
password TEXT NOT NULL,
|
||||||
salt TEXT NOT NULL,
|
salt TEXT NOT NULL,
|
||||||
group_id INTEGER NOT NULL DEFAULT 2,
|
role_id INTEGER NOT NULL DEFAULT 2,
|
||||||
FOREIGN KEY (group_id) REFERENCES groups (id) ON UPDATE SET NULL ON DELETE SET NULL,
|
FOREIGN KEY (role_id) REFERENCES roles (id) ON UPDATE SET NULL ON DELETE SET NULL,
|
||||||
UNIQUE(email, username)
|
UNIQUE(email, username)
|
||||||
);";
|
);";
|
||||||
let result = sqlx::query(query).execute(&conn).await;
|
let result = sqlx::query(query).execute(&conn).await;
|
||||||
@ -74,10 +85,17 @@ pub async fn db_init() -> Result<&'static str, Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
let instances = db_connection().await?;
|
let instances = db_connection().await?;
|
||||||
|
|
||||||
let query = "INSERT INTO groups(name) VALUES('admin'), ('user');
|
let query = "CREATE TRIGGER global_row_count
|
||||||
INSERT INTO settings(channel_name, preview_url, settings_path, extra_extensions, secret)
|
BEFORE INSERT ON global
|
||||||
|
WHEN (SELECT COUNT(*) FROM global) >= 1
|
||||||
|
BEGIN
|
||||||
|
SELECT RAISE(FAIL, 'Database is already init!');
|
||||||
|
END;
|
||||||
|
INSERT INTO global(secret) VALUES($1);
|
||||||
|
INSERT INTO roles(name) VALUES('admin'), ('user'), ('guest');
|
||||||
|
INSERT INTO settings(channel_name, preview_url, settings_path, extra_extensions)
|
||||||
VALUES('Channel 1', 'http://localhost/live/preview.m3u8',
|
VALUES('Channel 1', 'http://localhost/live/preview.m3u8',
|
||||||
'/etc/ffplayout/ffplayout.yml', '.jpg,.jpeg,.png', $1);";
|
'/etc/ffplayout/ffplayout.yml', '.jpg,.jpeg,.png');";
|
||||||
sqlx::query(query).bind(secret).execute(&instances).await?;
|
sqlx::query(query).bind(secret).execute(&instances).await?;
|
||||||
instances.close().await;
|
instances.close().await;
|
||||||
|
|
||||||
@ -91,6 +109,24 @@ pub async fn db_connection() -> Result<Pool<Sqlite>, sqlx::Error> {
|
|||||||
Ok(conn)
|
Ok(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_global() -> Result<GlobalSettings, sqlx::Error> {
|
||||||
|
let conn = db_connection().await?;
|
||||||
|
let query = "SELECT secret FROM global WHERE id = 1";
|
||||||
|
let result: GlobalSettings = sqlx::query_as(query).fetch_one(&conn).await?;
|
||||||
|
conn.close().await;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_role(id: &i64) -> Result<String, sqlx::Error> {
|
||||||
|
let conn = db_connection().await?;
|
||||||
|
let query = "SELECT name FROM roles WHERE id = $1";
|
||||||
|
let result: Role = sqlx::query_as(query).bind(id).fetch_one(&conn).await?;
|
||||||
|
conn.close().await;
|
||||||
|
|
||||||
|
Ok(result.name)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn add_user(
|
pub async fn add_user(
|
||||||
mail: &str,
|
mail: &str,
|
||||||
user: &str,
|
user: &str,
|
||||||
@ -100,7 +136,7 @@ pub async fn add_user(
|
|||||||
) -> Result<SqliteQueryResult, sqlx::Error> {
|
) -> Result<SqliteQueryResult, sqlx::Error> {
|
||||||
let conn = db_connection().await?;
|
let conn = db_connection().await?;
|
||||||
let query =
|
let query =
|
||||||
"INSERT INTO user (email, username, password, salt, group_id) VALUES($1, $2, $3, $4, $5)";
|
"INSERT INTO user (email, username, password, salt, role_id) VALUES($1, $2, $3, $4, $5)";
|
||||||
let result = sqlx::query(query)
|
let result = sqlx::query(query)
|
||||||
.bind(mail)
|
.bind(mail)
|
||||||
.bind(user)
|
.bind(user)
|
||||||
@ -116,7 +152,7 @@ pub async fn add_user(
|
|||||||
|
|
||||||
pub async fn get_login(user: &str) -> Result<User, sqlx::Error> {
|
pub async fn get_login(user: &str) -> Result<User, sqlx::Error> {
|
||||||
let conn = db_connection().await?;
|
let conn = db_connection().await?;
|
||||||
let query = "SELECT id, email, username, password, salt FROM user WHERE username = $1";
|
let query = "SELECT id, email, username, password, salt, role_id FROM user WHERE username = $1";
|
||||||
let result: User = sqlx::query_as(query).bind(user).fetch_one(&conn).await?;
|
let result: User = sqlx::query_as(query).bind(user).fetch_one(&conn).await?;
|
||||||
conn.close().await;
|
conn.close().await;
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
pub mod args_parse;
|
pub mod args_parse;
|
||||||
|
pub mod auth;
|
||||||
pub mod handles;
|
pub mod handles;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
@ -3,7 +3,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[derive(Debug, Deserialize, Serialize, sqlx::FromRow)]
|
#[derive(Debug, Deserialize, Serialize, sqlx::FromRow)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
#[sqlx(default)]
|
#[sqlx(default)]
|
||||||
pub id: Option<i64>,
|
#[serde(skip_deserializing)]
|
||||||
|
pub id: i64,
|
||||||
#[sqlx(default)]
|
#[sqlx(default)]
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
@ -15,7 +16,14 @@ pub struct User {
|
|||||||
pub salt: Option<String>,
|
pub salt: Option<String>,
|
||||||
#[sqlx(default)]
|
#[sqlx(default)]
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub group_id: Option<i64>,
|
pub role_id: Option<i64>,
|
||||||
|
#[sqlx(default)]
|
||||||
|
pub token: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct LoginUser {
|
||||||
|
pub id: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, sqlx::FromRow)]
|
#[derive(Debug, Deserialize, Serialize, sqlx::FromRow)]
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
use actix_web::{get, http::StatusCode, post, web, Responder};
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use actix_web::{
|
||||||
|
get,
|
||||||
|
http::StatusCode,
|
||||||
|
post, put,
|
||||||
|
web::{self, Data},
|
||||||
|
Responder,
|
||||||
|
};
|
||||||
|
use actix_web_grants::proc_macro::has_permissions;
|
||||||
use argon2::{password_hash::PasswordHash, Argon2, PasswordVerifier};
|
use argon2::{password_hash::PasswordHash, Argon2, PasswordVerifier};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use simplelog::*;
|
use simplelog::*;
|
||||||
|
|
||||||
use crate::api::{handles::get_login, models::User};
|
use crate::api::{
|
||||||
|
auth::{create_jwt, Claims},
|
||||||
#[get("/hello/{name}")]
|
handles::{get_login, get_role},
|
||||||
async fn greet(name: web::Path<String>) -> impl Responder {
|
models::{LoginUser, User},
|
||||||
format!("Hello {name}!")
|
};
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ResponseObj<T> {
|
struct ResponseObj<T> {
|
||||||
@ -17,9 +25,27 @@ struct ResponseObj<T> {
|
|||||||
data: Option<T>,
|
data: Option<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// curl -X POST -H "Content-Type: application/json" -d '{"username": "USER", "password": "abc123" }' http://127.0.0.1:8080/auth/login/
|
#[get("/settings")]
|
||||||
|
#[has_permissions("admin")]
|
||||||
|
async fn settings(data: Data<Mutex<LoginUser>>) -> impl Responder {
|
||||||
|
println!("{:?}", data.lock());
|
||||||
|
"Hello from settings!"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/user/{user_id}")]
|
||||||
|
#[has_permissions("admin")]
|
||||||
|
async fn update_user(user_id: web::Path<i64>, data: Data<Mutex<LoginUser>>) -> impl Responder {
|
||||||
|
if user_id.into_inner() == data.lock().unwrap().id {
|
||||||
|
return "Update allow!";
|
||||||
|
}
|
||||||
|
|
||||||
|
"Wrong user!"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// curl -X POST -H "Content-Type: application/json" -d '{"username": "USER", "password": "abc123" }' \
|
||||||
|
/// http://127.0.0.1:8080/auth/login/
|
||||||
#[post("/auth/login/")]
|
#[post("/auth/login/")]
|
||||||
pub async fn login(credentials: web::Json<User>) -> impl Responder {
|
pub async fn login(credentials: web::Json<User>, data: Data<Mutex<LoginUser>>) -> impl Responder {
|
||||||
match get_login(&credentials.username).await {
|
match get_login(&credentials.username).await {
|
||||||
Ok(mut user) => {
|
Ok(mut user) => {
|
||||||
let pass = user.password.clone();
|
let pass = user.password.clone();
|
||||||
@ -31,7 +57,19 @@ pub async fn login(credentials: web::Json<User>) -> impl Responder {
|
|||||||
.verify_password(credentials.password.as_bytes(), &hash)
|
.verify_password(credentials.password.as_bytes(), &hash)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
info!("user {} login", credentials.username);
|
let role = get_role(&user.role_id.unwrap_or_default())
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|_| "guest".to_string());
|
||||||
|
let claims = Claims::new(user.id, user.username.clone(), vec![role.clone()]);
|
||||||
|
|
||||||
|
if let Ok(token) = create_jwt(claims) {
|
||||||
|
user.token = Some(token);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut my_data = data.lock().unwrap();
|
||||||
|
my_data.id = user.id;
|
||||||
|
|
||||||
|
info!("user {} login, with role: {role}", credentials.username);
|
||||||
|
|
||||||
web::Json(ResponseObj {
|
web::Json(ResponseObj {
|
||||||
message: "login correct!".into(),
|
message: "login correct!".into(),
|
||||||
|
@ -2,13 +2,43 @@ use argon2::{
|
|||||||
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
|
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
|
||||||
Argon2,
|
Argon2,
|
||||||
};
|
};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
use simplelog::*;
|
use simplelog::*;
|
||||||
|
|
||||||
use crate::api::{
|
use crate::api::{
|
||||||
args_parse::Args,
|
args_parse::Args,
|
||||||
handles::{add_user, db_init},
|
handles::{add_user, db_init, get_global},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, sqlx::FromRow)]
|
||||||
|
pub struct GlobalSettings {
|
||||||
|
pub secret: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalSettings {
|
||||||
|
async fn new() -> Self {
|
||||||
|
let global_settings = get_global();
|
||||||
|
|
||||||
|
match global_settings.await {
|
||||||
|
Ok(g) => g,
|
||||||
|
Err(_) => GlobalSettings {
|
||||||
|
secret: String::new(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn global() -> &'static GlobalSettings {
|
||||||
|
INSTANCE.get().expect("Config is not initialized")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static INSTANCE: OnceCell<GlobalSettings> = OnceCell::new();
|
||||||
|
|
||||||
|
pub async fn init_config() {
|
||||||
|
let config = GlobalSettings::new().await;
|
||||||
|
INSTANCE.set(config).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn run_args(args: Args) -> Result<(), i32> {
|
pub async fn run_args(args: Args) -> Result<(), i32> {
|
||||||
if !args.init && args.listen.is_none() && args.username.is_none() {
|
if !args.init && args.listen.is_none() && args.username.is_none() {
|
||||||
error!("Wrong number of arguments! Run ffpapi --help for more information.");
|
error!("Wrong number of arguments! Run ffpapi --help for more information.");
|
||||||
|
@ -1,14 +1,36 @@
|
|||||||
use std::process::exit;
|
use std::{process::exit, sync::Mutex};
|
||||||
|
|
||||||
|
use actix_web::{
|
||||||
|
dev::ServiceRequest,
|
||||||
|
middleware,
|
||||||
|
web::{self, Data},
|
||||||
|
App, Error, HttpServer,
|
||||||
|
};
|
||||||
|
use actix_web_grants::permissions::AttachPermissions;
|
||||||
|
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||||
|
use actix_web_httpauth::middleware::HttpAuthentication;
|
||||||
|
|
||||||
use actix_web::{App, HttpServer};
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use simplelog::*;
|
use simplelog::*;
|
||||||
|
|
||||||
use ffplayout_engine::{
|
use ffplayout_engine::{
|
||||||
api::{args_parse::Args, routes::login, utils::run_args},
|
api::{
|
||||||
|
args_parse::Args,
|
||||||
|
auth,
|
||||||
|
models::LoginUser,
|
||||||
|
routes::{login, settings, update_user},
|
||||||
|
utils::{init_config, run_args},
|
||||||
|
},
|
||||||
utils::{init_logging, GlobalConfig},
|
utils::{init_logging, GlobalConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, Error> {
|
||||||
|
// We just get permissions from JWT
|
||||||
|
let claims = auth::decode_jwt(credentials.token()).await?;
|
||||||
|
req.attach(claims.permissions);
|
||||||
|
Ok(req)
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
@ -26,15 +48,31 @@ async fn main() -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(conn) = args.listen {
|
if let Some(conn) = args.listen {
|
||||||
|
init_config().await;
|
||||||
let ip_port = conn.split(':').collect::<Vec<&str>>();
|
let ip_port = conn.split(':').collect::<Vec<&str>>();
|
||||||
let addr = ip_port[0];
|
let addr = ip_port[0];
|
||||||
let port = ip_port[1].parse::<u16>().unwrap();
|
let port = ip_port[1].parse::<u16>().unwrap();
|
||||||
|
let data = Data::new(Mutex::new(LoginUser { id: 0 }));
|
||||||
|
|
||||||
info!("running ffplayout API, listen on {conn}");
|
info!("running ffplayout API, listen on {conn}");
|
||||||
|
|
||||||
HttpServer::new(|| App::new().service(login))
|
// TODO: add allow origin
|
||||||
.bind((addr, port))?
|
HttpServer::new(move || {
|
||||||
.run()
|
let auth = HttpAuthentication::bearer(validator);
|
||||||
.await
|
App::new()
|
||||||
|
.wrap(middleware::Logger::default())
|
||||||
|
.app_data(Data::clone(&data))
|
||||||
|
.service(login)
|
||||||
|
.service(
|
||||||
|
web::scope("/api")
|
||||||
|
.wrap(auth)
|
||||||
|
.service(settings)
|
||||||
|
.service(update_user),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.bind((addr, port))?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
} else {
|
} else {
|
||||||
error!("Run ffpapi with listen parameter!");
|
error!("Run ffpapi with listen parameter!");
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user