Introduce configuration option to decide wether the web ui is accessable without password or not

This commit is contained in:
Thomas Basler 2022-11-22 23:32:52 +01:00
parent a06a8fec3d
commit 4bdbcbccc5
7 changed files with 33 additions and 9 deletions

View File

@ -87,6 +87,7 @@ struct CONFIG_T {
bool Mqtt_Hass_Expire; bool Mqtt_Hass_Expire;
char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1]; char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1];
bool Security_AllowReadonly;
}; };
class ConfigurationClass { class ConfigurationClass {

View File

@ -9,8 +9,8 @@ public:
void loop(); void loop();
private: private:
void onPasswordGet(AsyncWebServerRequest* request); void onSecurityGet(AsyncWebServerRequest* request);
void onPasswordPost(AsyncWebServerRequest* request); void onSecurityPost(AsyncWebServerRequest* request);
void onAuthenticateGet(AsyncWebServerRequest* request); void onAuthenticateGet(AsyncWebServerRequest* request);

View File

@ -10,6 +10,7 @@
#define ACCESS_POINT_NAME "OpenDTU-" #define ACCESS_POINT_NAME "OpenDTU-"
#define ACCESS_POINT_PASSWORD "openDTU42" #define ACCESS_POINT_PASSWORD "openDTU42"
#define AUTH_USERNAME "admin" #define AUTH_USERNAME "admin"
#define SECURITY_ALLOW_READONLY true
#define ADMIN_TIMEOUT 180 #define ADMIN_TIMEOUT 180
#define WIFI_RECONNECT_TIMEOUT 15 #define WIFI_RECONNECT_TIMEOUT 15

View File

@ -77,6 +77,7 @@ bool ConfigurationClass::write()
JsonObject security = doc.createNestedObject("security"); JsonObject security = doc.createNestedObject("security");
security["password"] = config.Security_Password; security["password"] = config.Security_Password;
security["allow_readonly"] = config.Security_AllowReadonly;
JsonArray inverters = doc.createNestedArray("inverters"); JsonArray inverters = doc.createNestedArray("inverters");
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
@ -197,6 +198,7 @@ bool ConfigurationClass::read()
JsonObject security = doc["security"]; JsonObject security = doc["security"];
strlcpy(config.Security_Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security_Password)); strlcpy(config.Security_Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security_Password));
config.Security_AllowReadonly = security["allow_readonly"] | SECURITY_ALLOW_READONLY;
JsonArray inverters = doc["inverters"]; JsonArray inverters = doc["inverters"];
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {

View File

@ -15,8 +15,8 @@ void WebApiSecurityClass::init(AsyncWebServer* server)
_server = server; _server = server;
_server->on("/api/security/password", HTTP_GET, std::bind(&WebApiSecurityClass::onPasswordGet, this, _1)); _server->on("/api/security/config", HTTP_GET, std::bind(&WebApiSecurityClass::onSecurityGet, this, _1));
_server->on("/api/security/password", HTTP_POST, std::bind(&WebApiSecurityClass::onPasswordPost, this, _1)); _server->on("/api/security/config", HTTP_POST, std::bind(&WebApiSecurityClass::onSecurityPost, this, _1));
_server->on("/api/security/authenticate", HTTP_GET, std::bind(&WebApiSecurityClass::onAuthenticateGet, this, _1)); _server->on("/api/security/authenticate", HTTP_GET, std::bind(&WebApiSecurityClass::onAuthenticateGet, this, _1));
} }
@ -24,7 +24,7 @@ void WebApiSecurityClass::loop()
{ {
} }
void WebApiSecurityClass::onPasswordGet(AsyncWebServerRequest* request) void WebApiSecurityClass::onSecurityGet(AsyncWebServerRequest* request)
{ {
if (!WebApi.checkCredentials(request)) { if (!WebApi.checkCredentials(request)) {
return; return;
@ -35,12 +35,13 @@ void WebApiSecurityClass::onPasswordGet(AsyncWebServerRequest* request)
const CONFIG_T& config = Configuration.get(); const CONFIG_T& config = Configuration.get();
root[F("password")] = config.Security_Password; root[F("password")] = config.Security_Password;
root[F("allow_readonly")] = config.Security_AllowReadonly;
response->setLength(); response->setLength();
request->send(response); request->send(response);
} }
void WebApiSecurityClass::onPasswordPost(AsyncWebServerRequest* request) void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
{ {
if (!WebApi.checkCredentials(request)) { if (!WebApi.checkCredentials(request)) {
return; return;
@ -76,7 +77,8 @@ void WebApiSecurityClass::onPasswordPost(AsyncWebServerRequest* request)
return; return;
} }
if (!root.containsKey("password")) { if (!root.containsKey("password")
&& root.containsKey("allow_readonly")) {
retMsg[F("message")] = F("Values are missing!"); retMsg[F("message")] = F("Values are missing!");
response->setLength(); response->setLength();
request->send(response); request->send(response);
@ -92,6 +94,7 @@ void WebApiSecurityClass::onPasswordPost(AsyncWebServerRequest* request)
CONFIG_T& config = Configuration.get(); CONFIG_T& config = Configuration.get();
strlcpy(config.Security_Password, root[F("password")].as<String>().c_str(), sizeof(config.Security_Password)); strlcpy(config.Security_Password, root[F("password")].as<String>().c_str(), sizeof(config.Security_Password));
config.Security_AllowReadonly = root[F("allow_readonly")].as<bool>();
Configuration.write(); Configuration.write();
retMsg[F("type")] = F("success"); retMsg[F("type")] = F("success");

View File

@ -1,3 +1,4 @@
export interface SecurityConfig { export interface SecurityConfig {
password: string; password: string;
allow_readonly: boolean;
} }

View File

@ -32,6 +32,22 @@
</div> </div>
</div> </div>
<div class="card mt-5">
<div class="card-header text-bg-primary">Permissions</div>
<div class="card-body">
<div class="row mb-3">
<label class="col-sm-6 form-check-label" for="inputReadonly">Allow readonly access to web interface</label>
<div class="col-sm-6">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputReadonly"
v-model="securityConfigList.allow_readonly" />
</div>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary mb-3">Save</button> <button type="submit" class="btn btn-primary mb-3">Save</button>
</form> </form>
</BasePage> </BasePage>
@ -66,7 +82,7 @@ export default defineComponent({
methods: { methods: {
getPasswordConfig() { getPasswordConfig() {
this.dataLoading = true; this.dataLoading = true;
fetch("/api/security/password", { headers: authHeader() }) fetch("/api/security/config", { headers: authHeader() })
.then((response) => handleResponse(response, this.$emitter, this.$router)) .then((response) => handleResponse(response, this.$emitter, this.$router))
.then( .then(
(data) => { (data) => {
@ -89,7 +105,7 @@ export default defineComponent({
const formData = new FormData(); const formData = new FormData();
formData.append("data", JSON.stringify(this.securityConfigList)); formData.append("data", JSON.stringify(this.securityConfigList));
fetch("/api/security/password", { fetch("/api/security/config", {
method: "POST", method: "POST",
headers: authHeader(), headers: authHeader(),
body: formData, body: formData,