diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index 767b4f7c39..e1da2f93eb 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## ✨ 1.6.2 - Support for Guest Access [PR #167](https://github.com/Lissy93/dashy/pull/167)
+- Adds functionality for optional read-only guest access to dashboards with authentication
+- Can be enabled by setting `appConfig.enableGuestAccess: true`
+
## 💄 1.6.1 - Adds new Theme [PR #166](https://github.com/Lissy93/dashy/issues/166)
- Adds Dashy theme, for use in the dev dashboard
## ✨ 1.5.9 - New Minimal/ Startpage View [PR #155](https://github.com/Lissy93/dashy/issues/155)
diff --git a/README.md b/README.md
index b8ab6b203b..5bdc6adb2a 100644
--- a/README.md
+++ b/README.md
@@ -251,7 +251,10 @@ appConfig:
- user: alicia
hash: 4D1E58C90B3B94BCAD9848ECCACD6D2A8C9FBC5CA913304BBA5CDEAB36FEEFA3
```
-At present, access control is handled on the frontend, and therefore in security-critical situations, it is recommended to use an alternate method for authentication, such as [Authelia](https://www.authelia.com/), a VPN or web server and firewall rules.
+
+By default, when authentication is configured no user can access your dashboard without first logging in. If you would like to allow for read-only access by unauthenticated users, then you can enable guest mode, by setting `appConfig.enableGuestAccess: true`.
+
+**Note**: At present, access control is handled on the frontend, and therefore in security-critical situations, it is recommended to use an alternate method for authentication, such as [Authelia](https://www.authelia.com/), a VPN or web server and firewall rules. Instructions for setting this up can be found [in the docs](docs/authentication.md#alternative-authentication-methods).
+
+ {{ $t('config-editor.not-admin-note') }}
+
{{ responseText }}
{{ $t('config-editor.success-note-l1') }}
@@ -243,6 +246,10 @@ p.response-output {
}
}
+p.no-permission-note {
+ color: var(--config-settings-color);
+}
+
button.save-button {
padding: 0.5rem 1rem;
margin: 0.25rem auto;
diff --git a/src/components/Settings/AppButtons.vue b/src/components/Settings/AppButtons.vue
deleted file mode 100644
index 875ae11b41..0000000000
--- a/src/components/Settings/AppButtons.vue
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/components/Settings/AuthButtons.vue b/src/components/Settings/AuthButtons.vue
new file mode 100644
index 0000000000..29e082cbdd
--- /dev/null
+++ b/src/components/Settings/AuthButtons.vue
@@ -0,0 +1,85 @@
+
+
+
+
{{ makeText() }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Settings/SettingsContainer.vue b/src/components/Settings/SettingsContainer.vue
index 9c6a1bf6b1..e3e2e4d63e 100644
--- a/src/components/Settings/SettingsContainer.vue
+++ b/src/components/Settings/SettingsContainer.vue
@@ -13,7 +13,7 @@
-
+
{
+ if (!config || !config.appConfig) return false;
+ return config.appConfig.enableGuestAccess || false;
+};
+
+/* Returns true if user is already authenticated, or if auth is not enabled */
const isAuthenticated = () => {
const users = config.appConfig.auth;
- return (!users || users.length === 0 || isLoggedIn(users));
+ return (!users || users.length === 0 || isLoggedIn(users) || isGuestEnabled());
};
/* Get the users chosen starting view from app config, or return default */
@@ -94,13 +96,14 @@ const router = new Router({
appConfig: config.appConfig,
},
beforeEnter: (to, from, next) => {
- if (isAuthenticated()) router.push({ path: '/' });
+ // If the user already logged in + guest mode not enabled, then redirect home
+ if (isAuthenticated() && !isGuestEnabled()) router.push({ path: '/' });
next();
},
},
{ // The about app page
path: routePaths.about,
- name: 'about',
+ name: 'about', // We lazy load the About page so as to not slow down the app
component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
meta: makeMetaTags('About Dashy'),
},
@@ -115,9 +118,9 @@ const router = new Router({
});
/**
- * Before loading a route, check if the user has authentication enabled *
- * if so, then ensure that they are correctly logged in as a valid user *
- * If not logged in, prevent access and redirect them to the login page *
+ * Before loading a route, check if the user has authentication enabled
+ * if so, then ensure that they are correctly logged in as a valid user
+ * If not logged in, prevent all access and redirect them to login page
* */
router.beforeEach((to, from, next) => {
if (to.name !== 'login' && !isAuthenticated()) next({ name: 'login' });
@@ -131,5 +134,5 @@ router.afterEach((to) => {
});
});
-// Export the now configured router
+// All done - export the now configured router
export default router;
diff --git a/src/styles/style-helpers.scss b/src/styles/style-helpers.scss
index ebcbd6c131..7bac6d6d24 100644
--- a/src/styles/style-helpers.scss
+++ b/src/styles/style-helpers.scss
@@ -17,8 +17,6 @@
.svg-button {
color: var(--primary);
- width: 1.5rem;
- height: 1.5rem;
svg {
path {
fill: var(--settings-text-color);
diff --git a/src/utils/Auth.js b/src/utils/Auth.js
index 9ccca77c37..269b8e285e 100644
--- a/src/utils/Auth.js
+++ b/src/utils/Auth.js
@@ -1,5 +1,5 @@
import sha256 from 'crypto-js/sha256';
-import { cookieKeys, localStorageKeys } from './defaults';
+import { cookieKeys, localStorageKeys, userStateEnum } from './defaults';
/**
* Generates a 1-way hash, in order to be stored in local storage for authentication
@@ -34,6 +34,12 @@ export const isLoggedIn = (users) => {
return userAuthenticated;
};
+/* Returns true if authentication is enabled */
+export const isAuthEnabled = (users) => (users && users.length > 0);
+
+/* Returns true if guest access is enabled */
+export const isGuestAccessEnabled = (appConfig) => appConfig.enableGuestAccess || false;
+
/**
* Checks credentials entered by the user against those in the config
* Returns an object containing a boolean indicating success/ failure
@@ -107,3 +113,20 @@ export const isUserAdmin = (users) => {
});
return isAdmin;
};
+
+/**
+ * Determines which button should display, based on the user type
+ * 0 = Auth not configured (don't show anything)
+ * 1 = Auth configured, and user logged in (show logout button)
+ * 2 = Auth configured, guest access enabled, not logged in (show login)
+ * Note that if auth is enabled, but not guest access, and user not logged in,
+ * then they will never be able to view the homepage, so no button needed
+ */
+export const getUserState = (appConfig) => {
+ const { notConfigured, loggedIn, guestAccess } = userStateEnum; // Numeric enum options
+ const users = appConfig.auth || []; // Get auth object
+ if (!isAuthEnabled(users)) return notConfigured; // No auth enabled
+ if (isLoggedIn(users)) return loggedIn; // User is logged in
+ if (isGuestAccessEnabled(appConfig)) return guestAccess; // Guest is viewing
+ return notConfigured;
+};
diff --git a/src/utils/ConfigSchema.json b/src/utils/ConfigSchema.json
index 9637195a4b..96aef5e21d 100644
--- a/src/utils/ConfigSchema.json
+++ b/src/utils/ConfigSchema.json
@@ -248,6 +248,11 @@
}
}
},
+ "enableGuestAccess": {
+ "type": "boolean",
+ "default": false,
+ "description": "If set to true, an unauthenticated user will be able to have read-only access to dashboard, without needing to login. Requires auth to be configured."
+ },
"enableMultiTasking": {
"type": "boolean",
"default": false,
diff --git a/src/utils/defaults.js b/src/utils/defaults.js
index a1896251f0..4d3d400e04 100644
--- a/src/utils/defaults.js
+++ b/src/utils/defaults.js
@@ -166,4 +166,11 @@ module.exports = {
],
/* Use your own self-hosted Sentry instance. Only used if error reporting is turned on */
sentryDsn: 'https://3138ea85f15a4fa883a5b27a4dc8ee28@o937511.ingest.sentry.io/5887934',
+ /* A JS enum for indicating the user state, when guest mode + authentication is enabled */
+ userStateEnum: {
+ notConfigured: 0,
+ loggedIn: 1,
+ guestAccess: 2,
+ notLoggedIn: 3,
+ },
};
diff --git a/src/views/Login.vue b/src/views/Login.vue
index 1b8e147b74..3e287a6cc1 100644
--- a/src/views/Login.vue
+++ b/src/views/Login.vue
@@ -1,6 +1,25 @@
@@ -36,7 +76,12 @@ import router from '@/router';
import Button from '@/components/FormElements/Button';
import Input from '@/components/FormElements/Input';
import Defaults, { localStorageKeys } from '@/utils/defaults';
-import { checkCredentials, login } from '@/utils/Auth';
+import {
+ checkCredentials,
+ login,
+ isLoggedIn,
+ logout,
+} from '@/utils/Auth';
export default {
name: 'login',
@@ -76,6 +121,21 @@ export default {
successMsg: this.$t('login.success-message'),
};
},
+ existingUsername() {
+ return localStorage[localStorageKeys.USERNAME];
+ },
+ isUserAlreadyLoggedIn() {
+ const users = this.appConfig.auth;
+ const loggedIn = (!users || users.length === 0 || isLoggedIn(users));
+ return (loggedIn && this.existingUsername);
+ },
+ isGuestAccessEnabled() {
+ if (!this.appConfig || !this.appConfig.enableGuestAccess) return false;
+ return this.appConfig.enableGuestAccess;
+ },
+ isAuthenticationEnabled() {
+ return (this.appConfig && this.appConfig.auth && this.appConfig.auth.length > 0);
+ },
},
methods: {
/* Checks form is filled in, then initiates the login, and redirects to /home */
@@ -93,11 +153,42 @@ export default {
this.status = response.correct ? 'success' : 'error';
if (response.correct) { // Yay, credentials were correct :)
login(this.username, this.password, timeout); // Login, to set the cookie
- setTimeout(() => { // Wait a short while, then redirect back home
- router.push({ path: '/' });
- }, 250);
+ this.goHome();
+ }
+ },
+ /* Calls function to double-check guest access enabled, then log in as guest */
+ guestLogin() {
+ const isAllowed = this.isGuestAccessEnabled;
+ if (isAllowed) {
+ this.$toasted.show('Logged in as Guest, Redirecting...', { className: 'toast-success' });
+ this.goHome();
+ } else {
+ this.$toasted.show('Guest access not allowed', { className: 'toast-error' });
}
},
+ /* Calls logout, shows status message, and refreshed page */
+ getOut() {
+ logout();
+ this.status = 'success';
+ this.message = 'Logging out...';
+ this.refreshPage();
+ },
+ /* Logged in user redirects to home page */
+ stayLoggedIn() {
+ this.status = 'success';
+ this.message = 'Redirecting...';
+ this.goHome();
+ },
+ /* Refreshes the page */
+ refreshPage() {
+ setTimeout(() => { location.reload(); }, 250); // eslint-disable-line no-restricted-globals
+ },
+ /* Redirects to the homepage */
+ goHome() {
+ setTimeout(() => { // Wait a short while, then redirect back home
+ router.push({ path: '/' });
+ }, 250);
+ },
/* Since Theme setter isn't loaded at this point, we must manually get and apply users theme */
setTheme() {
const theme = localStorage[localStorageKeys.THEME] || Defaults.theme;
@@ -119,23 +210,43 @@ export default {
display: flex;
align-items: center;
justify-content: center;
+ justify-content: space-evenly;
min-height: calc(100vh - var(--footer-height));
+ /* User is already logged in note */
+ div.already-logged-in {
+ margin: 0 auto 0.5rem;
+ p.already-logged-in {
+ margin: 0 auto 0.5rem;
+ text-align: center;
+ }
+ span.username {
+ font-weight: bold;
+ text-transform: capitalize;
+ }
+ span.already-logged-in-note {
+ font-size: 0.8rem;
+ opacity: var(--dimming-factor);
+ text-align: left;
+ }
+ }
+
/* Login form container */
- form.login-form {
+ form.login-form, form.guest-form, div.already-logged-in, div.not-configured {
background: var(--login-form-background);
color: var(--login-form-color);
border: 1px solid var(--login-form-color);
border-radius: var(--curve-factor);
font-size: 1.4rem;
padding: 2rem;
- margin: 2rem auto;
+ margin: 2rem;
+ max-width: 22rem;
display: flex;
flex-direction: column;
/* Login form title */
- h2.login-title {
- font-size: 3rem;
+ h2 {
+ font-size: 2rem;
margin: 0 0 1rem 0;
text-align: center;
cursor: default;
@@ -177,6 +288,11 @@ export default {
&.success { color: var(--success); }
&.error { color: var(--warning); }
}
+ p.guest-intro {
+ font-size: 0.8rem;
+ opacity: var(--dimming-factor);
+ text-align: left;
+ }
}
}