mirror of
https://github.com/etiennecollin/unifi-voucher-manager.git
synced 2025-10-23 00:02:10 +00:00
fix: allow using self-signed certs
Closes https://github.com/etiennecollin/unifi-voucher-manager/issues/3
This commit is contained in:
33
README.md
33
README.md
@@ -115,22 +115,23 @@ Make sure to configure the required variables. The optional variables generally
|
||||
|
||||
To configure the WiFi QR code, you are required to configure the `WIFI_SSID` and `WIFI_PASSWORD` variables.
|
||||
|
||||
| Variable | Type | Description | Example | Type |
|
||||
| ------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| `UNIFI_CONTROLLER_URL` | Required | URL to your UniFi controller with protocol. | `https://unifi.example.com:8443` | `string` |
|
||||
| `UNIFI_API_KEY` | Required | API Key for your UniFi controller. | `abc123...` | `string` |
|
||||
| `UNIFI_SITE_ID` | Optional | Site ID of your UniFi controller. Using the value `default`, the backend will try to fetch the ID of the default site. | `default` (default) | `string` |
|
||||
| `FRONTEND_BIND_HOST` | Optional | Address on which the frontend server binds. | `0.0.0.0` (default) | `IPv4` |
|
||||
| `FRONTEND_BIND_PORT` | Optional | Port on which the frontend server binds. | `3000` (default) | `u16` |
|
||||
| `FRONTEND_TO_BACKEND_URL` | Optional | URL where the frontend will make its API requests to the backend. | `http://127.0.0.1` (default) | `URL` |
|
||||
| `BACKEND_BIND_HOST` | Optional | Address on which the server binds. | `127.0.0.1` (default) | `IPv4` |
|
||||
| `BACKEND_BIND_PORT` | Optional | Port on which the backend server binds. | `8080` (default) | `u16` |
|
||||
| `BACKEND_LOG_LEVEL` | Optional | Log level of the Rust backend. | `info`(default) | `trace\|debug\|info\|warn\|error` |
|
||||
| `TIMEZONE` | Optional | [Timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List) used to format dates and time. | `UTC` (default) | [`timezone`](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List) |
|
||||
| `WIFI_SSID` | Optional | WiFi SSID used for the QR code. (required for QR code to be generated) | `My WiFi SSID` | `string` |
|
||||
| `WIFI_PASSWORD` | Optional | WiFi password used for the QR code (required for QR code to be generated) | `My WiFi Password` | `string` |
|
||||
| `WIFI_TYPE` | Optional | WiFi security type used. Defaults to `WPA` if a password is provided and `nopass` otherwise. | `WPA` | `WPA\|WEP\|nopass` |
|
||||
| `WIFI_HIDDEN` | Optional | Whether the WiFi SSID is hidden or broadcasted. | `false` (default) | `bool` |
|
||||
| Variable | Type | Description | Example | Type |
|
||||
| ------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| `UNIFI_CONTROLLER_URL` | Required | URL to your UniFi controller with protocol (`http://` or `https://`). | `https://unifi.example.com` or `https://192.168.8.1:443` | `string` |
|
||||
| `UNIFI_API_KEY` | Required | API Key for your UniFi controller. | `abc123...` | `string` |
|
||||
| `UNIFI_HAS_VALID_CERT` | Optional | Whether your UniFi controller has a valid, non-self signed certificate. **If you directly use the controller IP address in the URL, this should probably be set to `false`.** | `true` (default) | `bool` |
|
||||
| `UNIFI_SITE_ID` | Optional | Site ID of your UniFi controller. Using the value `default`, the backend will try to fetch the ID of the default site. | `default` (default) | `string` |
|
||||
| `FRONTEND_BIND_HOST` | Optional | Address on which the frontend server binds. | `0.0.0.0` (default) | `IPv4` |
|
||||
| `FRONTEND_BIND_PORT` | Optional | Port on which the frontend server binds. | `3000` (default) | `u16` |
|
||||
| `FRONTEND_TO_BACKEND_URL` | Optional | URL where the frontend will make its API requests to the backend. | `http://127.0.0.1` (default) | `URL` |
|
||||
| `BACKEND_BIND_HOST` | Optional | Address on which the server binds. | `127.0.0.1` (default) | `IPv4` |
|
||||
| `BACKEND_BIND_PORT` | Optional | Port on which the backend server binds. | `8080` (default) | `u16` |
|
||||
| `BACKEND_LOG_LEVEL` | Optional | Log level of the Rust backend. | `info`(default) | `trace\|debug\|info\|warn\|error` |
|
||||
| `TIMEZONE` | Optional | [Timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List) used to format dates and time. | `UTC` (default) | [`timezone`](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List) |
|
||||
| `WIFI_SSID` | Optional | WiFi SSID used for the QR code. (required for QR code to be generated) | `My WiFi SSID` | `string` |
|
||||
| `WIFI_PASSWORD` | Optional | WiFi password used for the QR code (required for QR code to be generated) | `My WiFi Password` | `string` |
|
||||
| `WIFI_TYPE` | Optional | WiFi security type used. Defaults to `WPA` if a password is provided and `nopass` otherwise. | `WPA` | `WPA\|WEP\|nopass` |
|
||||
| `WIFI_HIDDEN` | Optional | Whether the WiFi SSID is hidden or broadcasted. | `false` (default) | `bool` |
|
||||
|
||||
### Getting UniFi API Credentials
|
||||
|
||||
|
@@ -20,6 +20,7 @@ pub struct Environment {
|
||||
pub unifi_api_key: String,
|
||||
pub backend_bind_host: String,
|
||||
pub backend_bind_port: u16,
|
||||
pub unifi_has_valid_cert: bool,
|
||||
pub timezone: Tz,
|
||||
}
|
||||
|
||||
@@ -30,6 +31,13 @@ impl Environment {
|
||||
|
||||
let unifi_controller_url: String =
|
||||
env::var("UNIFI_CONTROLLER_URL").map_err(|e| format!("UNIFI_CONTROLLER_URL: {e}"))?;
|
||||
|
||||
if !unifi_controller_url.starts_with("http://")
|
||||
&& !unifi_controller_url.starts_with("https://")
|
||||
{
|
||||
return Err("UNIFI_CONTROLLER_URL must start with http:// or https://".to_string());
|
||||
}
|
||||
|
||||
let unifi_api_key: String =
|
||||
env::var("UNIFI_API_KEY").map_err(|e| format!("UNIFI_API_KEY: {e}"))?;
|
||||
let unifi_site_id: String =
|
||||
@@ -44,6 +52,17 @@ impl Environment {
|
||||
Err(_) => DEFAULT_BACKEND_BIND_PORT,
|
||||
};
|
||||
|
||||
let unifi_has_valid_cert: bool = match env::var("UNIFI_HAS_VALID_CERT") {
|
||||
Ok(val) => match val.trim().to_lowercase().as_str() {
|
||||
"true" | "1" | "yes" => true,
|
||||
"false" | "0" | "no" => false,
|
||||
_ => {
|
||||
return Err("Invalid UNIFI_HAS_VALID_CERT, must be true/false".to_string());
|
||||
}
|
||||
},
|
||||
Err(_) => true,
|
||||
};
|
||||
|
||||
let timezone: Tz = match env::var("TIMEZONE") {
|
||||
Ok(s) => match s.parse() {
|
||||
Ok(tz) => {
|
||||
@@ -67,6 +86,7 @@ impl Environment {
|
||||
unifi_api_key,
|
||||
backend_bind_host,
|
||||
backend_bind_port,
|
||||
unifi_has_valid_cert,
|
||||
timezone,
|
||||
})
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ use chrono::DateTime;
|
||||
use chrono_tz::Tz;
|
||||
use reqwest::{Client, ClientBuilder, StatusCode};
|
||||
use std::{sync::OnceLock, time::Duration};
|
||||
use tracing::{error, info, warn};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::{
|
||||
ENVIRONMENT, Environment,
|
||||
@@ -52,6 +52,7 @@ impl<'a> UnifiAPI<'a> {
|
||||
.timeout(Duration::from_secs(30))
|
||||
.connect_timeout(Duration::from_secs(10))
|
||||
.default_headers(headers)
|
||||
.danger_accept_invalid_certs(!environment.unifi_has_valid_cert)
|
||||
.use_rustls_tls()
|
||||
.build()
|
||||
.expect("Failed to build UniFi reqwest client");
|
||||
@@ -113,7 +114,11 @@ impl<'a> UnifiAPI<'a> {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => {
|
||||
let status = e.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
error!("Request failed with status {}: {}", status, e.without_url());
|
||||
error!(
|
||||
"Request failed with status {}: {:?}",
|
||||
status,
|
||||
e.without_url()
|
||||
);
|
||||
return Err(status);
|
||||
}
|
||||
};
|
||||
@@ -134,9 +139,24 @@ impl<'a> UnifiAPI<'a> {
|
||||
}
|
||||
};
|
||||
|
||||
// It's a successful response, now parse the JSON
|
||||
clean_response.json::<U>().await.map_err(|e| {
|
||||
error!("Failed to parse response JSON: {}", e);
|
||||
// It's a successful response, now get the response body
|
||||
let response_text = clean_response.text().await.map_err(|e| {
|
||||
error!("Failed to read response body: {:?}", e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
// Parse the response body as JSON
|
||||
let response_json: serde_json::Value =
|
||||
serde_json::from_str(&response_text).map_err(|e| {
|
||||
error!("Failed to parse response body as JSON: {:?}", e);
|
||||
debug!("Response body: {}", response_text);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
// Parse the JSON into the expected structure
|
||||
serde_json::from_value::<U>(response_json).map_err(|e| {
|
||||
error!("Failed to parse response JSON structure: {:?}", e);
|
||||
debug!("Response body: {}", response_text);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})
|
||||
}
|
||||
|
@@ -11,10 +11,12 @@ services:
|
||||
restart: "unless-stopped"
|
||||
ports:
|
||||
- "3000:3000"
|
||||
# SEE README FOR VALID ENVIRONMENT VARIABLES
|
||||
environment:
|
||||
UNIFI_CONTROLLER_URL: "URL to your UniFi controller with protocol (`http://` or `https://`)."
|
||||
UNIFI_API_KEY: "API Key for your UniFi controller."
|
||||
UNIFI_HAS_VALID_CERT: "true" # Set to false if your UniFi controller has a self-signed certificate
|
||||
# To put the environment variables in a `.env` file, uncomment the env_file
|
||||
# section and comment the environment section.
|
||||
# env_file:
|
||||
# - ".env"
|
||||
environment:
|
||||
UNIFI_CONTROLLER_URL: "URL to your Unifi controller with protocol. Example: 'https://unifi.example.com:8443' or 'http://192.168.0.1'"
|
||||
UNIFI_API_KEY: "API Key for your Unifi controller"
|
||||
|
@@ -15,7 +15,7 @@ export default function NotificationItem({ id, message, type, onDone }: Props) {
|
||||
useEffect(() => {
|
||||
// slide in next tick
|
||||
const showTimer = setTimeout(() => setVisible(true), 10);
|
||||
// slide out after 4s
|
||||
// slide out after X ms
|
||||
const hideTimer = setTimeout(
|
||||
() => setVisible(false),
|
||||
NOTIFICATION_DURATION_MS,
|
||||
|
@@ -38,7 +38,7 @@ export default function CustomCreateTab() {
|
||||
setNewCode(code);
|
||||
form.reset();
|
||||
} else {
|
||||
notify("Voucher created, but code not available", "error");
|
||||
notify("Voucher created, but code not found in response", "warning");
|
||||
}
|
||||
} catch {
|
||||
notify("Failed to create voucher", "error");
|
||||
|
@@ -31,7 +31,7 @@ export default function QuickCreateTab() {
|
||||
setNewCode(code);
|
||||
form.reset();
|
||||
} else {
|
||||
notify("Voucher created, but code not available", "error");
|
||||
notify("Voucher created, but code not found in response", "warning");
|
||||
}
|
||||
} catch {
|
||||
notify("Failed to create voucher", "error");
|
||||
|
Reference in New Issue
Block a user