mirror of
https://github.com/glenndehaan/unifi-voucher-site.git
synced 2026-03-31 06:24:00 -04:00
Refactored server.js into separate controllers. Implemented UNIFI_TOKEN startup check within info.js. Implemented KIOSK_HOMEPAGE environment variable. Implemented new / redirect structure base on KIOSK_HOMEPAGE variable. Implemented Admin UI button within kiosk.ejs. Updated array.js with new deprecated variables. Updated docker-compose.yml. Updated README.md.
This commit is contained in:
BIN
.docs/images/integrations_example.png
Normal file
BIN
.docs/images/integrations_example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
21
README.md
21
README.md
@@ -41,18 +41,17 @@ UniFi Voucher Site is a web-based platform for generating and managing UniFi net
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- UniFi Network Controller (Cloud Key, Dream Machine, or Controller software)
|
- UniFi OS v4.2.8+
|
||||||
|
- UniFi Network v9.1.119+ (Cloud Key, Dream Machine, or UniFi OS software)
|
||||||
- UniFi Access Point (AP)
|
- UniFi Access Point (AP)
|
||||||
- UniFi Local Account with 'Full Management' access
|
- UniFi Integrations API Key
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
[Follow this guide to set up the Hotspot Portal](https://help.ui.com/hc/en-us/articles/115000166827-UniFi-Hotspot-Portal-and-Guest-WiFi), then continue with the installation below
|
[Follow this guide to set up the Hotspot Portal](https://help.ui.com/hc/en-us/articles/115000166827-UniFi-Hotspot-Portal-and-Guest-WiFi), then continue with the installation below
|
||||||
|
|
||||||
> Ensure voucher authentication is enabled within the Hotspot Portal
|
> Ensure voucher authentication is enabled within the Hotspot Portal
|
||||||
|
|
||||||
> Attention!: We recommend only using Local UniFi accounts due to short token lengths provided by UniFi Cloud Accounts. Also, UniFi Cloud Accounts using 2FA are not supported!
|
|
||||||
|
|
||||||
> Note: When creating a Local UniFi account ensure you give 'Full Management' access rights to the Network controller. The 'Hotspot Role' won't give access to the API and therefore the application will throw errors.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -81,6 +80,8 @@ services:
|
|||||||
UNIFI_USERNAME: 'admin'
|
UNIFI_USERNAME: 'admin'
|
||||||
# The password of a local UniFi OS account
|
# The password of a local UniFi OS account
|
||||||
UNIFI_PASSWORD: 'password'
|
UNIFI_PASSWORD: 'password'
|
||||||
|
# The API Key created on the integrations tab within UniFi OS
|
||||||
|
UNIFI_TOKEN: ''
|
||||||
# The UniFi Site ID
|
# The UniFi Site ID
|
||||||
UNIFI_SITE_ID: 'default'
|
UNIFI_SITE_ID: 'default'
|
||||||
# The UniFi SSID where guests need to connect to (Used within templating and 'Scan to Connect')
|
# The UniFi SSID where guests need to connect to (Used within templating and 'Scan to Connect')
|
||||||
@@ -142,6 +143,8 @@ services:
|
|||||||
KIOSK_NAME_REQUIRED: 'false'
|
KIOSK_NAME_REQUIRED: 'false'
|
||||||
# Enable/disable a printer for Kiosk Vouchers (this automatically prints vouchers), currently supported: escpos ip (Example: 192.168.1.10)
|
# Enable/disable a printer for Kiosk Vouchers (this automatically prints vouchers), currently supported: escpos ip (Example: 192.168.1.10)
|
||||||
KIOSK_PRINTER: ''
|
KIOSK_PRINTER: ''
|
||||||
|
# Enable/disable an override to redirect to the Kiosk on the / url (Also enables a link from the Kiosk back to the Admin UI)
|
||||||
|
KIOSK_HOMEPAGE: 'false'
|
||||||
# Sets the application Log Level (Valid Options: error|warn|info|debug|trace)
|
# Sets the application Log Level (Valid Options: error|warn|info|debug|trace)
|
||||||
LOG_LEVEL: 'info'
|
LOG_LEVEL: 'info'
|
||||||
# Sets the default translation for dropdowns
|
# Sets the default translation for dropdowns
|
||||||
@@ -173,6 +176,7 @@ The structure of the file should use lowercase versions of the environment varia
|
|||||||
"unifi_port": 443,
|
"unifi_port": 443,
|
||||||
"unifi_username": "admin",
|
"unifi_username": "admin",
|
||||||
"unifi_password": "password",
|
"unifi_password": "password",
|
||||||
|
"unifi_token": "",
|
||||||
"unifi_site_id": "default",
|
"unifi_site_id": "default",
|
||||||
"unifi_ssid": "",
|
"unifi_ssid": "",
|
||||||
"unifi_ssid_password": "",
|
"unifi_ssid_password": "",
|
||||||
@@ -201,6 +205,7 @@ The structure of the file should use lowercase versions of the environment varia
|
|||||||
"kiosk_voucher_types": "480,1,,,;",
|
"kiosk_voucher_types": "480,1,,,;",
|
||||||
"kiosk_name_required": false,
|
"kiosk_name_required": false,
|
||||||
"kiosk_printer": "",
|
"kiosk_printer": "",
|
||||||
|
"kiosk_homepage": false,
|
||||||
"log_level": "info",
|
"log_level": "info",
|
||||||
"translation_default": "en",
|
"translation_default": "en",
|
||||||
"translation_debug": false
|
"translation_debug": false
|
||||||
@@ -714,6 +719,10 @@ KIOSK_VOUCHER_TYPES: '480,1,,,;'
|
|||||||
KIOSK_PRINTER=192.168.1.50
|
KIOSK_PRINTER=192.168.1.50
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- **`KIOSK_HOMEPAGE`**:
|
||||||
|
- Set to `'true'` to redirect from `/` to `/kiosk` (Instead of the Admin UI).
|
||||||
|
- Set to `'false'` to disable the redirect functionality.
|
||||||
|
|
||||||
### Custom Branding (Logo and Background)
|
### Custom Branding (Logo and Background)
|
||||||
|
|
||||||
You can customize the appearance of the kiosk page by providing your own `logo.png` and `bg.jpg` images.
|
You can customize the appearance of the kiosk page by providing your own `logo.png` and `bg.jpg` images.
|
||||||
|
|||||||
247
controllers/api.js
Normal file
247
controllers/api.js
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
/**
|
||||||
|
* Import own modules
|
||||||
|
*/
|
||||||
|
const variables = require('../modules/variables');
|
||||||
|
const cache = require('../modules/cache');
|
||||||
|
const unifi = require('../modules/unifi');
|
||||||
|
const mail = require('../modules/mail');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import own utils
|
||||||
|
*/
|
||||||
|
const {updateCache} = require('../utils/cache');
|
||||||
|
const types = require('../utils/types');
|
||||||
|
const languages = require('../utils/languages');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
api: {
|
||||||
|
/**
|
||||||
|
* GET - /api
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: (req, res) => {
|
||||||
|
res.json({
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
message: 'OK',
|
||||||
|
endpoints: [
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
endpoint: '/api'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
endpoint: '/api/types'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
endpoint: '/api/languages'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
endpoint: '/api/vouchers'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
endpoint: '/api/voucher'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
types: {
|
||||||
|
/**
|
||||||
|
* GET - /api/types
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: (req, res) => {
|
||||||
|
res.json({
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
message: 'OK',
|
||||||
|
types: types(variables.voucherTypes)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
languages: {
|
||||||
|
/**
|
||||||
|
* GET - /api/languages
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: (req, res) => {
|
||||||
|
res.json({
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
message: 'OK',
|
||||||
|
languages: Object.keys(languages).map(language => {
|
||||||
|
return {
|
||||||
|
code: language,
|
||||||
|
name: languages[language]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
vouchers: {
|
||||||
|
/**
|
||||||
|
* GET - /api/vouchers
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: async (req, res) => {
|
||||||
|
res.json({
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
message: 'OK',
|
||||||
|
vouchers: cache.vouchers.map((voucher) => {
|
||||||
|
return {
|
||||||
|
id: voucher.id,
|
||||||
|
code: `${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`,
|
||||||
|
type: !voucher.authorizedGuestLimit ? 'multi' : voucher.authorizedGuestLimit === 1 ? 'single' : 'multi',
|
||||||
|
duration: voucher.timeLimitMinutes,
|
||||||
|
data_limit: voucher.dataUsageLimitMBytes ? voucher.dataUsageLimitMBytes : null,
|
||||||
|
download_limit: voucher.rxRateLimitKbps ? voucher.rxRateLimitKbps : null,
|
||||||
|
upload_limit: voucher.txRateLimitKbps ? voucher.txRateLimitKbps : null
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
updated: cache.updated
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
voucher: {
|
||||||
|
/**
|
||||||
|
* POST - /api/voucher
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
post: async (req, res) => {
|
||||||
|
// Verify valid body is sent
|
||||||
|
if(!req.body || !req.body.type) {
|
||||||
|
res.status(400).json({
|
||||||
|
error: 'Invalid Body!',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if email body is set
|
||||||
|
if(req.body.email) {
|
||||||
|
// Check if email module is enabled
|
||||||
|
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
|
||||||
|
res.status(400).json({
|
||||||
|
error: 'Email Not Configured!',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if email body is correct
|
||||||
|
if(!req.body.email.language || !req.body.email.address) {
|
||||||
|
res.status(400).json({
|
||||||
|
error: 'Invalid Body!',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if language is available
|
||||||
|
if(!Object.keys(languages).includes(req.body.email.language)) {
|
||||||
|
res.status(400).json({
|
||||||
|
error: 'Unknown Language!',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if type is implemented and valid
|
||||||
|
const typeCheck = (variables.voucherTypes).split(';').includes(req.body.type);
|
||||||
|
if(!typeCheck) {
|
||||||
|
res.status(400).json({
|
||||||
|
error: 'Unknown Type!',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create voucher code
|
||||||
|
const voucherCode = await unifi.create(types(req.body.type, true), 1, `||;;||api||;;||local||;;||`).catch((e) => {
|
||||||
|
res.status(500).json({
|
||||||
|
error: e,
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update application cache
|
||||||
|
await updateCache();
|
||||||
|
|
||||||
|
if(voucherCode) {
|
||||||
|
// Locate voucher data within cache
|
||||||
|
const voucherData = cache.vouchers.find(voucher => voucher.code === voucherCode.replaceAll('-', ''));
|
||||||
|
if(!voucherData) {
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Invalid application cache!',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should send and email
|
||||||
|
if(req.body.email) {
|
||||||
|
// Send mail
|
||||||
|
const emailResult = await mail.send(req.body.email.address, voucherData, req.body.email.language).catch((e) => {
|
||||||
|
res.status(500).json({
|
||||||
|
error: e,
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify is the email was sent successfully
|
||||||
|
if(emailResult) {
|
||||||
|
res.json({
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
message: 'OK',
|
||||||
|
voucher: {
|
||||||
|
id: voucherData.id,
|
||||||
|
code: voucherCode
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
status: 'SENT',
|
||||||
|
address: req.body.email.address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.json({
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
message: 'OK',
|
||||||
|
voucher: {
|
||||||
|
id: voucherData.id,
|
||||||
|
code: voucherCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
85
controllers/authentication.js
Normal file
85
controllers/authentication.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Import own modules
|
||||||
|
*/
|
||||||
|
const variables = require('../modules/variables');
|
||||||
|
const jwt = require('../modules/jwt');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
login: {
|
||||||
|
/**
|
||||||
|
* GET - /login
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: (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';
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST - /login
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
post: 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordCheck = req.body.password === variables.authInternalPassword;
|
||||||
|
|
||||||
|
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`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
logout: {
|
||||||
|
/**
|
||||||
|
* GET - /logout
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.oidc) {
|
||||||
|
res.redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/oidc/logout`);
|
||||||
|
} else {
|
||||||
|
res.cookie('authorization', '', {httpOnly: true, expires: new Date(0)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
111
controllers/bulk.js
Normal file
111
controllers/bulk.js
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* Import own modules
|
||||||
|
*/
|
||||||
|
const variables = require('../modules/variables');
|
||||||
|
const cache = require('../modules/cache');
|
||||||
|
const print = require('../modules/print');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import own utils
|
||||||
|
*/
|
||||||
|
const notes = require('../utils/notes');
|
||||||
|
const time = require('../utils/time');
|
||||||
|
const bytes = require('../utils/bytes');
|
||||||
|
const languages = require('../utils/languages');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
print: {
|
||||||
|
/**
|
||||||
|
* GET - /bulk/print
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: (req, res) => {
|
||||||
|
if(variables.printers === '') {
|
||||||
|
res.status(501).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render('components/bulk-print', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||||
|
timeConvert: time,
|
||||||
|
bytesConvert: bytes,
|
||||||
|
notesConvert: notes,
|
||||||
|
languages,
|
||||||
|
defaultLanguage: variables.translationDefault,
|
||||||
|
printers: variables.printers.split(','),
|
||||||
|
vouchers: cache.vouchers,
|
||||||
|
updated: cache.updated
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST - /bulk/print
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
post: async (req, res) => {
|
||||||
|
if(variables.printers === '') {
|
||||||
|
res.status(501).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!variables.printers.includes(req.body.printer)) {
|
||||||
|
res.status(400).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!req.body.vouchers) {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'No selected vouchers to print!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single checkboxes get send as string so conversion is needed
|
||||||
|
if(typeof req.body.vouchers === 'string') {
|
||||||
|
req.body.vouchers = [req.body.vouchers];
|
||||||
|
}
|
||||||
|
|
||||||
|
const vouchers = req.body.vouchers.map((voucher) => {
|
||||||
|
return cache.vouchers.find((e) => {
|
||||||
|
return e.id === voucher;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!vouchers.includes(undefined)) {
|
||||||
|
if(req.body.printer === 'pdf') {
|
||||||
|
const buffers = await print.pdf(vouchers, req.body.language, true);
|
||||||
|
const pdfData = Buffer.concat(buffers);
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Length': Buffer.byteLength(pdfData),
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
'Content-Disposition': `attachment;filename=bulk_vouchers_${new Date().getTime()}.pdf`
|
||||||
|
}).end(pdfData);
|
||||||
|
} else {
|
||||||
|
let printSuccess = true;
|
||||||
|
|
||||||
|
for(let voucher = 0; voucher < vouchers.length; voucher++) {
|
||||||
|
const printResult = await print.escpos(vouchers[voucher], req.body.language, req.body.printer).catch((e) => {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!printResult) {
|
||||||
|
printSuccess = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(printSuccess) {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'info', message: `Vouchers send to printer!`}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(404);
|
||||||
|
res.render('404', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
36
controllers/error.js
Normal file
36
controllers/error.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Import own modules
|
||||||
|
*/
|
||||||
|
const log = require('../modules/log');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* Handler for 404 status codes
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
404: (req, res) => {
|
||||||
|
res.status(404);
|
||||||
|
res.render('404', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for 500 status codes
|
||||||
|
*
|
||||||
|
* @param err
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param next
|
||||||
|
*/
|
||||||
|
500: (err, req, res, next) => {
|
||||||
|
log.error(err.stack);
|
||||||
|
res.status(500);
|
||||||
|
res.render('500', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||||
|
error: err.stack
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
168
controllers/kiosk.js
Normal file
168
controllers/kiosk.js
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
/**
|
||||||
|
* Import own modules
|
||||||
|
*/
|
||||||
|
const variables = require('../modules/variables');
|
||||||
|
const log = require('../modules/log');
|
||||||
|
const cache = require('../modules/cache');
|
||||||
|
const unifi = require('../modules/unifi');
|
||||||
|
const print = require('../modules/print');
|
||||||
|
const mail = require('../modules/mail');
|
||||||
|
const qr = require('../modules/qr');
|
||||||
|
const translation = require('../modules/translation');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import own utils
|
||||||
|
*/
|
||||||
|
const types = require('../utils/types');
|
||||||
|
const time = require('../utils/time');
|
||||||
|
const bytes = require('../utils/bytes');
|
||||||
|
const languages = require('../utils/languages');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* GET - /kiosk
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: (req, res) => {
|
||||||
|
// Check if kiosk is disabled
|
||||||
|
if(!variables.kioskEnabled) {
|
||||||
|
res.status(501).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render('kiosk', {
|
||||||
|
t: translation('kiosk', req.locale.language),
|
||||||
|
languages,
|
||||||
|
language: req.locale.language,
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||||
|
error: req.flashMessage.type === 'error',
|
||||||
|
error_text: req.flashMessage.message || '',
|
||||||
|
timeConvert: time,
|
||||||
|
bytesConvert: bytes,
|
||||||
|
voucher_types: types(variables.kioskVoucherTypes),
|
||||||
|
kiosk_name_required: variables.kioskNameRequired,
|
||||||
|
kiosk_homepage: variables.kioskHomepage
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST - /kiosk
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
post: async (req, res) => {
|
||||||
|
// Check if kiosk is disabled
|
||||||
|
if(!variables.kioskEnabled) {
|
||||||
|
res.status(501).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we need to generate a voucher or send an email with an existing voucher
|
||||||
|
if(req.body && req.body.id && req.body.code && req.body.email) {
|
||||||
|
// Check if email functions are enabled
|
||||||
|
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
|
||||||
|
res.status(501).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get voucher from cache
|
||||||
|
const voucher = cache.vouchers.find((e) => {
|
||||||
|
return e.id === req.body.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(voucher) {
|
||||||
|
const emailResult = await mail.send(req.body.email, voucher, req.locale.language).catch((e) => {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(emailResult) {
|
||||||
|
res.render('kiosk', {
|
||||||
|
t: translation('kiosk', req.locale.language),
|
||||||
|
languages,
|
||||||
|
language: req.locale.language,
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||||
|
error: req.flashMessage.type === 'error',
|
||||||
|
error_text: req.flashMessage.message || '',
|
||||||
|
email_enabled: variables.smtpFrom !== '' && variables.smtpHost !== '' && variables.smtpPort !== '',
|
||||||
|
unifiSsid: variables.unifiSsid,
|
||||||
|
unifiSsidPassword: variables.unifiSsidPassword,
|
||||||
|
qr: await qr(),
|
||||||
|
voucherId: req.body.id,
|
||||||
|
voucherCode: req.body.code,
|
||||||
|
email: req.body.email
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(404);
|
||||||
|
res.render('404', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const typeCheck = (variables.kioskVoucherTypes).split(';').includes(req.body['voucher-type']);
|
||||||
|
|
||||||
|
if (!typeCheck) {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Unknown Type!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(variables.kioskNameRequired && req.body['voucher-note'] !== '' && req.body['voucher-note'].includes('||;;||')) {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Invalid Notes!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const voucherNote = `${variables.kioskNameRequired ? req.body['voucher-note'] : ''}||;;||kiosk||;;||local||;;||`;
|
||||||
|
|
||||||
|
// Create voucher code
|
||||||
|
const voucherCode = await unifi.create(types(req.body['voucher-type'], true), 1, voucherNote).catch((e) => {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (voucherCode) {
|
||||||
|
log.info('[Cache] Requesting UniFi Vouchers...');
|
||||||
|
|
||||||
|
const vouchers = await unifi.list().catch((e) => {
|
||||||
|
log.error('[Cache] Error requesting vouchers!');
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (vouchers) {
|
||||||
|
cache.vouchers = vouchers;
|
||||||
|
cache.updated = new Date().getTime();
|
||||||
|
log.info(`[Cache] Saved ${vouchers.length} voucher(s)`);
|
||||||
|
|
||||||
|
// Locate voucher data within cache
|
||||||
|
const voucherData = cache.vouchers.find(voucher => voucher.code === voucherCode.replaceAll('-', ''));
|
||||||
|
if(!voucherData) {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Invalid application cache!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto print voucher if enabled
|
||||||
|
await print.escpos(voucherData, req.locale.language, variables.kioskPrinter).catch((e) => {
|
||||||
|
log.error(`[Kiosk] Unable to auto-print voucher on printer: ${variables.kioskPrinter}!`);
|
||||||
|
log.error(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
res.render('kiosk', {
|
||||||
|
t: translation('kiosk', req.locale.language),
|
||||||
|
languages,
|
||||||
|
language: req.locale.language,
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||||
|
error: req.flashMessage.type === 'error',
|
||||||
|
error_text: req.flashMessage.message || '',
|
||||||
|
email_enabled: variables.smtpFrom !== '' && variables.smtpHost !== '' && variables.smtpPort !== '',
|
||||||
|
unifiSsid: variables.unifiSsid,
|
||||||
|
unifiSsidPassword: variables.unifiSsidPassword,
|
||||||
|
qr: await qr(),
|
||||||
|
voucherId: voucherData.id,
|
||||||
|
voucherCode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
37
controllers/status.js
Normal file
37
controllers/status.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Import base packages
|
||||||
|
*/
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import own modules
|
||||||
|
*/
|
||||||
|
const variables = require('../modules/variables');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import own utils
|
||||||
|
*/
|
||||||
|
const status = require('../utils/status');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* GET - /status
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: async (req, res) => {
|
||||||
|
const user = req.oidc ? await req.oidc.fetchUserInfo() : { email: 'admin' };
|
||||||
|
|
||||||
|
res.render('status', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||||
|
gitTag: variables.gitTag,
|
||||||
|
gitBuild: variables.gitBuild,
|
||||||
|
kioskEnabled: variables.kioskEnabled,
|
||||||
|
user: user,
|
||||||
|
userIcon: req.oidc ? crypto.createHash('sha256').update(user.email).digest('hex') : '',
|
||||||
|
authDisabled: variables.authDisabled,
|
||||||
|
status: status()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
291
controllers/voucher.js
Normal file
291
controllers/voucher.js
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
/**
|
||||||
|
* Import own modules
|
||||||
|
*/
|
||||||
|
const variables = require('../modules/variables');
|
||||||
|
const log = require('../modules/log');
|
||||||
|
const cache = require('../modules/cache');
|
||||||
|
const unifi = require('../modules/unifi');
|
||||||
|
const print = require('../modules/print');
|
||||||
|
const mail = require('../modules/mail');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import own utils
|
||||||
|
*/
|
||||||
|
const types = require('../utils/types');
|
||||||
|
const notes = require('../utils/notes');
|
||||||
|
const time = require('../utils/time');
|
||||||
|
const bytes = require('../utils/bytes');
|
||||||
|
const languages = require('../utils/languages');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
voucher: {
|
||||||
|
/**
|
||||||
|
* GET - /voucher/:id
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: (req, res) => {
|
||||||
|
const voucher = cache.vouchers.find((e) => {
|
||||||
|
return e.id === req.params.id;
|
||||||
|
});
|
||||||
|
const guests = cache.guests.filter((e) => {
|
||||||
|
return e.voucher_code === voucher.code;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(voucher) {
|
||||||
|
res.render('components/details', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||||
|
timeConvert: time,
|
||||||
|
bytesConvert: bytes,
|
||||||
|
notesConvert: notes,
|
||||||
|
voucher,
|
||||||
|
guests,
|
||||||
|
updated: cache.updated
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(404);
|
||||||
|
res.render('404', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST - /voucher
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
post: async (req, res) => {
|
||||||
|
if (typeof req.body === "undefined") {
|
||||||
|
res.status(400).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.body['voucher-type'] !== 'custom') {
|
||||||
|
const typeCheck = (variables.voucherTypes).split(';').includes(req.body['voucher-type']);
|
||||||
|
|
||||||
|
if (!typeCheck) {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Unknown Type!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.body['voucher-note'] !== '' && req.body['voucher-note'].includes('||;;||')) {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Invalid Notes!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = req.oidc ? await req.oidc.fetchUserInfo() : { email: null };
|
||||||
|
const voucherNote = `${req.body['voucher-note'] !== '' ? req.body['voucher-note'] : ''}||;;||web||;;||${req.oidc ? 'oidc' : 'local'}||;;||${req.oidc ? user.email.split('@')[1].toLowerCase() : ''}`;
|
||||||
|
|
||||||
|
// Create voucher code
|
||||||
|
const voucherCode = await unifi.create(types(req.body['voucher-type'] === 'custom' ? `${req.body['voucher-duration-type'] === 'day' ? (parseInt(req.body['voucher-duration']) * 24 * 60) : req.body['voucher-duration-type'] === 'hour' ? (parseInt(req.body['voucher-duration']) * 60) : parseInt(req.body['voucher-duration'])},${req.body['voucher-usage'] === '-1' ? req.body['voucher-quota'] : req.body['voucher-usage']},${req.body['voucher-upload-limit']},${req.body['voucher-download-limit']},${req.body['voucher-data-limit']};` : req.body['voucher-type'], true), parseInt(req.body['voucher-amount']), voucherNote).catch((e) => {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(voucherCode) {
|
||||||
|
log.info('[Cache] Requesting UniFi Vouchers...');
|
||||||
|
|
||||||
|
const vouchers = await unifi.list().catch((e) => {
|
||||||
|
log.error('[Cache] Error requesting vouchers!');
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(vouchers) {
|
||||||
|
cache.vouchers = vouchers;
|
||||||
|
cache.updated = new Date().getTime();
|
||||||
|
log.info(`[Cache] Saved ${vouchers.length} voucher(s)`);
|
||||||
|
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'info', message: parseInt(req.body['voucher-amount']) > 1 ? `${req.body['voucher-amount']} Vouchers Created!` : `Voucher Created: ${voucherCode}`}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
remove: {
|
||||||
|
/**
|
||||||
|
* GET - /voucher/:id/remove
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: async (req, res) => {
|
||||||
|
// Revoke voucher code
|
||||||
|
const response = await unifi.remove(req.params.id).catch((e) => {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(response) {
|
||||||
|
log.info('[Cache] Requesting UniFi Vouchers...');
|
||||||
|
|
||||||
|
const vouchers = await unifi.list().catch((e) => {
|
||||||
|
log.error('[Cache] Error requesting vouchers!');
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(vouchers) {
|
||||||
|
cache.vouchers = vouchers;
|
||||||
|
cache.updated = new Date().getTime();
|
||||||
|
log.info(`[Cache] Saved ${vouchers.length} voucher(s)`);
|
||||||
|
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'info', message: `Voucher Removed!`}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
print: {
|
||||||
|
/**
|
||||||
|
* GET - /voucher/:id/print
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: (req, res) => {
|
||||||
|
if(variables.printers === '') {
|
||||||
|
res.status(501).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const voucher = cache.vouchers.find((e) => {
|
||||||
|
return e.id === req.params.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(voucher) {
|
||||||
|
res.render('components/print', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||||
|
languages,
|
||||||
|
defaultLanguage: variables.translationDefault,
|
||||||
|
printers: variables.printers.split(','),
|
||||||
|
voucher,
|
||||||
|
updated: cache.updated
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(404);
|
||||||
|
res.render('404', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST - /voucher/:id/print
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
post: async (req, res) => {
|
||||||
|
if(variables.printers === '') {
|
||||||
|
res.status(501).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!variables.printers.includes(req.body.printer)) {
|
||||||
|
res.status(400).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const voucher = cache.vouchers.find((e) => {
|
||||||
|
return e.id === req.params.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(voucher) {
|
||||||
|
if(req.body.printer === 'pdf') {
|
||||||
|
const buffers = await print.pdf(voucher, req.body.language);
|
||||||
|
const pdfData = Buffer.concat(buffers);
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Length': Buffer.byteLength(pdfData),
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
'Content-Disposition': `attachment;filename=voucher_${req.params.id}.pdf`
|
||||||
|
}).end(pdfData);
|
||||||
|
} else {
|
||||||
|
const printResult = await print.escpos(voucher, req.body.language, req.body.printer).catch((e) => {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(printResult) {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'info', message: `Voucher send to printer!`}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(404);
|
||||||
|
res.render('404', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
email: {
|
||||||
|
/**
|
||||||
|
* GET - /voucher/:id/email
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: (req, res) => {
|
||||||
|
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
|
||||||
|
res.status(501).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const voucher = cache.vouchers.find((e) => {
|
||||||
|
return e.id === req.params.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(voucher) {
|
||||||
|
res.render('components/email', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||||
|
languages,
|
||||||
|
defaultLanguage: variables.translationDefault,
|
||||||
|
voucher,
|
||||||
|
updated: cache.updated
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(404);
|
||||||
|
res.render('404', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST - /voucher/:id/email
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
post: async (req, res) => {
|
||||||
|
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
|
||||||
|
res.status(501).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof req.body === "undefined") {
|
||||||
|
res.status(400).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const voucher = cache.vouchers.find((e) => {
|
||||||
|
return e.id === req.params.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(voucher) {
|
||||||
|
const emailResult = await mail.send(req.body.email, voucher, req.body.language).catch((e) => {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(emailResult) {
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'info', message: 'Email has been sent!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(404);
|
||||||
|
res.render('404', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
142
controllers/vouchers.js
Normal file
142
controllers/vouchers.js
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* Import base packages
|
||||||
|
*/
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import own modules
|
||||||
|
*/
|
||||||
|
const variables = require('../modules/variables');
|
||||||
|
const log = require('../modules/log');
|
||||||
|
const cache = require('../modules/cache');
|
||||||
|
const unifi = require('../modules/unifi');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import own utils
|
||||||
|
*/
|
||||||
|
const types = require('../utils/types');
|
||||||
|
const notes = require('../utils/notes');
|
||||||
|
const time = require('../utils/time');
|
||||||
|
const bytes = require('../utils/bytes');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* GET - /vouchers
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
get: async (req, res) => {
|
||||||
|
if(req.query.refresh) {
|
||||||
|
log.info('[Cache] Requesting UniFi Vouchers...');
|
||||||
|
|
||||||
|
const vouchers = await unifi.list().catch((e) => {
|
||||||
|
log.error('[Cache] Error requesting vouchers!');
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!vouchers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info('[Cache] Requesting UniFi Guests...');
|
||||||
|
|
||||||
|
const guests = await unifi.guests().catch((e) => {
|
||||||
|
log.error('[Cache] Error requesting guests!');
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(vouchers && guests) {
|
||||||
|
cache.vouchers = vouchers;
|
||||||
|
cache.guests = guests;
|
||||||
|
cache.updated = new Date().getTime();
|
||||||
|
log.info(`[Cache] Saved ${vouchers.length} voucher(s)`);
|
||||||
|
log.info(`[Cache] Saved ${guests.length} guest(s)`);
|
||||||
|
|
||||||
|
res.cookie('flashMessage', JSON.stringify({type: 'info', message: 'Synced Vouchers & Guests!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = req.oidc ? await req.oidc.fetchUserInfo() : { email: 'admin' };
|
||||||
|
|
||||||
|
res.render('voucher', {
|
||||||
|
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
||||||
|
gitTag: variables.gitTag,
|
||||||
|
gitBuild: variables.gitBuild,
|
||||||
|
user: user,
|
||||||
|
userIcon: req.oidc ? crypto.createHash('sha256').update(user.email).digest('hex') : '',
|
||||||
|
authDisabled: variables.authDisabled,
|
||||||
|
info: req.flashMessage.type === 'info',
|
||||||
|
info_text: req.flashMessage.message || '',
|
||||||
|
error: req.flashMessage.type === 'error',
|
||||||
|
error_text: req.flashMessage.message || '',
|
||||||
|
kioskEnabled: variables.kioskEnabled,
|
||||||
|
timeConvert: time,
|
||||||
|
bytesConvert: bytes,
|
||||||
|
notesConvert: notes,
|
||||||
|
email_enabled: variables.smtpFrom !== '' && variables.smtpHost !== '' && variables.smtpPort !== '',
|
||||||
|
printer_enabled: variables.printers !== '',
|
||||||
|
voucher_types: types(variables.voucherTypes),
|
||||||
|
voucher_custom: variables.voucherCustom,
|
||||||
|
vouchers: cache.vouchers.filter((item) => {
|
||||||
|
if(variables.authOidcRestrictVisibility && req.oidc) {
|
||||||
|
return item.name && notes(item.name).auth_oidc_domain === user.email.split('@')[1].toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}).filter((item) => {
|
||||||
|
if(req.query.status === 'available') {
|
||||||
|
return item.authorizedGuestCount === 0 && !item.expired;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.query.status === 'in-use') {
|
||||||
|
return item.authorizedGuestCount > 0 && !item.expired;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.query.status === 'expired') {
|
||||||
|
return item.expired;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}).filter((item) => {
|
||||||
|
if(req.query.quota === 'multi-use') {
|
||||||
|
return (item.authorizedGuestLimit && item.authorizedGuestLimit > 1) || !item.authorizedGuestLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.query.quota === 'single-use') {
|
||||||
|
return item.authorizedGuestLimit && item.authorizedGuestLimit === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}).sort((a, b) => {
|
||||||
|
if(req.query.sort === 'code') {
|
||||||
|
if (a.code > b.code) return -1;
|
||||||
|
if (a.code < b.code) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.query.sort === 'note') {
|
||||||
|
if ((notes(a.name).note || '') > (notes(b.name).note || '')) return -1;
|
||||||
|
if ((notes(a.name).note || '') < (notes(b.name).note || '')) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.query.sort === 'duration') {
|
||||||
|
if (a.timeLimitMinutes > b.timeLimitMinutes) return -1;
|
||||||
|
if (a.timeLimitMinutes < b.timeLimitMinutes) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.query.sort === 'status') {
|
||||||
|
if (a.authorizedGuestCount > b.authorizedGuestCount) return -1;
|
||||||
|
if (a.authorizedGuestCount < b.authorizedGuestCount) return 1;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
updated: cache.updated,
|
||||||
|
filters: {
|
||||||
|
status: req.query.status,
|
||||||
|
quota: req.query.quota
|
||||||
|
},
|
||||||
|
sort: req.query.sort
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@ services:
|
|||||||
UNIFI_PORT: 443
|
UNIFI_PORT: 443
|
||||||
UNIFI_USERNAME: 'admin'
|
UNIFI_USERNAME: 'admin'
|
||||||
UNIFI_PASSWORD: 'password'
|
UNIFI_PASSWORD: 'password'
|
||||||
|
UNIFI_TOKEN: ''
|
||||||
UNIFI_SITE_ID: 'default'
|
UNIFI_SITE_ID: 'default'
|
||||||
UNIFI_SSID: ''
|
UNIFI_SSID: ''
|
||||||
UNIFI_SSID_PASSWORD: ''
|
UNIFI_SSID_PASSWORD: ''
|
||||||
@@ -36,5 +37,6 @@ services:
|
|||||||
KIOSK_VOUCHER_TYPES: '480,1,,,;'
|
KIOSK_VOUCHER_TYPES: '480,1,,,;'
|
||||||
KIOSK_NAME_REQUIRED: 'false'
|
KIOSK_NAME_REQUIRED: 'false'
|
||||||
KIOSK_PRINTER: ''
|
KIOSK_PRINTER: ''
|
||||||
|
KIOSK_HOMEPAGE: 'false'
|
||||||
LOG_LEVEL: 'info'
|
LOG_LEVEL: 'info'
|
||||||
TRANSLATION_DEBUG: 'false'
|
TRANSLATION_DEBUG: 'false'
|
||||||
|
|||||||
@@ -130,4 +130,12 @@ module.exports = () => {
|
|||||||
if(variables.unifiUsername.includes('@')) {
|
if(variables.unifiUsername.includes('@')) {
|
||||||
log.error('[UniFi] Incorrect username detected! UniFi Cloud credentials are not supported!');
|
log.error('[UniFi] Incorrect username detected! UniFi Cloud credentials are not supported!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if UniFi Token is set
|
||||||
|
*/
|
||||||
|
if(variables.unifiToken === '') {
|
||||||
|
log.error('[UniFi] Integration API Key is not set within UNIFI_TOKEN environment variable!');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ module.exports = {
|
|||||||
kioskVoucherTypes: config('kiosk_voucher_types') || process.env.KIOSK_VOUCHER_TYPES || '480,1,,,;',
|
kioskVoucherTypes: config('kiosk_voucher_types') || process.env.KIOSK_VOUCHER_TYPES || '480,1,,,;',
|
||||||
kioskNameRequired: config('kiosk_name_required') || (process.env.KIOSK_NAME_REQUIRED === 'true') || false,
|
kioskNameRequired: config('kiosk_name_required') || (process.env.KIOSK_NAME_REQUIRED === 'true') || false,
|
||||||
kioskPrinter: config('kiosk_printer') || process.env.KIOSK_PRINTER || '',
|
kioskPrinter: config('kiosk_printer') || process.env.KIOSK_PRINTER || '',
|
||||||
|
kioskHomepage: config('kiosk_homepage') || (process.env.KIOSK_HOMEPAGE === 'true') || false,
|
||||||
logLevel: config('log_level') || process.env.LOG_LEVEL || 'info',
|
logLevel: config('log_level') || process.env.LOG_LEVEL || 'info',
|
||||||
translationDefault: config('translation_default') || process.env.TRANSLATION_DEFAULT || 'en',
|
translationDefault: config('translation_default') || process.env.TRANSLATION_DEFAULT || 'en',
|
||||||
translationDebug: config('translation_debug') || (process.env.TRANSLATION_DEBUG === 'true') || false,
|
translationDebug: config('translation_debug') || (process.env.TRANSLATION_DEBUG === 'true') || false,
|
||||||
|
|||||||
858
server.js
858
server.js
@@ -3,7 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const crypto = require('crypto');
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
@@ -14,15 +13,9 @@ const locale = require('express-locale');
|
|||||||
*/
|
*/
|
||||||
const variables = require('./modules/variables');
|
const variables = require('./modules/variables');
|
||||||
const log = require('./modules/log');
|
const log = require('./modules/log');
|
||||||
const cache = require('./modules/cache');
|
|
||||||
const jwt = require('./modules/jwt');
|
const jwt = require('./modules/jwt');
|
||||||
const info = require('./modules/info');
|
const info = require('./modules/info');
|
||||||
const unifi = require('./modules/unifi');
|
|
||||||
const print = require('./modules/print');
|
|
||||||
const mail = require('./modules/mail');
|
|
||||||
const oidc = require('./modules/oidc');
|
const oidc = require('./modules/oidc');
|
||||||
const qr = require('./modules/qr');
|
|
||||||
const translation = require('./modules/translation');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import own middlewares
|
* Import own middlewares
|
||||||
@@ -30,16 +23,22 @@ const translation = require('./modules/translation');
|
|||||||
const authorization = require('./middlewares/authorization');
|
const authorization = require('./middlewares/authorization');
|
||||||
const flashMessage = require('./middlewares/flashMessage');
|
const flashMessage = require('./middlewares/flashMessage');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import own controllers
|
||||||
|
*/
|
||||||
|
const api = require('./controllers/api');
|
||||||
|
const authentication = require('./controllers/authentication');
|
||||||
|
const bulk = require('./controllers/bulk');
|
||||||
|
const error = require('./controllers/error');
|
||||||
|
const kiosk = require('./controllers/kiosk');
|
||||||
|
const status = require('./controllers/status');
|
||||||
|
const voucher = require('./controllers/voucher');
|
||||||
|
const vouchers = require('./controllers/vouchers');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import own utils
|
* Import own utils
|
||||||
*/
|
*/
|
||||||
const {updateCache} = require('./utils/cache');
|
const {updateCache} = require('./utils/cache');
|
||||||
const types = require('./utils/types');
|
|
||||||
const notes = require('./utils/notes');
|
|
||||||
const time = require('./utils/time');
|
|
||||||
const bytes = require('./utils/bytes');
|
|
||||||
const status = require('./utils/status');
|
|
||||||
const languages = require('./utils/languages');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup Express app
|
* Setup Express app
|
||||||
@@ -138,835 +137,64 @@ app.use(locale({
|
|||||||
app.use(flashMessage);
|
app.use(flashMessage);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure routers
|
* Setup Base Routes
|
||||||
*/
|
*/
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
if(variables.serviceWeb) {
|
if(variables.serviceWeb) {
|
||||||
res.redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
res.redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/${variables.kioskEnabled && variables.kioskHomepage ? 'kiosk' : 'vouchers'}`);
|
||||||
} else {
|
} else {
|
||||||
res.status(501).send();
|
res.status(501).send();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if web service is enabled
|
/**
|
||||||
|
* Setup Web Routes
|
||||||
|
*/
|
||||||
if(variables.serviceWeb) {
|
if(variables.serviceWeb) {
|
||||||
app.get('/kiosk', (req, res) => {
|
app.get('/kiosk', kiosk.get);
|
||||||
// Check if kiosk is disabled
|
app.post('/kiosk', kiosk.post);
|
||||||
if(!variables.kioskEnabled) {
|
|
||||||
res.status(501).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.render('kiosk', {
|
app.get('/login', authentication.login.get);
|
||||||
t: translation('kiosk', req.locale.language),
|
app.post('/login', authentication.login.post);
|
||||||
languages,
|
app.get('/logout', [authorization.web], authentication.logout.get);
|
||||||
language: req.locale.language,
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
|
||||||
error: req.flashMessage.type === 'error',
|
|
||||||
error_text: req.flashMessage.message || '',
|
|
||||||
timeConvert: time,
|
|
||||||
bytesConvert: bytes,
|
|
||||||
voucher_types: types(variables.kioskVoucherTypes),
|
|
||||||
kiosk_name_required: variables.kioskNameRequired
|
|
||||||
});
|
|
||||||
});
|
|
||||||
app.post('/kiosk', async (req, res) => {
|
|
||||||
// Check if kiosk is disabled
|
|
||||||
if(!variables.kioskEnabled) {
|
|
||||||
res.status(501).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we need to generate a voucher or send an email with an existing voucher
|
app.post('/voucher', [authorization.web], voucher.voucher.post);
|
||||||
if(req.body && req.body.id && req.body.code && req.body.email) {
|
app.get('/voucher/:id/remove', [authorization.web], voucher.remove.get);
|
||||||
// Check if email functions are enabled
|
app.get('/voucher/:id/print', [authorization.web], voucher.print.get);
|
||||||
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
|
app.post('/voucher/:id/print', [authorization.web], voucher.print.post);
|
||||||
res.status(501).send();
|
app.get('/voucher/:id/email', [authorization.web], voucher.email.get);
|
||||||
return;
|
app.post('/voucher/:id/email', [authorization.web], voucher.email.post);
|
||||||
}
|
|
||||||
|
|
||||||
// Get voucher from cache
|
app.get('/vouchers', [authorization.web], vouchers.get);
|
||||||
const voucher = cache.vouchers.find((e) => {
|
app.get('/voucher/:id', [authorization.web], voucher.voucher.get);
|
||||||
return e.id === req.body.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(voucher) {
|
app.get('/status', [authorization.web], status.get);
|
||||||
const emailResult = await mail.send(req.body.email, voucher, req.locale.language).catch((e) => {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(emailResult) {
|
app.get('/bulk/print', [authorization.web], bulk.print.get);
|
||||||
res.render('kiosk', {
|
app.post('/bulk/print', [authorization.web], bulk.print.post);
|
||||||
t: translation('kiosk', req.locale.language),
|
|
||||||
languages,
|
|
||||||
language: req.locale.language,
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
|
||||||
error: req.flashMessage.type === 'error',
|
|
||||||
error_text: req.flashMessage.message || '',
|
|
||||||
email_enabled: variables.smtpFrom !== '' && variables.smtpHost !== '' && variables.smtpPort !== '',
|
|
||||||
unifiSsid: variables.unifiSsid,
|
|
||||||
unifiSsidPassword: variables.unifiSsidPassword,
|
|
||||||
qr: await qr(),
|
|
||||||
voucherId: req.body.id,
|
|
||||||
voucherCode: req.body.code,
|
|
||||||
email: req.body.email
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(404);
|
|
||||||
res.render('404', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const typeCheck = (variables.kioskVoucherTypes).split(';').includes(req.body['voucher-type']);
|
|
||||||
|
|
||||||
if (!typeCheck) {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Unknown Type!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(variables.kioskNameRequired && req.body['voucher-note'] !== '' && req.body['voucher-note'].includes('||;;||')) {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Invalid Notes!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const voucherNote = `${variables.kioskNameRequired ? req.body['voucher-note'] : ''}||;;||kiosk||;;||local||;;||`;
|
|
||||||
|
|
||||||
// Create voucher code
|
|
||||||
const voucherCode = await unifi.create(types(req.body['voucher-type'], true), 1, voucherNote).catch((e) => {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (voucherCode) {
|
|
||||||
log.info('[Cache] Requesting UniFi Vouchers...');
|
|
||||||
|
|
||||||
const vouchers = await unifi.list().catch((e) => {
|
|
||||||
log.error('[Cache] Error requesting vouchers!');
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (vouchers) {
|
|
||||||
cache.vouchers = vouchers;
|
|
||||||
cache.updated = new Date().getTime();
|
|
||||||
log.info(`[Cache] Saved ${vouchers.length} voucher(s)`);
|
|
||||||
|
|
||||||
// Locate voucher data within cache
|
|
||||||
const voucherData = cache.vouchers.find(voucher => voucher.code === voucherCode.replaceAll('-', ''));
|
|
||||||
if(!voucherData) {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Invalid application cache!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/kiosk`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto print voucher if enabled
|
|
||||||
await print.escpos(voucherData, req.locale.language, variables.kioskPrinter).catch((e) => {
|
|
||||||
log.error(`[Kiosk] Unable to auto-print voucher on printer: ${variables.kioskPrinter}!`);
|
|
||||||
log.error(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
res.render('kiosk', {
|
|
||||||
t: translation('kiosk', req.locale.language),
|
|
||||||
languages,
|
|
||||||
language: req.locale.language,
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
|
||||||
error: req.flashMessage.type === 'error',
|
|
||||||
error_text: req.flashMessage.message || '',
|
|
||||||
email_enabled: variables.smtpFrom !== '' && variables.smtpHost !== '' && variables.smtpPort !== '',
|
|
||||||
unifiSsid: variables.unifiSsid,
|
|
||||||
unifiSsidPassword: variables.unifiSsidPassword,
|
|
||||||
qr: await qr(),
|
|
||||||
voucherId: voucherData.id,
|
|
||||||
voucherCode
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
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';
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
// Check if internal authentication is enabled
|
|
||||||
if(!variables.authInternalEnabled) {
|
|
||||||
res.status(501).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof req.body === "undefined") {
|
|
||||||
res.status(400).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const passwordCheck = req.body.password === variables.authInternalPassword;
|
|
||||||
|
|
||||||
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`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(req.oidc) {
|
|
||||||
res.redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/oidc/logout`);
|
|
||||||
} else {
|
|
||||||
res.cookie('authorization', '', {httpOnly: true, expires: new Date(0)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/voucher', [authorization.web], async (req, res) => {
|
|
||||||
if (typeof req.body === "undefined") {
|
|
||||||
res.status(400).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(req.body['voucher-type'] !== 'custom') {
|
|
||||||
const typeCheck = (variables.voucherTypes).split(';').includes(req.body['voucher-type']);
|
|
||||||
|
|
||||||
if (!typeCheck) {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Unknown Type!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(req.body['voucher-note'] !== '' && req.body['voucher-note'].includes('||;;||')) {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Invalid Notes!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = req.oidc ? await req.oidc.fetchUserInfo() : { email: null };
|
|
||||||
const voucherNote = `${req.body['voucher-note'] !== '' ? req.body['voucher-note'] : ''}||;;||web||;;||${req.oidc ? 'oidc' : 'local'}||;;||${req.oidc ? user.email.split('@')[1].toLowerCase() : ''}`;
|
|
||||||
|
|
||||||
// Create voucher code
|
|
||||||
const voucherCode = await unifi.create(types(req.body['voucher-type'] === 'custom' ? `${req.body['voucher-duration-type'] === 'day' ? (parseInt(req.body['voucher-duration']) * 24 * 60) : req.body['voucher-duration-type'] === 'hour' ? (parseInt(req.body['voucher-duration']) * 60) : parseInt(req.body['voucher-duration'])},${req.body['voucher-usage'] === '-1' ? req.body['voucher-quota'] : req.body['voucher-usage']},${req.body['voucher-upload-limit']},${req.body['voucher-download-limit']},${req.body['voucher-data-limit']};` : req.body['voucher-type'], true), parseInt(req.body['voucher-amount']), voucherNote).catch((e) => {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(voucherCode) {
|
|
||||||
log.info('[Cache] Requesting UniFi Vouchers...');
|
|
||||||
|
|
||||||
const vouchers = await unifi.list().catch((e) => {
|
|
||||||
log.error('[Cache] Error requesting vouchers!');
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(vouchers) {
|
|
||||||
cache.vouchers = vouchers;
|
|
||||||
cache.updated = new Date().getTime();
|
|
||||||
log.info(`[Cache] Saved ${vouchers.length} voucher(s)`);
|
|
||||||
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: parseInt(req.body['voucher-amount']) > 1 ? `${req.body['voucher-amount']} Vouchers Created!` : `Voucher Created: ${voucherCode}`}), {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('/voucher/:id/remove', [authorization.web], async (req, res) => {
|
|
||||||
// Revoke voucher code
|
|
||||||
const response = await unifi.remove(req.params.id).catch((e) => {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(response) {
|
|
||||||
log.info('[Cache] Requesting UniFi Vouchers...');
|
|
||||||
|
|
||||||
const vouchers = await unifi.list().catch((e) => {
|
|
||||||
log.error('[Cache] Error requesting vouchers!');
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(vouchers) {
|
|
||||||
cache.vouchers = vouchers;
|
|
||||||
cache.updated = new Date().getTime();
|
|
||||||
log.info(`[Cache] Saved ${vouchers.length} voucher(s)`);
|
|
||||||
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: `Voucher Removed!`}), {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('/voucher/:id/print', [authorization.web], async (req, res) => {
|
|
||||||
if(variables.printers === '') {
|
|
||||||
res.status(501).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const voucher = cache.vouchers.find((e) => {
|
|
||||||
return e.id === req.params.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(voucher) {
|
|
||||||
res.render('components/print', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
|
||||||
languages,
|
|
||||||
defaultLanguage: variables.translationDefault,
|
|
||||||
printers: variables.printers.split(','),
|
|
||||||
voucher,
|
|
||||||
updated: cache.updated
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(404);
|
|
||||||
res.render('404', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/voucher/:id/print', [authorization.web], async (req, res) => {
|
|
||||||
if(variables.printers === '') {
|
|
||||||
res.status(501).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!variables.printers.includes(req.body.printer)) {
|
|
||||||
res.status(400).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const voucher = cache.vouchers.find((e) => {
|
|
||||||
return e.id === req.params.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(voucher) {
|
|
||||||
if(req.body.printer === 'pdf') {
|
|
||||||
const buffers = await print.pdf(voucher, req.body.language);
|
|
||||||
const pdfData = Buffer.concat(buffers);
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Length': Buffer.byteLength(pdfData),
|
|
||||||
'Content-Type': 'application/pdf',
|
|
||||||
'Content-Disposition': `attachment;filename=voucher_${req.params.id}.pdf`
|
|
||||||
}).end(pdfData);
|
|
||||||
} else {
|
|
||||||
const printResult = await print.escpos(voucher, req.body.language, req.body.printer).catch((e) => {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(printResult) {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: `Voucher send to printer!`}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(404);
|
|
||||||
res.render('404', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.get('/voucher/:id/email', [authorization.web], async (req, res) => {
|
|
||||||
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
|
|
||||||
res.status(501).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const voucher = cache.vouchers.find((e) => {
|
|
||||||
return e.id === req.params.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(voucher) {
|
|
||||||
res.render('components/email', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
|
||||||
languages,
|
|
||||||
defaultLanguage: variables.translationDefault,
|
|
||||||
voucher,
|
|
||||||
updated: cache.updated
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(404);
|
|
||||||
res.render('404', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/voucher/:id/email', [authorization.web], async (req, res) => {
|
|
||||||
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
|
|
||||||
res.status(501).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof req.body === "undefined") {
|
|
||||||
res.status(400).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const voucher = cache.vouchers.find((e) => {
|
|
||||||
return e.id === req.params.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(voucher) {
|
|
||||||
const emailResult = await mail.send(req.body.email, voucher, req.body.language).catch((e) => {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(emailResult) {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: 'Email has been sent!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(404);
|
|
||||||
res.render('404', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.get('/vouchers', [authorization.web], async (req, res) => {
|
|
||||||
if(req.query.refresh) {
|
|
||||||
log.info('[Cache] Requesting UniFi Vouchers...');
|
|
||||||
|
|
||||||
const vouchers = await unifi.list().catch((e) => {
|
|
||||||
log.error('[Cache] Error requesting vouchers!');
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!vouchers) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info('[Cache] Requesting UniFi Guests...');
|
|
||||||
|
|
||||||
const guests = await unifi.guests().catch((e) => {
|
|
||||||
log.error('[Cache] Error requesting guests!');
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(vouchers && guests) {
|
|
||||||
cache.vouchers = vouchers;
|
|
||||||
cache.guests = guests;
|
|
||||||
cache.updated = new Date().getTime();
|
|
||||||
log.info(`[Cache] Saved ${vouchers.length} voucher(s)`);
|
|
||||||
log.info(`[Cache] Saved ${guests.length} guest(s)`);
|
|
||||||
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: 'Synced Vouchers & Guests!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = req.oidc ? await req.oidc.fetchUserInfo() : { email: 'admin' };
|
|
||||||
|
|
||||||
res.render('voucher', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
|
||||||
gitTag: variables.gitTag,
|
|
||||||
gitBuild: variables.gitBuild,
|
|
||||||
user: user,
|
|
||||||
userIcon: req.oidc ? crypto.createHash('sha256').update(user.email).digest('hex') : '',
|
|
||||||
authDisabled: variables.authDisabled,
|
|
||||||
info: req.flashMessage.type === 'info',
|
|
||||||
info_text: req.flashMessage.message || '',
|
|
||||||
error: req.flashMessage.type === 'error',
|
|
||||||
error_text: req.flashMessage.message || '',
|
|
||||||
kioskEnabled: variables.kioskEnabled,
|
|
||||||
timeConvert: time,
|
|
||||||
bytesConvert: bytes,
|
|
||||||
notesConvert: notes,
|
|
||||||
email_enabled: variables.smtpFrom !== '' && variables.smtpHost !== '' && variables.smtpPort !== '',
|
|
||||||
printer_enabled: variables.printers !== '',
|
|
||||||
voucher_types: types(variables.voucherTypes),
|
|
||||||
voucher_custom: variables.voucherCustom,
|
|
||||||
vouchers: cache.vouchers.filter((item) => {
|
|
||||||
if(variables.authOidcRestrictVisibility && req.oidc) {
|
|
||||||
return item.name && notes(item.name).auth_oidc_domain === user.email.split('@')[1].toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}).filter((item) => {
|
|
||||||
if(req.query.status === 'available') {
|
|
||||||
return item.authorizedGuestCount === 0 && !item.expired;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(req.query.status === 'in-use') {
|
|
||||||
return item.authorizedGuestCount > 0 && !item.expired;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(req.query.status === 'expired') {
|
|
||||||
return item.expired;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}).filter((item) => {
|
|
||||||
if(req.query.quota === 'multi-use') {
|
|
||||||
return (item.authorizedGuestLimit && item.authorizedGuestLimit > 1) || !item.authorizedGuestLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(req.query.quota === 'single-use') {
|
|
||||||
return item.authorizedGuestLimit && item.authorizedGuestLimit === 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}).sort((a, b) => {
|
|
||||||
if(req.query.sort === 'code') {
|
|
||||||
if (a.code > b.code) return -1;
|
|
||||||
if (a.code < b.code) return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(req.query.sort === 'note') {
|
|
||||||
if ((notes(a.name).note || '') > (notes(b.name).note || '')) return -1;
|
|
||||||
if ((notes(a.name).note || '') < (notes(b.name).note || '')) return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(req.query.sort === 'duration') {
|
|
||||||
if (a.timeLimitMinutes > b.timeLimitMinutes) return -1;
|
|
||||||
if (a.timeLimitMinutes < b.timeLimitMinutes) return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(req.query.sort === 'status') {
|
|
||||||
if (a.authorizedGuestCount > b.authorizedGuestCount) return -1;
|
|
||||||
if (a.authorizedGuestCount < b.authorizedGuestCount) return 1;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
updated: cache.updated,
|
|
||||||
filters: {
|
|
||||||
status: req.query.status,
|
|
||||||
quota: req.query.quota
|
|
||||||
},
|
|
||||||
sort: req.query.sort
|
|
||||||
});
|
|
||||||
});
|
|
||||||
app.get('/voucher/:id', [authorization.web], async (req, res) => {
|
|
||||||
const voucher = cache.vouchers.find((e) => {
|
|
||||||
return e.id === req.params.id;
|
|
||||||
});
|
|
||||||
const guests = cache.guests.filter((e) => {
|
|
||||||
return e.voucher_code === voucher.code;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(voucher) {
|
|
||||||
res.render('components/details', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
|
||||||
timeConvert: time,
|
|
||||||
bytesConvert: bytes,
|
|
||||||
notesConvert: notes,
|
|
||||||
voucher,
|
|
||||||
guests,
|
|
||||||
updated: cache.updated
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(404);
|
|
||||||
res.render('404', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.get('/status', [authorization.web], async (req, res) => {
|
|
||||||
const user = req.oidc ? await req.oidc.fetchUserInfo() : { email: 'admin' };
|
|
||||||
|
|
||||||
res.render('status', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
|
||||||
gitTag: variables.gitTag,
|
|
||||||
gitBuild: variables.gitBuild,
|
|
||||||
kioskEnabled: variables.kioskEnabled,
|
|
||||||
user: user,
|
|
||||||
userIcon: req.oidc ? crypto.createHash('sha256').update(user.email).digest('hex') : '',
|
|
||||||
authDisabled: variables.authDisabled,
|
|
||||||
status: status()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
app.get('/bulk/print', [authorization.web], async (req, res) => {
|
|
||||||
if(variables.printers === '') {
|
|
||||||
res.status(501).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.render('components/bulk-print', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
|
||||||
timeConvert: time,
|
|
||||||
bytesConvert: bytes,
|
|
||||||
notesConvert: notes,
|
|
||||||
languages,
|
|
||||||
defaultLanguage: variables.translationDefault,
|
|
||||||
printers: variables.printers.split(','),
|
|
||||||
vouchers: cache.vouchers,
|
|
||||||
updated: cache.updated
|
|
||||||
});
|
|
||||||
});
|
|
||||||
app.post('/bulk/print', [authorization.web], async (req, res) => {
|
|
||||||
if(variables.printers === '') {
|
|
||||||
res.status(501).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!variables.printers.includes(req.body.printer)) {
|
|
||||||
res.status(400).send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!req.body.vouchers) {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'No selected vouchers to print!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Single checkboxes get send as string so conversion is needed
|
|
||||||
if(typeof req.body.vouchers === 'string') {
|
|
||||||
req.body.vouchers = [req.body.vouchers];
|
|
||||||
}
|
|
||||||
|
|
||||||
const vouchers = req.body.vouchers.map((voucher) => {
|
|
||||||
return cache.vouchers.find((e) => {
|
|
||||||
return e.id === voucher;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!vouchers.includes(undefined)) {
|
|
||||||
if(req.body.printer === 'pdf') {
|
|
||||||
const buffers = await print.pdf(vouchers, req.body.language, true);
|
|
||||||
const pdfData = Buffer.concat(buffers);
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Length': Buffer.byteLength(pdfData),
|
|
||||||
'Content-Type': 'application/pdf',
|
|
||||||
'Content-Disposition': `attachment;filename=bulk_vouchers_${new Date().getTime()}.pdf`
|
|
||||||
}).end(pdfData);
|
|
||||||
} else {
|
|
||||||
let printSuccess = true;
|
|
||||||
|
|
||||||
for(let voucher = 0; voucher < vouchers.length; voucher++) {
|
|
||||||
const printResult = await print.escpos(vouchers[voucher], req.body.language, req.body.printer).catch((e) => {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!printResult) {
|
|
||||||
printSuccess = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(printSuccess) {
|
|
||||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: `Vouchers send to printer!`}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(404);
|
|
||||||
res.render('404', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup API Routes
|
||||||
|
*/
|
||||||
if(variables.serviceApi) {
|
if(variables.serviceApi) {
|
||||||
app.get('/api', (req, res) => {
|
app.get('/api', api.api.get);
|
||||||
res.json({
|
app.get('/api/types', api.types.get);
|
||||||
error: null,
|
app.get('/api/languages', api.languages.get);
|
||||||
data: {
|
|
||||||
message: 'OK',
|
|
||||||
endpoints: [
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
endpoint: '/api'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
endpoint: '/api/types'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
endpoint: '/api/languages'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
endpoint: '/api/vouchers'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
endpoint: '/api/voucher'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
app.get('/api/types', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
error: null,
|
|
||||||
data: {
|
|
||||||
message: 'OK',
|
|
||||||
types: types(variables.voucherTypes)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
app.get('/api/languages', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
error: null,
|
|
||||||
data: {
|
|
||||||
message: 'OK',
|
|
||||||
languages: Object.keys(languages).map(language => {
|
|
||||||
return {
|
|
||||||
code: language,
|
|
||||||
name: languages[language]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
app.get('/api/vouchers', [authorization.api], async (req, res) => {
|
|
||||||
res.json({
|
|
||||||
error: null,
|
|
||||||
data: {
|
|
||||||
message: 'OK',
|
|
||||||
vouchers: cache.vouchers.map((voucher) => {
|
|
||||||
return {
|
|
||||||
id: voucher.id,
|
|
||||||
code: `${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`,
|
|
||||||
type: !voucher.authorizedGuestLimit ? 'multi' : voucher.authorizedGuestLimit === 1 ? 'single' : 'multi',
|
|
||||||
duration: voucher.timeLimitMinutes,
|
|
||||||
data_limit: voucher.dataUsageLimitMBytes ? voucher.dataUsageLimitMBytes : null,
|
|
||||||
download_limit: voucher.rxRateLimitKbps ? voucher.rxRateLimitKbps : null,
|
|
||||||
upload_limit: voucher.txRateLimitKbps ? voucher.txRateLimitKbps : null
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
updated: cache.updated
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
app.post('/api/voucher', [authorization.api], async (req, res) => {
|
|
||||||
// Verify valid body is sent
|
|
||||||
if(!req.body || !req.body.type) {
|
|
||||||
res.status(400).json({
|
|
||||||
error: 'Invalid Body!',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if email body is set
|
app.get('/api/vouchers', [authorization.api], api.vouchers.get);
|
||||||
if(req.body.email) {
|
app.post('/api/voucher', [authorization.api], api.voucher.post);
|
||||||
// Check if email module is enabled
|
|
||||||
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
|
|
||||||
res.status(400).json({
|
|
||||||
error: 'Email Not Configured!',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if email body is correct
|
|
||||||
if(!req.body.email.language || !req.body.email.address) {
|
|
||||||
res.status(400).json({
|
|
||||||
error: 'Invalid Body!',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if language is available
|
|
||||||
if(!Object.keys(languages).includes(req.body.email.language)) {
|
|
||||||
res.status(400).json({
|
|
||||||
error: 'Unknown Language!',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if type is implemented and valid
|
|
||||||
const typeCheck = (variables.voucherTypes).split(';').includes(req.body.type);
|
|
||||||
if(!typeCheck) {
|
|
||||||
res.status(400).json({
|
|
||||||
error: 'Unknown Type!',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create voucher code
|
|
||||||
const voucherCode = await unifi.create(types(req.body.type, true), 1, `||;;||api||;;||local||;;||`).catch((e) => {
|
|
||||||
res.status(500).json({
|
|
||||||
error: e,
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update application cache
|
|
||||||
await updateCache();
|
|
||||||
|
|
||||||
if(voucherCode) {
|
|
||||||
// Locate voucher data within cache
|
|
||||||
const voucherData = cache.vouchers.find(voucher => voucher.code === voucherCode.replaceAll('-', ''));
|
|
||||||
if(!voucherData) {
|
|
||||||
res.status(500).json({
|
|
||||||
error: 'Invalid application cache!',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we should send and email
|
|
||||||
if(req.body.email) {
|
|
||||||
// Send mail
|
|
||||||
const emailResult = await mail.send(req.body.email.address, voucherData, req.body.email.language).catch((e) => {
|
|
||||||
res.status(500).json({
|
|
||||||
error: e,
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify is the email was sent successfully
|
|
||||||
if(emailResult) {
|
|
||||||
res.json({
|
|
||||||
error: null,
|
|
||||||
data: {
|
|
||||||
message: 'OK',
|
|
||||||
voucher: {
|
|
||||||
id: voucherData.id,
|
|
||||||
code: voucherCode
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
status: 'SENT',
|
|
||||||
address: req.body.email.address
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.json({
|
|
||||||
error: null,
|
|
||||||
data: {
|
|
||||||
message: 'OK',
|
|
||||||
voucher: {
|
|
||||||
id: voucherData.id,
|
|
||||||
code: voucherCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup default 404 message
|
* Setup default 404 message
|
||||||
*/
|
*/
|
||||||
app.use((req, res) => {
|
app.use(error["404"]);
|
||||||
res.status(404);
|
|
||||||
res.render('404', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup default 500 message
|
* Setup default 500 message
|
||||||
*/
|
*/
|
||||||
app.use((err, req, res, next) => {
|
app.use(error["500"]);
|
||||||
log.error(err.stack);
|
|
||||||
res.status(500);
|
|
||||||
res.render('500', {
|
|
||||||
baseUrl: req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : '',
|
|
||||||
error: err.stack
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable powered by header for security reasons
|
* Disable powered by header for security reasons
|
||||||
|
|||||||
@@ -32,6 +32,16 @@
|
|||||||
<div id="timer-container" class="timer-progress bg-gray-200 dark:bg-gray-700">
|
<div id="timer-container" class="timer-progress bg-gray-200 dark:bg-gray-700">
|
||||||
<div id="timer-bar" class="bg-sky-600 h-full"></div>
|
<div id="timer-bar" class="bg-sky-600 h-full"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<% } else { %>
|
||||||
|
<% if(kiosk_homepage) { %>
|
||||||
|
<a href="<%= baseUrl %>/vouchers" class="p-2 fixed top-2 left-2 text-md bg-sky-700 text-white rounded-md flex items-center justify-center hover:bg-sky-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-700">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M3 4.25A2.25 2.25 0 0 1 5.25 2h5.5A2.25 2.25 0 0 1 13 4.25v2a.75.75 0 0 1-1.5 0v-2a.75.75 0 0 0-.75-.75h-5.5a.75.75 0 0 0-.75.75v11.5c0 .414.336.75.75.75h5.5a.75.75 0 0 0 .75-.75v-2a.75.75 0 0 1 1.5 0v2A2.25 2.25 0 0 1 10.75 18h-5.5A2.25 2.25 0 0 1 3 15.75V4.25Z" clip-rule="evenodd" />
|
||||||
|
<path fill-rule="evenodd" d="M19 10a.75.75 0 0 0-.75-.75H8.704l1.048-.943a.75.75 0 1 0-1.004-1.114l-2.5 2.25a.75.75 0 0 0 0 1.114l2.5 2.25a.75.75 0 1 0 1.004-1.114l-1.048-.943h9.546A.75.75 0 0 0 19 10Z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
Admin UI
|
||||||
|
</a>
|
||||||
|
<% } %>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<div class="fixed top-0 left-0 w-full h-full -z-20">
|
<div class="fixed top-0 left-0 w-full h-full -z-20">
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ module.exports = {
|
|||||||
'UI_BACK_BUTTON',
|
'UI_BACK_BUTTON',
|
||||||
'PRINTER_TYPE',
|
'PRINTER_TYPE',
|
||||||
'PRINTER_IP',
|
'PRINTER_IP',
|
||||||
'KIOSK_VOUCHER_TYPE'
|
'KIOSK_VOUCHER_TYPE',
|
||||||
|
'UNIFI_USERNAME',
|
||||||
|
'UNIFI_PASSWORD'
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user