Password protection for security settings API
Also implemented the base functionallity to protect further API endpoints.
This commit is contained in:
parent
af4b47beeb
commit
8d14dbd367
@ -168,7 +168,7 @@ Users report that [ESP_Flasher](https://github.com/Jason2866/ESP_Flasher/release
|
|||||||
## First configuration
|
## First configuration
|
||||||
* After the initial flashing of the microcontroller, an Access Point called "OpenDTU-*" is opened. The default password is "openDTU42".
|
* After the initial flashing of the microcontroller, an Access Point called "OpenDTU-*" is opened. The default password is "openDTU42".
|
||||||
* Use a web browser to open the address [http://192.168.4.1](http://192.168.4.1)
|
* Use a web browser to open the address [http://192.168.4.1](http://192.168.4.1)
|
||||||
* Navigate to Settings --> Network Settings and enter your WiFi credentials
|
* Navigate to Settings --> Network Settings and enter your WiFi credentials. The username to access the config menu is "admin" and the password the same as for accessing the Access Point (default: "openDTU42").
|
||||||
* OpenDTU then simultaneously connects to your WiFi AP with this credentials. Navigate to Info --> Network and look into section "Network Interface (Station)" for the IP address received via DHCP.
|
* OpenDTU then simultaneously connects to your WiFi AP with this credentials. Navigate to Info --> Network and look into section "Network Interface (Station)" for the IP address received via DHCP.
|
||||||
* When OpenDTU is connected to a configured WiFI AP, the "OpenDTU-*" Access Point is closed after 3 minutes.
|
* When OpenDTU is connected to a configured WiFI AP, the "OpenDTU-*" Access Point is closed after 3 minutes.
|
||||||
* OpenDTU needs access to a working NTP server to get the current date & time. Both are sent to the inverter with each request. Default NTP server is pool.ntp.org. If your network has different requirements please change accordingly (Settings --> NTP Settings).
|
* OpenDTU needs access to a working NTP server to get the current date & time. Both are sent to the inverter with each request. Default NTP server is pool.ntp.org. If your network has different requirements please change accordingly (Settings --> NTP Settings).
|
||||||
|
|||||||
@ -24,6 +24,8 @@ public:
|
|||||||
void init();
|
void init();
|
||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
|
static bool checkCredentials(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AsyncWebServer _server;
|
AsyncWebServer _server;
|
||||||
AsyncEventSource _events;
|
AsyncEventSource _events;
|
||||||
|
|||||||
@ -12,5 +12,7 @@ private:
|
|||||||
void onPasswordGet(AsyncWebServerRequest* request);
|
void onPasswordGet(AsyncWebServerRequest* request);
|
||||||
void onPasswordPost(AsyncWebServerRequest* request);
|
void onPasswordPost(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
|
void onAuthenticateGet(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
AsyncWebServer* _server;
|
||||||
};
|
};
|
||||||
@ -9,6 +9,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 ADMIN_TIMEOUT 180
|
#define ADMIN_TIMEOUT 180
|
||||||
#define WIFI_RECONNECT_TIMEOUT 15
|
#define WIFI_RECONNECT_TIMEOUT 15
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "ArduinoJson.h"
|
#include "ArduinoJson.h"
|
||||||
#include "AsyncJson.h"
|
#include "AsyncJson.h"
|
||||||
|
#include "Configuration.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
|
|
||||||
WebApiClass::WebApiClass()
|
WebApiClass::WebApiClass()
|
||||||
@ -55,4 +56,22 @@ void WebApiClass::loop()
|
|||||||
_webApiWsLive.loop();
|
_webApiWsLive.loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebApiClass::checkCredentials(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
CONFIG_T& config = Configuration.get();
|
||||||
|
if (request->authenticate(AUTH_USERNAME, config.Security_Password)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerResponse* r = request->beginResponse(401);
|
||||||
|
|
||||||
|
// WebAPI should set the X-Requested-With to prevent browser internal auth dialogs
|
||||||
|
if (!request->hasHeader("X-Requested-With")) {
|
||||||
|
r->addHeader(F("WWW-Authenticate"), F("Basic realm=\"Login Required\""));
|
||||||
|
}
|
||||||
|
request->send(r);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
WebApiClass WebApi;
|
WebApiClass WebApi;
|
||||||
@ -6,6 +6,7 @@
|
|||||||
#include "ArduinoJson.h"
|
#include "ArduinoJson.h"
|
||||||
#include "AsyncJson.h"
|
#include "AsyncJson.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
|
#include "WebApi.h"
|
||||||
#include "helper.h"
|
#include "helper.h"
|
||||||
|
|
||||||
void WebApiSecurityClass::init(AsyncWebServer* server)
|
void WebApiSecurityClass::init(AsyncWebServer* server)
|
||||||
@ -16,6 +17,7 @@ void WebApiSecurityClass::init(AsyncWebServer* server)
|
|||||||
|
|
||||||
_server->on("/api/security/password", HTTP_GET, std::bind(&WebApiSecurityClass::onPasswordGet, this, _1));
|
_server->on("/api/security/password", HTTP_GET, std::bind(&WebApiSecurityClass::onPasswordGet, this, _1));
|
||||||
_server->on("/api/security/password", HTTP_POST, std::bind(&WebApiSecurityClass::onPasswordPost, this, _1));
|
_server->on("/api/security/password", HTTP_POST, std::bind(&WebApiSecurityClass::onPasswordPost, this, _1));
|
||||||
|
_server->on("/api/security/authenticate", HTTP_GET, std::bind(&WebApiSecurityClass::onAuthenticateGet, this, _1));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiSecurityClass::loop()
|
void WebApiSecurityClass::loop()
|
||||||
@ -24,6 +26,10 @@ void WebApiSecurityClass::loop()
|
|||||||
|
|
||||||
void WebApiSecurityClass::onPasswordGet(AsyncWebServerRequest* request)
|
void WebApiSecurityClass::onPasswordGet(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
|
if (!WebApi.checkCredentials(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
@ -36,6 +42,10 @@ void WebApiSecurityClass::onPasswordGet(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
void WebApiSecurityClass::onPasswordPost(AsyncWebServerRequest* request)
|
void WebApiSecurityClass::onPasswordPost(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
|
if (!WebApi.checkCredentials(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
JsonObject retMsg = response->getRoot();
|
JsonObject retMsg = response->getRoot();
|
||||||
retMsg[F("type")] = F("warning");
|
retMsg[F("type")] = F("warning");
|
||||||
@ -87,6 +97,21 @@ void WebApiSecurityClass::onPasswordPost(AsyncWebServerRequest* request)
|
|||||||
retMsg[F("type")] = F("success");
|
retMsg[F("type")] = F("success");
|
||||||
retMsg[F("message")] = F("Settings saved!");
|
retMsg[F("message")] = F("Settings saved!");
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
if (!WebApi.checkCredentials(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonObject retMsg = response->getRoot();
|
||||||
|
retMsg[F("type")] = F("success");
|
||||||
|
retMsg[F("message")] = F("Authentication successfull!");
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
@ -14,6 +14,7 @@
|
|||||||
"@popperjs/core": "^2.11.6",
|
"@popperjs/core": "^2.11.6",
|
||||||
"bootstrap": "^5.2.2",
|
"bootstrap": "^5.2.2",
|
||||||
"bootstrap-icons-vue": "^1.8.1",
|
"bootstrap-icons-vue": "^1.8.1",
|
||||||
|
"mitt": "^3.0.0",
|
||||||
"spark-md5": "^3.0.2",
|
"spark-md5": "^3.0.2",
|
||||||
"vue": "^3.2.41",
|
"vue": "^3.2.41",
|
||||||
"vue-router": "^4.1.6"
|
"vue-router": "^4.1.6"
|
||||||
|
|||||||
@ -73,19 +73,41 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<form class="d-flex" role="search" v-if="isLogged">
|
||||||
|
<button class="btn btn-outline-danger" @click="signout">Logout</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import { logout, isLoggedIn } from '@/utils/authentication';
|
||||||
import { BIconSun } from 'bootstrap-icons-vue';
|
import { BIconSun } from 'bootstrap-icons-vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
BIconSun,
|
BIconSun,
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLogged: this.isLoggedIn(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$emitter.on("logged-in", () => {
|
||||||
|
this.isLogged = this.isLoggedIn();
|
||||||
|
});
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isLoggedIn,
|
||||||
|
logout,
|
||||||
|
signout(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.logout();
|
||||||
|
this.isLogged = this.isLoggedIn();
|
||||||
|
this.$router.push('/');
|
||||||
|
},
|
||||||
onClick() {
|
onClick() {
|
||||||
this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove("show");
|
this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove("show");
|
||||||
}
|
}
|
||||||
|
|||||||
9
webapp/src/emitter.d.ts
vendored
Normal file
9
webapp/src/emitter.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import mitt from 'mitt';
|
||||||
|
|
||||||
|
declare module '@vue/runtime-core' {
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
$emitter: Emitter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { } // Important! See note.
|
||||||
@ -1,12 +1,16 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
import mitt from 'mitt';
|
||||||
|
|
||||||
import './scss/styles.scss'
|
import './scss/styles.scss'
|
||||||
import "bootstrap"
|
import "bootstrap"
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
|
const emitter = mitt();
|
||||||
|
app.config.globalProperties.$emitter = emitter;
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import DtuAdminView from '@/views/DtuAdminView.vue'
|
|||||||
import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue'
|
import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue'
|
||||||
import ConfigAdminView from '@/views/ConfigAdminView.vue'
|
import ConfigAdminView from '@/views/ConfigAdminView.vue'
|
||||||
import SecurityAdminView from '@/views/SecurityAdminView.vue'
|
import SecurityAdminView from '@/views/SecurityAdminView.vue'
|
||||||
|
import LoginView from '@/views/LoginView.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@ -23,6 +24,11 @@ const router = createRouter({
|
|||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: HomeView
|
component: HomeView
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'Login',
|
||||||
|
component: LoginView
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/about',
|
||||||
name: 'About',
|
name: 'About',
|
||||||
@ -89,6 +95,23 @@ const router = createRouter({
|
|||||||
component: SecurityAdminView
|
component: SecurityAdminView
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
});
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
// redirect to login page if not logged in and trying to access a restricted page
|
||||||
|
const publicPages = ['/', '/login', '/about', '/info/network', '/info/system', '/info/ntp', '/info/mqtt',
|
||||||
|
'/settings/network', '/settings/ntp', '/settings/mqtt', '/settings/inverter', '/settings/dtu', '/firmware/upgrade', '/settings/config', ];
|
||||||
|
const authRequired = !publicPages.includes(to.path);
|
||||||
|
const loggedIn = localStorage.getItem('user');
|
||||||
|
|
||||||
|
if (authRequired && !loggedIn) {
|
||||||
|
return next({
|
||||||
|
path: '/login',
|
||||||
|
query: { returnUrl: to.path }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
78
webapp/src/utils/authentication.ts
Normal file
78
webapp/src/utils/authentication.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
export function authHeader(): HeadersInit {
|
||||||
|
// return authorization header with basic auth credentials
|
||||||
|
let user = JSON.parse(localStorage.getItem('user') || "");
|
||||||
|
|
||||||
|
if (user && user.authdata) {
|
||||||
|
return { 'Authorization': 'Basic ' + user.authdata };
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logout() {
|
||||||
|
// remove user from local storage to log user out
|
||||||
|
localStorage.removeItem('user');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLoggedIn(): boolean {
|
||||||
|
return (localStorage.getItem('user') != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function login(username: String, password: String) {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'Authorization': 'Basic ' + window.btoa(username + ':' + password)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return fetch('/api/security/authenticate', requestOptions)
|
||||||
|
.then(handleAuthResponse)
|
||||||
|
.then(retVal => {
|
||||||
|
// login successful if there's a user in the response
|
||||||
|
if (retVal) {
|
||||||
|
// store user details and basic auth credentials in local storage
|
||||||
|
// to keep user logged in between page refreshes
|
||||||
|
retVal.authdata = window.btoa(username + ':' + password);
|
||||||
|
localStorage.setItem('user', JSON.stringify(retVal));
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleResponse(response: Response) {
|
||||||
|
return response.text().then(text => {
|
||||||
|
const data = text && JSON.parse(text);
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 401) {
|
||||||
|
// auto logout if 401 response returned from api
|
||||||
|
logout();
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = (data && data.message) || response.statusText;
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAuthResponse(response: Response) {
|
||||||
|
return response.text().then(text => {
|
||||||
|
const data = text && JSON.parse(text);
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 401) {
|
||||||
|
// auto logout if 401 response returned from api
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = "Invalid credentials";
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,12 +1,19 @@
|
|||||||
import { timestampToString } from './time';
|
import { timestampToString } from './time';
|
||||||
import { formatNumber } from './number';
|
import { formatNumber } from './number';
|
||||||
|
import { login, logout, isLoggedIn } from './authentication';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
timestampToString,
|
timestampToString,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
isLoggedIn,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
timestampToString,
|
timestampToString,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
isLoggedIn,
|
||||||
}
|
}
|
||||||
88
webapp/src/views/LoginView.vue
Normal file
88
webapp/src/views/LoginView.vue
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<BasePage :title="'Login'" :isLoading="dataLoading">
|
||||||
|
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
|
||||||
|
{{ alertMessage }}
|
||||||
|
</BootstrapAlert>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header text-bg-danger">System Login</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<form @submit.prevent="handleSubmit">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" v-model="username" name="username" class="form-control"
|
||||||
|
:class="{ 'is-invalid': submitted && !username }" />
|
||||||
|
<div v-show="submitted && !username" class="invalid-feedback">Username is required</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label htmlFor="password">Password</label>
|
||||||
|
<input type="password" v-model="password" name="password" class="form-control"
|
||||||
|
:class="{ 'is-invalid': submitted && !password }" />
|
||||||
|
<div v-show="submitted && !password" class="invalid-feedback">Password is required</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button class="btn btn-primary" :disabled="dataLoading">Login</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BasePage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import router from '@/router';
|
||||||
|
import { login } from '@/utils';
|
||||||
|
import BasePage from '@/components/BasePage.vue';
|
||||||
|
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
BasePage,
|
||||||
|
BootstrapAlert,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dataLoading: false,
|
||||||
|
alertMessage: "",
|
||||||
|
alertType: "info",
|
||||||
|
showAlert: false,
|
||||||
|
returnUrl: '',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
submitted: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// get return url from route parameters or default to '/'
|
||||||
|
this.returnUrl = this.$route.query.returnUrl?.toString() || '/';
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleSubmit(e: Event) {
|
||||||
|
this.submitted = true;
|
||||||
|
const { username, password } = this;
|
||||||
|
|
||||||
|
// stop here if form is invalid
|
||||||
|
if (!(username && password)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataLoading = true;
|
||||||
|
login(username, password)
|
||||||
|
.then(
|
||||||
|
() => {
|
||||||
|
this.$emitter.emit("logged-in");
|
||||||
|
router.push(this.returnUrl);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.alertMessage = error;
|
||||||
|
this.alertType = 'danger';
|
||||||
|
this.showAlert = true;
|
||||||
|
this.dataLoading = false;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -26,8 +26,8 @@
|
|||||||
|
|
||||||
<div class="alert alert-secondary" role="alert">
|
<div class="alert alert-secondary" role="alert">
|
||||||
<b>Hint:</b>
|
<b>Hint:</b>
|
||||||
The administrator password is used to connect to the device when in AP mode.
|
The administrator password is used to access this web interface (user 'admin'), but also to
|
||||||
It must be 8..64 characters.
|
connect to the device when in AP mode. It must be 8..64 characters.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -41,6 +41,7 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
||||||
|
import { handleResponse, authHeader } from '@/utils/authentication';
|
||||||
import type { SecurityConfig } from '@/types/SecurityConfig';
|
import type { SecurityConfig } from '@/types/SecurityConfig';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -65,8 +66,8 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
getPasswordConfig() {
|
getPasswordConfig() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/security/password")
|
fetch("/api/security/password", { headers: authHeader() })
|
||||||
.then((response) => response.json())
|
.then(handleResponse)
|
||||||
.then(
|
.then(
|
||||||
(data) => {
|
(data) => {
|
||||||
this.securityConfigList = data;
|
this.securityConfigList = data;
|
||||||
@ -90,15 +91,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
fetch("/api/security/password", {
|
fetch("/api/security/password", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then(function (response) {
|
.then(handleResponse)
|
||||||
if (response.status != 200) {
|
|
||||||
throw response.status;
|
|
||||||
} else {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(
|
.then(
|
||||||
(response) => {
|
(response) => {
|
||||||
this.alertMessage = response.message;
|
this.alertMessage = response.message;
|
||||||
|
|||||||
@ -1474,6 +1474,11 @@ minimatch@^5.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^2.0.1"
|
brace-expansion "^2.0.1"
|
||||||
|
|
||||||
|
mitt@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd"
|
||||||
|
integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
|
||||||
|
|
||||||
ms@2.1.2:
|
ms@2.1.2:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user