Compare commits
merge into: warricksothr:master
warricksothr:develop
warricksothr:master
pull from: warricksothr:develop
warricksothr:develop
warricksothr:master
41 Commits
23 changed files with 1611 additions and 831 deletions
-
3.gitignore
-
8.gitlab-ci.yml
-
1500Cargo.lock
-
24Cargo.toml
-
11docker/build-linux.bat
-
8docker/run-linux.bat
-
24rsddns-example.yml
-
39src/args/mod.rs
-
42src/args/parse.rs
-
46src/config/default.rs
-
30src/config/error.rs
-
33src/config/load.rs
-
5src/config/mod.rs
-
97src/config/model.rs
-
6src/config/validate.rs
-
125src/main.rs
-
238src/server/api.rs
-
59src/server/error.rs
-
62src/server/middleware/api_auth.rs
-
1src/server/middleware/mod.rs
-
10src/server/mod.rs
-
19src/server/router.rs
-
52src/server/util.rs
@ -1,2 +1,5 @@ |
|||
/.idea |
|||
/docker/linux |
|||
/target |
|||
**/*.rs.bk |
|||
rsddns.yml |
1500
Cargo.lock
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,11 @@ |
|||
if not exist "%cd%\docker\linux\target" mkdir "%cd%\docker\linux\target" |
|||
if not exist "%cd%\docker\linux\cargo" mkdir "%cd%\docker\linux\cargo" |
|||
|
|||
docker run --rm ^ |
|||
-e CARGO_HOME="/tmp/cargo" ^ |
|||
-v "%cd%":/usr/src/rsddns ^ |
|||
-v "%cd%\docker\linux\target":/usr/src/rsddns/target ^ |
|||
-v "%cd%\docker\linux\cargo":/tmp/cargo ^ |
|||
-w /usr/src/rsddns ^ |
|||
rust:1.31-stretch ^ |
|||
cargo build --release && strip /usr/src/rsddns/target/release/rsddns |
@ -0,0 +1,8 @@ |
|||
copy rsddns.yml "%cd%\docker\linux\target\release\rsddns.yml" |
|||
|
|||
docker run --rm -i -t ^ |
|||
-p 8080:8080 ^ |
|||
-v "%cd%\docker\linux\target\release":/opt/rsddns ^ |
|||
-w /opt/rsddns ^ |
|||
debian:stretch-slim ^ |
|||
./rsddns -c ./rsddns.yml -h 0.0.0.0 -p 8080 |
@ -0,0 +1,24 @@ |
|||
--- |
|||
server: |
|||
host: localhost |
|||
port: 8080 |
|||
workers: 4 |
|||
cloudflare: |
|||
email: something@something.com |
|||
key: somekeyblahblahblahimakey |
|||
domains: |
|||
- domain: something.com |
|||
zone_id: blahblahchangemeimakey |
|||
ddns: |
|||
domains: |
|||
- domain: something.com |
|||
subdomains: |
|||
- ddns |
|||
users: |
|||
- username: userOne |
|||
token: 6d37d7a9-6b6b-4db2-99f2-c261e4f4b922 |
|||
domains: |
|||
- domain: IAmNotADomain.com |
|||
root: ddns.IAmNotADomain.com |
|||
zones: |
|||
- home |
@ -0,0 +1,39 @@ |
|||
use clap::{App, Arg};
|
|||
|
|||
use crate::VERSION;
|
|||
|
|||
pub mod parse;
|
|||
|
|||
pub fn get_app() -> App<'static, 'static> {
|
|||
App::new("Dynamic DNS Server")
|
|||
.author("Drew Short, <warrick@sothr.com>")
|
|||
.version(VERSION)
|
|||
.about("Receive DDNS requests and update associated cloudflare subdomains")
|
|||
.args(&[
|
|||
Arg::with_name("config")
|
|||
.short("c")
|
|||
.long("config")
|
|||
.value_name("PATH")
|
|||
.default_value("/etc/rsddns/rsddns.yml")
|
|||
.help("Set a custom configuration file path.")
|
|||
.takes_value(true),
|
|||
Arg::with_name("host")
|
|||
.short("h")
|
|||
.long("host")
|
|||
.value_name("HOST")
|
|||
.help("The address the server listens on.")
|
|||
.takes_value(true),
|
|||
Arg::with_name("port")
|
|||
.short("p")
|
|||
.long("port")
|
|||
.value_name("PORT")
|
|||
.help("The port to run the server on.")
|
|||
.takes_value(true),
|
|||
Arg::with_name("workers")
|
|||
.short("w")
|
|||
.long("workers")
|
|||
.value_name("NUMBER")
|
|||
.help("The number of workers to serve requests with (Defaults to the number of cores on the system).")
|
|||
.takes_value(true),
|
|||
])
|
|||
}
|
@ -0,0 +1,42 @@ |
|||
use std::str::FromStr;
|
|||
|
|||
use clap::ArgMatches;
|
|||
|
|||
use crate::config::model::Config;
|
|||
|
|||
fn get_config_for_string(arg_value: Option<&str>, config_value: &Option<String>, default_value: &str) -> String {
|
|||
debug!("arg: {:?}; config: {:?}; default: {}", arg_value, config_value, default_value);
|
|||
String::from(match arg_value {
|
|||
Some(v) => v,
|
|||
None => match config_value {
|
|||
Some(host) => host,
|
|||
None => default_value,
|
|||
}
|
|||
})
|
|||
}
|
|||
|
|||
fn get_config_for_number<T: FromStr + std::fmt::Debug>(arg_value: Option<&str>, config_value: Option<T>, default_value: T) -> T {
|
|||
debug!("arg: {:?}; config: {:?}; default: {:?}", arg_value, config_value, default_value);
|
|||
match arg_value {
|
|||
Some(v) => v.parse::<T>().unwrap_or(default_value),
|
|||
None => match config_value {
|
|||
Some(v) => v,
|
|||
None => default_value
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
pub fn get_host(args: &ArgMatches, config: &Config, default_host: &str) -> String {
|
|||
let arg_value = args.value_of("host");
|
|||
get_config_for_string(arg_value, &config.server.host, default_host).clone()
|
|||
}
|
|||
|
|||
pub fn get_port(args: &ArgMatches, config: &Config, default_port: i16) -> i16 {
|
|||
let arg_value = args.value_of("port");
|
|||
get_config_for_number(arg_value, config.server.port, default_port)
|
|||
}
|
|||
|
|||
pub fn get_workers(args: &ArgMatches, config: &Config, default_workers: usize) -> usize {
|
|||
let arg_value = args.value_of("workers");
|
|||
get_config_for_number(arg_value, config.server.workers, default_workers)
|
|||
}
|
@ -0,0 +1,46 @@ |
|||
use crate::config::model::*;
|
|||
|
|||
fn get_default_config() -> Config {
|
|||
Config {
|
|||
server: ServerConfig {
|
|||
host: Option::Some(String::from("localhost")),
|
|||
port: Option::Some(8080),
|
|||
workers: Option::Some(4),
|
|||
},
|
|||
cloudflare: CloudflareConfig {
|
|||
email: String::from("something@something.com"),
|
|||
key: String::from("IAmNotAKey"),
|
|||
domains: vec![
|
|||
CloudflareDomainConfig {
|
|||
domain: String::from("IAmNotADomain.com"),
|
|||
zone_id: String::from("IAmNotAZoneID"),
|
|||
}
|
|||
],
|
|||
},
|
|||
ddns: DDNSConfig {
|
|||
domains: vec![
|
|||
DDNSDomain {
|
|||
domain: String::from("IAmNotADomain.com"),
|
|||
subdomains: vec![
|
|||
String::from("ddns")
|
|||
],
|
|||
}
|
|||
]
|
|||
},
|
|||
users: vec![
|
|||
UserConfig {
|
|||
username: String::from("userOne"),
|
|||
token: String::from("6d37d7a9-6b6b-4db2-99f2-c261e4f4b922"),
|
|||
roots: vec![
|
|||
UserRootConfig {
|
|||
domain: String::from("IAmNotADomain.com"),
|
|||
root: String::from("ddns.IAmNotADomain.com"),
|
|||
zones: vec![
|
|||
String::from("home")
|
|||
],
|
|||
}
|
|||
],
|
|||
}
|
|||
],
|
|||
}
|
|||
}
|
@ -0,0 +1,30 @@ |
|||
use std::error::Error;
|
|||
use std::fmt;
|
|||
|
|||
#[derive(Debug)]
|
|||
pub struct ConfigError {
|
|||
description: String,
|
|||
original_error: Option<Box<Error>>,
|
|||
}
|
|||
|
|||
impl ConfigError {
|
|||
pub fn new(description: &str, original_error: Option<Box<Error>>) -> ConfigError {
|
|||
ConfigError {
|
|||
description: String::from(description),
|
|||
original_error,
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
impl Error for ConfigError {}
|
|||
|
|||
impl fmt::Display for ConfigError {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
match &self.original_error {
|
|||
Some(original_error) => {
|
|||
write!(f, "{}: \"{}\"", self.description, original_error)
|
|||
}
|
|||
None => write!(f, "{}", self.description)
|
|||
}
|
|||
}
|
|||
}
|
@ -0,0 +1,33 @@ |
|||
use std::fs::File;
|
|||
use std::io::prelude::*;
|
|||
|
|||
use serde_yaml;
|
|||
|
|||
use crate::config::error::ConfigError;
|
|||
use crate::config::model::Config;
|
|||
|
|||
fn read_config(yaml_str: &str) -> Result<Config, ConfigError> {
|
|||
match serde_yaml::from_str(yaml_str) {
|
|||
Ok(v) => Result::Ok(v),
|
|||
Err(e) => Result::Err(ConfigError::new("Invalid Configuration", Option::Some(Box::from(e))))
|
|||
}
|
|||
}
|
|||
|
|||
pub fn read(path: &str) -> Result<Config, ConfigError> {
|
|||
match File::open(path) {
|
|||
Ok(mut file) => {
|
|||
let mut contents = String::new();
|
|||
match file.read_to_string(&mut contents) {
|
|||
Ok(c) => {
|
|||
if c > 0 {
|
|||
read_config(&contents)
|
|||
} else {
|
|||
Result::Err(ConfigError::new("Empty Configuration File", Option::None))
|
|||
}
|
|||
}
|
|||
Err(e) => Result::Err(ConfigError::new("Cannot Read Configuration File", Option::Some(Box::from(e))))
|
|||
}
|
|||
}
|
|||
Err(_e) => Result::Err(ConfigError::new(&format!("Configuration File Doesn't Exist \"{}\"", path), Option::None))
|
|||
}
|
|||
}
|
@ -0,0 +1,5 @@ |
|||
pub mod default;
|
|||
pub mod error;
|
|||
pub mod load;
|
|||
pub mod model;
|
|||
pub mod validate;
|
@ -0,0 +1,97 @@ |
|||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|||
pub struct ServerConfig {
|
|||
pub host: Option<String>,
|
|||
pub port: Option<i16>,
|
|||
pub workers: Option<usize>,
|
|||
}
|
|||
|
|||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|||
pub struct CloudflareDomainConfig {
|
|||
pub domain: String,
|
|||
pub zone_id: String,
|
|||
}
|
|||
|
|||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|||
pub struct CloudflareConfig {
|
|||
pub domains: Vec<CloudflareDomainConfig>,
|
|||
pub key: String,
|
|||
pub email: String,
|
|||
}
|
|||
|
|||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|||
pub struct DDNSDomain {
|
|||
pub domain: String,
|
|||
pub subdomains: Vec<String>,
|
|||
}
|
|||
|
|||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|||
pub struct DDNSConfig {
|
|||
pub domains: Vec<DDNSDomain>
|
|||
}
|
|||
|
|||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|||
pub struct UserRootConfig {
|
|||
pub domain: String,
|
|||
pub root: String,
|
|||
pub zones: Vec<String>,
|
|||
}
|
|||
|
|||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|||
pub struct UserConfig {
|
|||
pub username: String,
|
|||
pub token: String,
|
|||
pub roots: Vec<UserRootConfig>,
|
|||
}
|
|||
|
|||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|||
pub struct Config {
|
|||
pub server: ServerConfig,
|
|||
pub cloudflare: CloudflareConfig,
|
|||
pub ddns: DDNSConfig,
|
|||
pub users: Vec<UserConfig>,
|
|||
}
|
|||
|
|||
impl Config {
|
|||
pub fn get_user(&self, username: &str) -> Option<&UserConfig> {
|
|||
for user in &self.users {
|
|||
if user.username == username {
|
|||
return Some(user);
|
|||
}
|
|||
}
|
|||
None
|
|||
}
|
|||
|
|||
pub fn get_users_for_root_and_zone(&self, root: &str, zone: &str) -> Option<Vec<&UserConfig>> {
|
|||
let mut users: Vec<&UserConfig> = Vec::new();
|
|||
|
|||
for user in &self.users {
|
|||
if user.has_root_and_zone(root, zone) {
|
|||
users.push(user);
|
|||
}
|
|||
}
|
|||
|
|||
if users.len() > 0 { Some(users) } else { None }
|
|||
}
|
|||
|
|||
|
|||
pub fn is_valid_username_and_token(&self, username: &str, token: &str) -> bool {
|
|||
for user in &self.users {
|
|||
if user.username == username && user.token == token {
|
|||
return true;
|
|||
}
|
|||
}
|
|||
return false;
|
|||
}
|
|||
}
|
|||
|
|||
impl UserConfig {
|
|||
pub fn has_root_and_zone(&self, search_root: &str, search_zone: &str) -> bool {
|
|||
for root in &self.roots {
|
|||
let zone_match = &search_zone.to_string();
|
|||
if root.root == search_root && root.zones.contains(zone_match) {
|
|||
return true;
|
|||
}
|
|||
}
|
|||
false
|
|||
}
|
|||
}
|
@ -0,0 +1,6 @@ |
|||
use crate::config::model::Config;
|
|||
use crate::config::error::ConfigError;
|
|||
|
|||
pub fn validate(config: &Config) -> Result<&Config, ConfigError> {
|
|||
Ok(config)
|
|||
}
|
@ -1,84 +1,93 @@ |
|||
#[macro_use]
|
|||
extern crate serde_derive;
|
|||
#[macro_use]
|
|||
extern crate log;
|
|||
|
|||
extern crate actix_web;
|
|||
extern crate bytes;
|
|||
extern crate clap;
|
|||
extern crate cloudflare;
|
|||
extern crate env_logger;
|
|||
extern crate futures;
|
|||
#[macro_use]
|
|||
extern crate lazy_static;
|
|||
#[macro_use]
|
|||
extern crate log;
|
|||
extern crate num_cpus;
|
|||
extern crate serde;
|
|||
#[macro_use]
|
|||
extern crate serde_derive;
|
|||
extern crate serde_yaml;
|
|||
|
|||
use std::process;
|
|||
use std::sync::Arc;
|
|||
|
|||
use clap::{App, Arg};
|
|||
use cloudflare::Cloudflare;
|
|||
|
|||
mod args;
|
|||
mod config;
|
|||
mod server;
|
|||
|
|||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
|||
|
|||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|||
const DEFAULT_HOST: &str = "localhost";
|
|||
const DEFAULT_PORT: i16 = 8080;
|
|||
|
|||
lazy_static! {
|
|||
static ref DEFAULT_PORT_STR: String = DEFAULT_PORT.to_string();
|
|||
static ref DEFAULT_WORKERS: usize = num_cpus::get();
|
|||
static ref DEFAULT_WORKERS_STR: String = DEFAULT_WORKERS.to_string();
|
|||
}
|
|||
|
|||
fn main() {
|
|||
match std::env::var("RUST_LOG") {
|
|||
Ok(_) => (),
|
|||
Err(_) => std::env::set_var("RUST_LOG", "actix_web=info"),
|
|||
Err(_) => std::env::set_var("RUST_LOG", "error,rsddns=info,actix_web=info")
|
|||
}
|
|||
env_logger::init();
|
|||
|
|||
let args = App::new("Dynamic DNS Server")
|
|||
.version(VERSION)
|
|||
.author("Drew Short <warrick@sothr.com>")
|
|||
.about("Recieve DDNS requests and update cloudflare subdomains")
|
|||
.args(&[
|
|||
Arg::with_name("config")
|
|||
.short("c")
|
|||
.long("config")
|
|||
.value_name("PATH")
|
|||
.default_value("/etc/rsddns/rsddns.yml")
|
|||
.help("Set a custom configuration file path.")
|
|||
.takes_value(true),
|
|||
Arg::with_name("host")
|
|||
.short("h")
|
|||
.long("host")
|
|||
.value_name("HOST")
|
|||
.default_value("localhost")
|
|||
.help("The address the server listens on.")
|
|||
.takes_value(true),
|
|||
Arg::with_name("port")
|
|||
.short("p")
|
|||
.long("port")
|
|||
.value_name("PORT")
|
|||
.default_value("8080")
|
|||
.help("The port to run the server on.")
|
|||
.takes_value(true),
|
|||
Arg::with_name("workers")
|
|||
.short("w")
|
|||
.long("workers")
|
|||
.value_name("NUMBER")
|
|||
.help("The number of workers to serve requests with.")
|
|||
.takes_value(true),
|
|||
])
|
|||
.get_matches();
|
|||
let args = args::get_app().get_matches();
|
|||
|
|||
let config: &str = args.value_of("config").unwrap_or("/etc/rsddns/rsddns.yml");
|
|||
let host: &str = args.value_of("host").unwrap_or("localhost");
|
|||
let port: i32 = args
|
|||
.value_of("port")
|
|||
.unwrap()
|
|||
.parse::<i32>()
|
|||
.unwrap_or(8080);
|
|||
let workers: usize = match args.value_of("workers") {
|
|||
Some(count) => count.parse::<usize>().unwrap_or(num_cpus::get()),
|
|||
None => num_cpus::get(),
|
|||
let config_path: &str = args.value_of("config").unwrap_or("/etc/rsddns/rsddns.yml");
|
|||
let config = match config::load::read(config_path) {
|
|||
Ok(c) => {
|
|||
info!("Loaded configuration from \"{}\"", config_path);
|
|||
c
|
|||
}
|
|||
Err(e) => {
|
|||
error!("{}", e);
|
|||
process::exit(-1)
|
|||
}
|
|||
};
|
|||
|
|||
match config::validate::validate(&config) {
|
|||
Ok(_) => info!("Configuration Is Valid"),
|
|||
Err(e) => {
|
|||
error!("{}", e);
|
|||
process::exit(-1)
|
|||
}
|
|||
}
|
|||
|
|||
let host = args::parse::get_host(&args, &config, DEFAULT_HOST);
|
|||
let port = args::parse::get_port(&args, &config, DEFAULT_PORT);
|
|||
let workers = args::parse::get_workers(&args, &config, *DEFAULT_WORKERS);
|
|||
|
|||
let bind = format!("{}:{}", host, port);
|
|||
info!(
|
|||
"Starting server on {}:{} with workers={} and config {}",
|
|||
host, port, workers, config
|
|||
"Starting server on {} with workers={} and config \"{}\"",
|
|||
bind, workers, config_path
|
|||
);
|
|||
|
|||
actix_web::server::new(|| server::router::create())
|
|||
let shared_config = Arc::new(config);
|
|||
let shared_cloudflare = match Cloudflare::new(
|
|||
&shared_config.cloudflare.key,
|
|||
&shared_config.cloudflare.email,
|
|||
"https://api.cloudflare.com/client/v4/") {
|
|||
Ok(api) => Arc::new(api),
|
|||
Err(_e) => process::exit(-1)
|
|||
};
|
|||
|
|||
let actix_server = actix_web::server::new(move || server::router::create(shared_config.clone(), shared_cloudflare.clone()))
|
|||
.workers(workers)
|
|||
.bind(format!("{}:{}", host, port))
|
|||
.unwrap()
|
|||
.run();
|
|||
.bind(bind);
|
|||
|
|||
match actix_server {
|
|||
Ok(server) => server.run(),
|
|||
Err(e) => error!("{}", e)
|
|||
}
|
|||
}
|
@ -0,0 +1,59 @@ |
|||
use std::error;
|
|||
use std::fmt;
|
|||
use std::net;
|
|||
use std::result;
|
|||
|
|||
pub type Result<T> = result::Result<T, Error>;
|
|||
|
|||
#[derive(Debug)]
|
|||
pub enum Error {
|
|||
APIError(APIError),
|
|||
Cloudflare(cloudflare::errors::Error),
|
|||
AddrParseError(net::AddrParseError)
|
|||
}
|
|||
|
|||
impl From<APIError> for Error {
|
|||
fn from(err: APIError) -> Error {
|
|||
Error::APIError(err)
|
|||
}
|
|||
}
|
|||
|
|||
impl From<cloudflare::errors::Error> for Error {
|
|||
fn from(err: cloudflare::errors::Error) -> Error {
|
|||
Error::Cloudflare(err)
|
|||
}
|
|||
}
|
|||
|
|||
impl From<net::AddrParseError> for Error {
|
|||
fn from(err: net::AddrParseError) -> Error {
|
|||
Error::AddrParseError(err)
|
|||
}
|
|||
}
|
|||
|
|||
#[derive(Debug)]
|
|||
pub struct APIError {
|
|||
description: String,
|
|||
original_error: Option<Box<error::Error>>,
|
|||
}
|
|||
|
|||
impl APIError {
|
|||
pub fn new(description: &str, original_error: Option<Box<error::Error>>) -> Error {
|
|||
Error::from(APIError {
|
|||
description: String::from(description),
|
|||
original_error,
|
|||
})
|
|||
}
|
|||
}
|
|||
|
|||
impl error::Error for APIError {}
|
|||
|
|||
impl fmt::Display for APIError {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
match &self.original_error {
|
|||
Some(original_error) => {
|
|||
write!(f, "{}: \"{:?}\"", self.description, original_error)
|
|||
}
|
|||
None => write!(f, "{}", self.description)
|
|||
}
|
|||
}
|
|||
}
|
@ -0,0 +1,62 @@ |
|||
use actix_web::{HttpRequest, HttpResponse};
|
|||
use actix_web::error::Result;
|
|||
use actix_web::middleware::{Middleware, Started};
|
|||
|
|||
use crate::config::model::Config;
|
|||
use crate::config::model::UserConfig;
|
|||
use crate::server::router::AppState;
|
|||
use crate::server::util;
|
|||
|
|||
fn valid_username_and_token_in_vec(username: &str, token: &str, users: Vec<&UserConfig>) -> bool {
|
|||
for user in users {
|
|||
if user.username == username && user.token == token {
|
|||
return true;
|
|||
}
|
|||
}
|
|||
return false;
|
|||
}
|
|||
|
|||
pub struct APIAuthUser;
|
|||
|
|||
pub struct APIAuthRootAndZone;
|
|||
|
|||
impl Middleware<AppState> for APIAuthUser {
|
|||
fn start(&self, req: &HttpRequest<AppState>) -> Result<Started> {
|
|||
let config: &Config = &req.state().config;
|
|||
let username = util::get_username_from_request(req);
|
|||
let token = util::get_token_from_request(req);
|
|||
if username.is_none() || token.is_none() {
|
|||
Ok(Started::Response(HttpResponse::Unauthorized().into()))
|
|||
} else if config.is_valid_username_and_token(&username.unwrap(), &token.unwrap()) {
|
|||
Ok(Started::Done)
|
|||
} else {
|
|||
Ok(Started::Response(HttpResponse::Unauthorized().into()))
|
|||
}
|
|||
}
|
|||
}
|
|||
|
|||
impl Middleware<AppState> for APIAuthRootAndZone {
|
|||
fn start(&self, req: &HttpRequest<AppState>) -> Result<Started> {
|
|||
let config: &Config = &req.state().config;
|
|||
let root = util::get_match_value(req, "root");
|
|||
let zone = util::get_match_value(req, "zone");
|
|||
if root.is_none() || zone.is_none() {
|
|||
Ok(Started::Response(HttpResponse::BadRequest().into()))
|
|||
} else {
|
|||
match config.get_users_for_root_and_zone(&root.unwrap(), &zone.unwrap()) {
|
|||
Some(users) => {
|
|||
let username = util::get_username_from_request(req);
|
|||
let token = util::get_token_from_request(req);
|
|||
if username.is_none() || token.is_none() {
|
|||
Ok(Started::Response(HttpResponse::BadRequest().into()))
|
|||
} else if valid_username_and_token_in_vec(&username.unwrap(), &token.unwrap(), users) {
|
|||
Ok(Started::Done)
|
|||
} else {
|
|||
Ok(Started::Response(HttpResponse::Unauthorized().into()))
|
|||
}
|
|||
}
|
|||
None => Ok(Started::Response(HttpResponse::Unauthorized().into()))
|
|||
}
|
|||
}
|
|||
}
|
|||
}
|
@ -0,0 +1 @@ |
|||
pub mod api_auth;
|
@ -1,19 +1,23 @@ |
|||
use actix_web::{HttpRequest, Json, Result};
|
|||
|
|||
use VERSION;
|
|||
use crate::server::router::AppState;
|
|||
use crate::VERSION;
|
|||
|
|||
pub mod api;
|
|||
pub mod error;
|
|||
pub mod middleware;
|
|||
pub mod router;
|
|||
pub mod util;
|
|||
|
|||
#[derive(Serialize)]
|
|||
pub struct Health {
|
|||
version: &'static str,
|
|||
}
|
|||
|
|||
pub fn index(_req: &HttpRequest) -> &'static str {
|
|||
pub fn index(_req: &HttpRequest<AppState>) -> &'static str {
|
|||
"Hello, World!"
|
|||
}
|
|||
|
|||
pub fn healthcheck(_req: HttpRequest) -> Result<Json<Health>> {
|
|||
pub fn healthcheck(_req: HttpRequest<AppState>) -> Result<Json<Health>> {
|
|||
Ok(Json(Health { version: VERSION }))
|
|||
}
|
@ -0,0 +1,52 @@ |
|||
use actix_web::HttpRequest;
|
|||
|
|||
use crate::config::model::Config;
|
|||
use crate::config::model::UserConfig;
|
|||
use crate::server::router::AppState;
|
|||
|
|||
pub fn get_match_value<S>(req: &HttpRequest<S>, key: &str) -> Option<String> {
|
|||
let match_info = req.resource().match_info();
|
|||
match match_info.get(key) {
|
|||
Some(value) => Some(String::from(value)),
|
|||
None => None
|
|||
}
|
|||
}
|
|||
|
|||
pub fn get_header_value<S>(req: &HttpRequest<S>, key: &str) -> Option<String> {
|
|||
match req.headers().get(key) {
|
|||
Some(header) => match header.to_str() {
|
|||
Ok(header_value) => Some(String::from(header_value)),
|
|||
Err(_e) => None
|
|||
},
|
|||
None => None
|
|||
}
|
|||
}
|
|||
|
|||
pub fn get_user_from_request(req: &HttpRequest<AppState>) -> Option<&UserConfig> {
|
|||
let config: &Config = &req.state().config;
|
|||
let username = get_username_from_request(req);
|
|||
match username {
|
|||
Some(username) => config.get_user(&username),
|
|||
None => None
|
|||
}
|
|||
}
|
|||
|
|||
pub fn get_username_from_request<S>(req: &HttpRequest<S>) -> Option<String> {
|
|||
get_header_value(req, "X-AUTH-USERNAME")
|
|||
}
|
|||
|
|||
pub fn get_token_from_request<S>(req: &HttpRequest<S>) -> Option<String> {
|
|||
get_header_value(req, "X-AUTH-TOKEN")
|
|||
}
|
|||
|
|||
pub fn get_domain_from_root(root: &str) -> Option<String> {
|
|||
let mut parts: Vec<&str> = root.split('.').collect();
|
|||
let length = parts.len();
|
|||
if length <= 1 {
|
|||
None
|
|||
} else if length == 2 {
|
|||
Some(String::from(root))
|
|||
} else {
|
|||
Some(String::from(parts.split_off(length - 2).join(".")))
|
|||
}
|
|||
}
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue