diff options
-rw-r--r-- | Cargo.lock | 3 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/auth.rs | 16 | ||||
-rw-r--r-- | src/error.rs | 65 | ||||
-rw-r--r-- | src/routes/device.rs | 12 | ||||
-rw-r--r-- | src/routes/start.rs | 9 | ||||
-rw-r--r-- | src/wol.rs | 18 |
7 files changed, 76 insertions, 50 deletions
@@ -2353,7 +2353,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" | |||
2353 | 2353 | ||
2354 | [[package]] | 2354 | [[package]] |
2355 | name = "webol" | 2355 | name = "webol" |
2356 | version = "0.3.1" | 2356 | version = "0.3.2" |
2357 | dependencies = [ | 2357 | dependencies = [ |
2358 | "axum", | 2358 | "axum", |
2359 | "axum-macros", | 2359 | "axum-macros", |
@@ -2364,6 +2364,7 @@ dependencies = [ | |||
2364 | "serde_json", | 2364 | "serde_json", |
2365 | "sqlx", | 2365 | "sqlx", |
2366 | "surge-ping", | 2366 | "surge-ping", |
2367 | "thiserror", | ||
2367 | "time", | 2368 | "time", |
2368 | "tokio", | 2369 | "tokio", |
2369 | "tracing", | 2370 | "tracing", |
@@ -1,6 +1,6 @@ | |||
1 | [package] | 1 | [package] |
2 | name = "webol" | 2 | name = "webol" |
3 | version = "0.3.1" | 3 | version = "0.3.2" |
4 | edition = "2021" | 4 | edition = "2021" |
5 | 5 | ||
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
@@ -20,3 +20,4 @@ axum-macros = "0.4" | |||
20 | uuid = { version = "1.6", features = ["v4", "fast-rng"] } | 20 | uuid = { version = "1.6", features = ["v4", "fast-rng"] } |
21 | dashmap = "5.5" | 21 | dashmap = "5.5" |
22 | color-eyre = "0.6" | 22 | color-eyre = "0.6" |
23 | thiserror = "1.0" | ||
diff --git a/src/auth.rs b/src/auth.rs index feca652..eb4d1bf 100644 --- a/src/auth.rs +++ b/src/auth.rs | |||
@@ -9,7 +9,7 @@ pub fn auth(config: &Config, secret: Option<&HeaderValue>) -> Result<bool, Error | |||
9 | if let Some(value) = secret { | 9 | if let Some(value) = secret { |
10 | trace!("value exists"); | 10 | trace!("value exists"); |
11 | let key = &config.apikey; | 11 | let key = &config.apikey; |
12 | if value.to_str().map_err(Error::HeaderToStr)? == key.as_str() { | 12 | if value.to_str()? == key.as_str() { |
13 | debug!("successful auth"); | 13 | debug!("successful auth"); |
14 | Ok(true) | 14 | Ok(true) |
15 | } else { | 15 | } else { |
@@ -22,11 +22,17 @@ pub fn auth(config: &Config, secret: Option<&HeaderValue>) -> Result<bool, Error | |||
22 | } | 22 | } |
23 | } | 23 | } |
24 | 24 | ||
25 | #[derive(Debug)] | 25 | #[derive(Debug, thiserror::Error)] |
26 | pub enum Error { | 26 | pub enum Error { |
27 | #[error("wrong secret")] | ||
27 | WrongSecret, | 28 | WrongSecret, |
29 | #[error("missing secret")] | ||
28 | MissingSecret, | 30 | MissingSecret, |
29 | HeaderToStr(ToStrError) | 31 | #[error("parse error: {source}")] |
32 | HeaderToStr { | ||
33 | #[from] | ||
34 | source: ToStrError | ||
35 | } | ||
30 | } | 36 | } |
31 | 37 | ||
32 | impl Error { | 38 | impl Error { |
@@ -34,8 +40,8 @@ impl Error { | |||
34 | match self { | 40 | match self { |
35 | Self::WrongSecret => (StatusCode::UNAUTHORIZED, "Wrong credentials"), | 41 | Self::WrongSecret => (StatusCode::UNAUTHORIZED, "Wrong credentials"), |
36 | Self::MissingSecret => (StatusCode::BAD_REQUEST, "Missing credentials"), | 42 | Self::MissingSecret => (StatusCode::BAD_REQUEST, "Missing credentials"), |
37 | Self::HeaderToStr(err) => { | 43 | Self::HeaderToStr { source } => { |
38 | error!("server error: {}", err.to_string()); | 44 | error!("auth: {}", source); |
39 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") | 45 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") |
40 | }, | 46 | }, |
41 | } | 47 | } |
diff --git a/src/error.rs b/src/error.rs index 56d6c52..4f1bedd 100644 --- a/src/error.rs +++ b/src/error.rs | |||
@@ -1,44 +1,59 @@ | |||
1 | use std::io; | 1 | use crate::auth::Error as AuthError; |
2 | use axum::http::StatusCode; | 2 | use axum::http::StatusCode; |
3 | use axum::Json; | ||
4 | use axum::response::{IntoResponse, Response}; | 3 | use axum::response::{IntoResponse, Response}; |
4 | use axum::Json; | ||
5 | use serde_json::json; | 5 | use serde_json::json; |
6 | use std::io; | ||
6 | use tracing::error; | 7 | use tracing::error; |
7 | use crate::auth::Error as AuthError; | ||
8 | 8 | ||
9 | #[derive(Debug)] | 9 | #[derive(Debug, thiserror::Error)] |
10 | pub enum Error { | 10 | pub enum Error { |
11 | #[error("generic error")] | ||
11 | Generic, | 12 | Generic, |
12 | Auth(AuthError), | 13 | |
13 | DB(sqlx::Error), | 14 | #[error("auth: {source}")] |
14 | IpParse(<std::net::IpAddr as std::str::FromStr>::Err), | 15 | Auth { |
15 | BufferParse(std::num::ParseIntError), | 16 | #[from] |
16 | Broadcast(io::Error), | 17 | source: AuthError, |
18 | }, | ||
19 | |||
20 | #[error("db: {source}")] | ||
21 | Db { | ||
22 | #[from] | ||
23 | source: sqlx::Error, | ||
24 | }, | ||
25 | |||
26 | #[error("buffer parse: {source}")] | ||
27 | ParseInt { | ||
28 | #[from] | ||
29 | source: std::num::ParseIntError, | ||
30 | }, | ||
31 | |||
32 | #[error("io: {source}")] | ||
33 | Io { | ||
34 | #[from] | ||
35 | source: io::Error, | ||
36 | }, | ||
17 | } | 37 | } |
18 | 38 | ||
19 | impl IntoResponse for Error { | 39 | impl IntoResponse for Error { |
20 | fn into_response(self) -> Response { | 40 | fn into_response(self) -> Response { |
41 | error!("{}", self.to_string()); | ||
21 | let (status, error_message) = match self { | 42 | let (status, error_message) = match self { |
22 | Self::Auth(err) => { | 43 | Self::Auth { source } => source.get(), |
23 | err.get() | ||
24 | }, | ||
25 | Self::Generic => (StatusCode::INTERNAL_SERVER_ERROR, ""), | 44 | Self::Generic => (StatusCode::INTERNAL_SERVER_ERROR, ""), |
26 | Self::IpParse(err) => { | 45 | Self::Db { source } => { |
27 | error!("server error: {}", err.to_string()); | 46 | error!("{source}"); |
28 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") | ||
29 | }, | ||
30 | Self::DB(err) => { | ||
31 | error!("server error: {}", err.to_string()); | ||
32 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") | 47 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") |
33 | }, | 48 | } |
34 | Self::Broadcast(err) => { | 49 | Self::Io { source } => { |
35 | error!("server error: {}", err.to_string()); | 50 | error!("{source}"); |
36 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") | 51 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") |
37 | }, | 52 | } |
38 | Self::BufferParse(err) => { | 53 | Self::ParseInt { source } => { |
39 | error!("server error: {}", err.to_string()); | 54 | error!("{source}"); |
40 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") | 55 | (StatusCode::INTERNAL_SERVER_ERROR, "Server Error") |
41 | }, | 56 | } |
42 | }; | 57 | }; |
43 | let body = Json(json!({ | 58 | let body = Json(json!({ |
44 | "error": error_message, | 59 | "error": error_message, |
diff --git a/src/routes/device.rs b/src/routes/device.rs index c85df1b..aa52cf7 100644 --- a/src/routes/device.rs +++ b/src/routes/device.rs | |||
@@ -12,7 +12,7 @@ use crate::error::Error; | |||
12 | pub async fn get(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<GetDevicePayload>) -> Result<Json<Value>, Error> { | 12 | pub async fn get(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<GetDevicePayload>) -> Result<Json<Value>, Error> { |
13 | info!("add device {}", payload.id); | 13 | info!("add device {}", payload.id); |
14 | let secret = headers.get("authorization"); | 14 | let secret = headers.get("authorization"); |
15 | if auth(&state.config, secret).map_err(Error::Auth)? { | 15 | if auth(&state.config, secret)? { |
16 | let device = sqlx::query_as!( | 16 | let device = sqlx::query_as!( |
17 | Device, | 17 | Device, |
18 | r#" | 18 | r#" |
@@ -21,7 +21,7 @@ pub async fn get(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, | |||
21 | WHERE id = $1; | 21 | WHERE id = $1; |
22 | "#, | 22 | "#, |
23 | payload.id | 23 | payload.id |
24 | ).fetch_one(&state.db).await.map_err(Error::DB)?; | 24 | ).fetch_one(&state.db).await?; |
25 | 25 | ||
26 | debug!("got device {:?}", device); | 26 | debug!("got device {:?}", device); |
27 | 27 | ||
@@ -39,7 +39,7 @@ pub struct GetDevicePayload { | |||
39 | pub async fn put(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PutDevicePayload>) -> Result<Json<Value>, Error> { | 39 | pub async fn put(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PutDevicePayload>) -> Result<Json<Value>, Error> { |
40 | info!("add device {} ({}, {}, {})", payload.id, payload.mac, payload.broadcast_addr, payload.ip); | 40 | info!("add device {} ({}, {}, {})", payload.id, payload.mac, payload.broadcast_addr, payload.ip); |
41 | let secret = headers.get("authorization"); | 41 | let secret = headers.get("authorization"); |
42 | if auth(&state.config, secret).map_err(Error::Auth)? { | 42 | if auth(&state.config, secret)? { |
43 | sqlx::query!( | 43 | sqlx::query!( |
44 | r#" | 44 | r#" |
45 | INSERT INTO devices (id, mac, broadcast_addr, ip) | 45 | INSERT INTO devices (id, mac, broadcast_addr, ip) |
@@ -49,7 +49,7 @@ pub async fn put(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, | |||
49 | payload.mac, | 49 | payload.mac, |
50 | payload.broadcast_addr, | 50 | payload.broadcast_addr, |
51 | payload.ip | 51 | payload.ip |
52 | ).execute(&state.db).await.map_err(Error::DB)?; | 52 | ).execute(&state.db).await?; |
53 | 53 | ||
54 | Ok(Json(json!(PutDeviceResponse { success: true }))) | 54 | Ok(Json(json!(PutDeviceResponse { success: true }))) |
55 | } else { | 55 | } else { |
@@ -73,7 +73,7 @@ pub struct PutDeviceResponse { | |||
73 | pub async fn post(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PostDevicePayload>) -> Result<Json<Value>, Error> { | 73 | pub async fn post(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PostDevicePayload>) -> Result<Json<Value>, Error> { |
74 | info!("edit device {} ({}, {}, {})", payload.id, payload.mac, payload.broadcast_addr, payload.ip); | 74 | info!("edit device {} ({}, {}, {})", payload.id, payload.mac, payload.broadcast_addr, payload.ip); |
75 | let secret = headers.get("authorization"); | 75 | let secret = headers.get("authorization"); |
76 | if auth(&state.config, secret).map_err(Error::Auth)? { | 76 | if auth(&state.config, secret)? { |
77 | let device = sqlx::query_as!( | 77 | let device = sqlx::query_as!( |
78 | Device, | 78 | Device, |
79 | r#" | 79 | r#" |
@@ -85,7 +85,7 @@ pub async fn post(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, | |||
85 | payload.broadcast_addr, | 85 | payload.broadcast_addr, |
86 | payload.ip, | 86 | payload.ip, |
87 | payload.id | 87 | payload.id |
88 | ).fetch_one(&state.db).await.map_err(Error::DB)?; | 88 | ).fetch_one(&state.db).await?; |
89 | 89 | ||
90 | Ok(Json(json!(device))) | 90 | Ok(Json(json!(device))) |
91 | } else { | 91 | } else { |
diff --git a/src/routes/start.rs b/src/routes/start.rs index ce95bf3..66b7cb4 100644 --- a/src/routes/start.rs +++ b/src/routes/start.rs | |||
@@ -20,7 +20,7 @@ pub async fn start( | |||
20 | ) -> Result<Json<Value>, Error> { | 20 | ) -> Result<Json<Value>, Error> { |
21 | info!("POST request"); | 21 | info!("POST request"); |
22 | let secret = headers.get("authorization"); | 22 | let secret = headers.get("authorization"); |
23 | let authorized = auth(&state.config, secret).map_err(Error::Auth)?; | 23 | let authorized = auth(&state.config, secret)?; |
24 | if authorized { | 24 | if authorized { |
25 | let device = sqlx::query_as!( | 25 | let device = sqlx::query_as!( |
26 | Device, | 26 | Device, |
@@ -32,16 +32,15 @@ pub async fn start( | |||
32 | payload.id | 32 | payload.id |
33 | ) | 33 | ) |
34 | .fetch_one(&state.db) | 34 | .fetch_one(&state.db) |
35 | .await | 35 | .await?; |
36 | .map_err(Error::DB)?; | ||
37 | 36 | ||
38 | info!("starting {}", device.id); | 37 | info!("starting {}", device.id); |
39 | 38 | ||
40 | let bind_addr = "0.0.0.0:0"; | 39 | let bind_addr = "0.0.0.0:0"; |
41 | 40 | ||
42 | let _ = send_packet( | 41 | let _ = send_packet( |
43 | &bind_addr.parse().map_err(Error::IpParse)?, | 42 | bind_addr, |
44 | &device.broadcast_addr.parse().map_err(Error::IpParse)?, | 43 | &device.broadcast_addr, |
45 | &create_buffer(&device.mac)?, | 44 | &create_buffer(&device.mac)?, |
46 | )?; | 45 | )?; |
47 | let dev_id = device.id.clone(); | 46 | let dev_id = device.id.clone(); |
@@ -1,4 +1,4 @@ | |||
1 | use std::net::{SocketAddr, UdpSocket}; | 1 | use std::net::{ToSocketAddrs, UdpSocket}; |
2 | 2 | ||
3 | use crate::error::Error; | 3 | use crate::error::Error; |
4 | 4 | ||
@@ -11,8 +11,8 @@ pub fn create_buffer(mac_addr: &str) -> Result<Vec<u8>, Error> { | |||
11 | let mut mac = Vec::new(); | 11 | let mut mac = Vec::new(); |
12 | let sp = mac_addr.split(':'); | 12 | let sp = mac_addr.split(':'); |
13 | for f in sp { | 13 | for f in sp { |
14 | mac.push(u8::from_str_radix(f, 16).map_err(Error::BufferParse)?); | 14 | mac.push(u8::from_str_radix(f, 16)?); |
15 | }; | 15 | } |
16 | let mut buf = vec![255; 6]; | 16 | let mut buf = vec![255; 6]; |
17 | for _ in 0..16 { | 17 | for _ in 0..16 { |
18 | for i in &mac { | 18 | for i in &mac { |
@@ -23,8 +23,12 @@ pub fn create_buffer(mac_addr: &str) -> Result<Vec<u8>, Error> { | |||
23 | } | 23 | } |
24 | 24 | ||
25 | /// Sends a buffer on UDP broadcast | 25 | /// Sends a buffer on UDP broadcast |
26 | pub fn send_packet(bind_addr: &SocketAddr, broadcast_addr: &SocketAddr, buffer: &[u8]) -> Result<usize, Error> { | 26 | pub fn send_packet<A: ToSocketAddrs>( |
27 | let socket = UdpSocket::bind(bind_addr).map_err(Error::Broadcast)?; | 27 | bind_addr: A, |
28 | socket.set_broadcast(true).map_err(Error::Broadcast)?; | 28 | broadcast_addr: A, |
29 | socket.send_to(buffer, broadcast_addr).map_err(Error::Broadcast) | 29 | buffer: &[u8], |
30 | ) -> Result<usize, Error> { | ||
31 | let socket = UdpSocket::bind(bind_addr)?; | ||
32 | socket.set_broadcast(true)?; | ||
33 | Ok(socket.send_to(buffer, broadcast_addr)?) | ||
30 | } | 34 | } |