mirror of
https://github.com/glenndehaan/unifi-voucher-site.git
synced 2026-03-31 06:24:00 -04:00
Added Login with OIDC button to login page. Made login.ejs dynamic based on enabled authentication services. Made GitHub icon on login.ejs smaller. Refactored authorization.js middleware to support running both internal and OIDC authentication within the same instance. Added extra error to info.js when both authentication services are disabled but authentication itself is enabled. Updated status.js to correctly display both authentication services running at the same time. Updated README.md. Enabled /login when OIDC is enabled. Added missing middleware on /logout. Fixed JWT not initializing when authInternalEnabled is true
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -24,34 +24,48 @@ module.exports = {
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
web: async (req, res, next) => {
|
||||
// Check if authentication is enabled & OIDC is disabled
|
||||
if(!variables.authDisabled && !variables.authOidcEnabled) {
|
||||
// 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`);
|
||||
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) {
|
||||
// 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`);
|
||||
if(check) {
|
||||
internal = true;
|
||||
}
|
||||
} 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;
|
||||
} 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();
|
||||
}
|
||||
|
||||
// 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`);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
16
server.js
16
server.js
@@ -48,7 +48,7 @@ info();
|
||||
/**
|
||||
* Initialize JWT
|
||||
*/
|
||||
if(!variables.authDisabled && !variables.authOidcEnabled) {
|
||||
if(!variables.authDisabled && variables.authInternalEnabled) {
|
||||
jwt.init();
|
||||
}
|
||||
|
||||
@@ -124,7 +124,6 @@ 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) {
|
||||
@@ -139,10 +138,18 @@ if(variables.serviceWeb) {
|
||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||
error: req.flashMessage.type === 'error',
|
||||
error_text: req.flashMessage.message || '',
|
||||
app_header: timeHeader
|
||||
app_header: timeHeader,
|
||||
internalAuth: variables.authInternalEnabled,
|
||||
oidcAuth: variables.authOidcEnabled
|
||||
});
|
||||
});
|
||||
app.post('/login', async (req, res) => {
|
||||
// Check if internal authentication is enabled
|
||||
if(!variables.authInternalEnabled) {
|
||||
res.status(501).send();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof req.body === "undefined") {
|
||||
res.status(400).send();
|
||||
return;
|
||||
@@ -157,8 +164,7 @@ if(variables.serviceWeb) {
|
||||
|
||||
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) => {
|
||||
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`);
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
<% if(internalAuth) { %>
|
||||
<form class="space-y-6" action="<%= baseUrl %>/login" method="post" enctype="multipart/form-data">
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium leading-6 text-gray-900 dark:text-white">Password</label>
|
||||
@@ -62,10 +63,38 @@
|
||||
<button type="submit" class="flex w-full justify-center rounded-md bg-sky-700 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-sky-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-700">Sign in</button>
|
||||
</div>
|
||||
</form>
|
||||
<% } %>
|
||||
|
||||
<% if(oidcAuth) { %>
|
||||
<div>
|
||||
<% if(internalAuth) { %>
|
||||
<div class="relative mt-10">
|
||||
<div class="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div class="w-full border-t border-black/5 dark:border-white/5"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center text-sm font-medium leading-6">
|
||||
<span class="bg-gray-100 dark:bg-gray-900 px-6 text-gray-900 dark:text-white">Or continue with</span>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="<%= internalAuth ? 'mt-6' : '' %>">
|
||||
<a href="<%= baseUrl %>/oidc/login" class="flex w-full items-center justify-center gap-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:ring-transparent">
|
||||
<svg class="h-5 w-5" viewBox="0 0 64 64" aria-hidden="true">
|
||||
<path fill="#f7931e" d="M29.1 6.4v54.5l9.7-4.5V1.7l-9.7 4.7z"></path>
|
||||
<path fill="#b2b2b2" d="M62.7 22.4L64 36.3l-18.7-4.1"></path>
|
||||
<path d="M40.5 19.6v6.2a29.54 29.54 0 0 1 10.6 3.8l6.9-4.2a43.585 43.585 0 0 0-17.5-5.8M9.7 40.2c0-6.9 7.5-12.7 17.7-14.4v-6.2C11.8 21.5 0 30 0 40.2 0 50.8 12.6 59.5 29.1 61v-6.1C18 53.5 9.7 47.4 9.7 40.2" fill="#b2b2b2"></path>
|
||||
<path d="M40.5 19.6v6.2a29.54 29.54 0 0 1 10.6 3.8l6.9-4.2a43.585 43.585 0 0 0-17.5-5.8M9.7 40.2c0-6.9 7.5-12.7 17.7-14.4v-6.2C11.8 21.5 0 30 0 40.2 0 50.8 12.6 59.5 29.1 61v-6.1C18 53.5 9.7 47.4 9.7 40.2" fill="#b2b2b2"></path>
|
||||
</svg>
|
||||
<span class="text-sm font-semibold leading-6">OpenID Connect</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<p class="mt-10 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
<a href="https://github.com/glenndehaan/unifi-voucher-site" aria-label="GitHub Project Link" target="_blank" rel="noreferrer noopener" class="hover:text-gray-600 dark:hover:text-gray-500">
|
||||
<svg class="inline-block w-10" viewBox="0 0 42 42" fill="currentColor">
|
||||
<svg class="inline-block w-8" viewBox="0 0 42 42" fill="currentColor">
|
||||
<path d="M21,0.5c-11.6,0-21,9.4-21,21c0,9.3,6,17.1,14.4,19.9c1.1,0.2,1.4-0.5,1.4-1c0-0.5,0-1.8,0-3.6C9.9,38.1,8.7,34,8.7,34c-1-2.4-2.3-3.1-2.3-3.1c-1.9-1.3,0.1-1.3,0.1-1.3c2.1,0.1,3.2,2.2,3.2,2.2c1.9,3.2,4.9,2.3,6.1,1.7c0.2-1.4,0.7-2.3,1.3-2.8c-4.7-0.5-9.6-2.3-9.6-10.4c0-2.3,0.8-4.2,2.2-5.6c-0.2-0.5-0.9-2.7,0.2-5.6c0,0,1.8-0.6,5.8,2.2c1.7-0.5,3.5-0.7,5.3-0.7c1.8,0,3.6,0.2,5.3,0.7c4-2.7,5.8-2.2,5.8-2.2c1.1,2.9,0.4,5,0.2,5.6c1.3,1.5,2.2,3.3,2.2,5.6c0,8.1-4.9,9.8-9.6,10.4c0.8,0.6,1.4,1.9,1.4,3.9c0,2.8,0,5.1,0,5.8c0,0.6,0.4,1.2,1.4,1C36,38.7,42,30.8,42,21.5C42,9.9,32.6,0.5,21,0.5z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user