diff --git a/README.md b/README.md index 3ab456c..3d2b239 100644 --- a/README.md +++ b/README.md @@ -311,8 +311,6 @@ To enable OIDC authentication, set the following environment variables in your a - **`AUTH_OIDC_CLIENT_SECRET`**: The client secret associated with your OIDC provider. This value is specific to the OIDC client created for the UniFi Voucher Site. -> Please note that **enabling OIDC support will automatically disable the built-in login system**. Once OIDC is activated, all user authentication will be handled through your configured identity provider, and the local login mechanism will no longer be available. - > Ensure your idP supports **Confidential Clients** with the **Authorization Code Flow** #### Determine Supported Client Types @@ -363,6 +361,8 @@ If you prefer not to use any authentication for the web and api service, you can AUTH_DISABLE: 'true' ``` +> Note: This disables the token based authentication on the API + ## Print Functionality The UniFi Voucher Site application includes built-in support for printing vouchers using 80mm receipt printers, offering a convenient way to distribute vouchers in physical format. diff --git a/middlewares/authorization.js b/middlewares/authorization.js index d93bf7a..8a5fa72 100644 --- a/middlewares/authorization.js +++ b/middlewares/authorization.js @@ -24,34 +24,48 @@ module.exports = { * @return {Promise} */ web: async (req, res, next) => { - // Check if authentication is enabled & OIDC is disabled - if(!variables.authDisabled && !variables.authOidcEnabled) { + let internal = false; + let oidc = false; + + // Continue is authentication is disabled + if(variables.authDisabled) { + next(); + return; + } + + // Check if Internal auth is enabled then verify user status + if(variables.authInternalEnabled) { // Check if user has an existing authorization cookie - if (!req.cookies.authorization) { - res.redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/login`); - return; - } + if (req.cookies.authorization) { + // Check if token is correct and valid + try { + const check = jwt.verify(req.cookies.authorization); - // Check if token is correct and valid - try { - const check = jwt.verify(req.cookies.authorization); - - if(!check) { - res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Invalid or expired login!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/login`); - } - } catch (e) { - res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Invalid or expired login!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/login`); - return; + if(check) { + internal = true; + } + } catch (e) {} } } - // Check if authentication is enabled & OIDC is enabled - if(!variables.authDisabled && variables.authOidcEnabled) { - const middleware = oidc.requiresAuth(); - return middleware(req, res, next); + // Check if OIDC is enabled then verify user status + if(variables.authOidcEnabled) { + oidc = req.oidc.isAuthenticated(); } - next(); + // Check if user is authorized by a service + if(internal || oidc) { + // Remove req.oidc if user is authenticated internally + if(internal) { + delete req.oidc; + } + + next(); + return; + } + + // Fallback to login page + res.redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/login`); }, /** diff --git a/modules/info.js b/modules/info.js index 5fb6688..1eb076a 100644 --- a/modules/info.js +++ b/modules/info.js @@ -72,7 +72,14 @@ module.exports = () => { /** * Log auth status */ - log.info(`[Auth] ${variables.authDisabled ? 'Disabled!' : `Enabled! Type: ${variables.authOidcEnabled ? 'OIDC' : 'Internal'}`}`); + log.info(`[Auth] ${variables.authDisabled ? 'Disabled!' : `Enabled! Type: ${variables.authInternalEnabled ? 'Internal' : ''}${variables.authInternalEnabled && variables.authOidcEnabled ? ', ' : ''}${variables.authOidcEnabled ? 'OIDC' : ''}`}`); + + /** + * Check auth services + */ + if(!variables.authDisabled && !variables.authInternalEnabled && !variables.authOidcEnabled) { + log.error(`[Auth] Incorrect Configuration Detected!. Authentication is enabled but all authentication services have been disabled`); + } /** * Verify OIDC configuration diff --git a/server.js b/server.js index 888677f..8046b67 100644 --- a/server.js +++ b/server.js @@ -48,7 +48,7 @@ info(); /** * Initialize JWT */ -if(!variables.authDisabled && !variables.authOidcEnabled) { +if(!variables.authDisabled && variables.authInternalEnabled) { jwt.init(); } @@ -124,41 +124,47 @@ app.get('/', (req, res) => { // Check if web service is enabled if(variables.serviceWeb) { - if(!variables.authOidcEnabled) { - app.get('/login', (req, res) => { - // Check if authentication is disabled - if (variables.authDisabled) { - res.redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`); - return; - } + app.get('/login', (req, res) => { + // Check if authentication is disabled + if (variables.authDisabled) { + res.redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`); + return; + } - const hour = new Date().getHours(); - const timeHeader = hour < 12 ? 'Good Morning' : hour < 18 ? 'Good Afternoon' : 'Good Evening'; + const hour = new Date().getHours(); + const timeHeader = hour < 12 ? 'Good Morning' : hour < 18 ? 'Good Afternoon' : 'Good Evening'; - res.render('login', { - baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '', - error: req.flashMessage.type === 'error', - error_text: req.flashMessage.message || '', - app_header: timeHeader - }); + res.render('login', { + baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '', + error: req.flashMessage.type === 'error', + error_text: req.flashMessage.message || '', + app_header: timeHeader, + internalAuth: variables.authInternalEnabled, + oidcAuth: variables.authOidcEnabled }); - app.post('/login', async (req, res) => { - if (typeof req.body === "undefined") { - res.status(400).send(); - return; - } + }); + app.post('/login', async (req, res) => { + // Check if internal authentication is enabled + if(!variables.authInternalEnabled) { + res.status(501).send(); + return; + } - const passwordCheck = req.body.password === variables.authInternalPassword; + if (typeof req.body === "undefined") { + res.status(400).send(); + return; + } - if (!passwordCheck) { - res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Password Invalid!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/login`); - return; - } + const passwordCheck = req.body.password === variables.authInternalPassword; - res.cookie('authorization', jwt.sign(), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`); - }); - } - app.get('/logout', (req, res) => { + if (!passwordCheck) { + res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Password Invalid!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/login`); + return; + } + + res.cookie('authorization', jwt.sign(), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`); + }); + app.get('/logout', [authorization.web], (req, res) => { // Check if authentication is disabled if (variables.authDisabled) { res.redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`); diff --git a/template/login.ejs b/template/login.ejs index 9b53249..6e68511 100644 --- a/template/login.ejs +++ b/template/login.ejs @@ -50,22 +50,51 @@
-
+ <% if(internalAuth) { %> + +
+ +
+ +
+
+ +
+ +
+
+ <% } %> + + <% if(oidcAuth) { %>
- -
- + <% if(internalAuth) { %> +
+ +
+ Or continue with +
+
+ <% } %> + +
- -
- -
- + <% } %>

- + diff --git a/utils/status.js b/utils/status.js index 54a2053..fcdff35 100644 --- a/utils/status.js +++ b/utils/status.js @@ -90,10 +90,10 @@ module.exports = () => { modules: { internal: { status: { - text: (!variables.authDisabled && !variables.authOidcEnabled) ? 'Enabled' : 'Disabled', - state: (!variables.authDisabled && !variables.authOidcEnabled) ? 'green' : 'red' + text: (!variables.authDisabled && variables.authInternalEnabled) ? 'Enabled' : 'Disabled', + state: (!variables.authDisabled && variables.authInternalEnabled) ? 'green' : 'red' }, - details: (!variables.authDisabled && !variables.authOidcEnabled) ? 'Internal Authentication enabled.' : 'Internal Authentication not enabled.', + details: (!variables.authDisabled && variables.authInternalEnabled) ? 'Internal Authentication enabled.' : 'Internal Authentication not enabled.', info: 'https://github.com/glenndehaan/unifi-voucher-site#1-internal-authentication-default' }, oidc: {