mirror of
https://github.com/Zandercraft/TechnicFlux.git
synced 2026-11-02 05:19:20 +00:00
Add session handling and optional reCaptcha support
This commit is contained in:
18
.env.example
18
.env.example
@@ -29,6 +29,14 @@ ADMIN_PASS="flux"
|
||||
# 4. Put your solder API URL (http[s]://example.com/api/) in and link it.
|
||||
API_KEY=""
|
||||
|
||||
# (REQUIRED) Session Secret Key. CHANGE THIS!!!!
|
||||
# This is the key that will be used to encrypt sessions. It is recommended that you make it secure.
|
||||
# You can generate one using the following steps:
|
||||
# 1. Go to https://www.lastpass.com/features/password-generator#generatorTool
|
||||
# 2. Set password length to 50 and leave it set to all characters
|
||||
# 3. Copy the password here.
|
||||
SESSION_KEY="(/jg~WBd4%+cXt9vp'&/lN-;mynl:N}v£D!0I5V04tcpYb&;.F"
|
||||
|
||||
# (Optional) Default Author Name (for custom files)
|
||||
# This is the name that will be pre-filled when you go to upload custom files for a modpack.
|
||||
# Unset behaviour: You will need to specify the author every time.
|
||||
@@ -49,3 +57,13 @@ API_RATE_REQUESTS=1000
|
||||
# The amount of time (in milliseconds) that the request limit is applied to.
|
||||
# Unset behaviour: Defaults to 60000
|
||||
API_RATE_WINDOW=60000
|
||||
|
||||
# --- Google ReCaptcha ---
|
||||
# TechnicFlux comes with Google ReCaptcha support.
|
||||
# When configured, this will enable captcha protection on your login page.
|
||||
|
||||
# (Optional) Google ReCaptcha Credentials
|
||||
# You can obtain these credentials at https://www.google.com/recaptcha/admin/create
|
||||
# Unset behaviour: ReCaptcha is disabled on login page.
|
||||
#CAPTCHA_SITE_KEY=""
|
||||
#CAPTCHA_SITE_SECRET=""
|
||||
|
||||
15
app.js
15
app.js
@@ -4,6 +4,8 @@ const path = require('path')
|
||||
const cookieParser = require('cookie-parser')
|
||||
const logger = require('morgan')
|
||||
const multer = require('multer')
|
||||
const session = require('express-session')
|
||||
const MongoStore = require('connect-mongo')
|
||||
|
||||
// Setup multer disk-store
|
||||
const storage = multer.diskStorage({
|
||||
@@ -27,9 +29,20 @@ const app = express()
|
||||
app.set('views', path.join(__dirname, 'views'))
|
||||
app.set('view engine', 'hbs')
|
||||
|
||||
// --- Middleware ---
|
||||
app.use(session({
|
||||
secret: process.env.SESSION_KEY,
|
||||
saveUninitialized: false,
|
||||
resave: false,
|
||||
store: MongoStore.create({
|
||||
mongoUrl: process.env.MONGODB_CONN_STRING,
|
||||
touchAfter: 24 * 3600,
|
||||
collectionName: "technicflux_sessions"
|
||||
})
|
||||
}))
|
||||
app.use(logger('dev'))
|
||||
app.use(express.json())
|
||||
app.use(express.urlencoded({ extended: false }))
|
||||
app.use(express.urlencoded({ extended: true }))
|
||||
app.use(cookieParser())
|
||||
app.use(express.static(path.join(__dirname, 'public')))
|
||||
|
||||
|
||||
3
bin/www
3
bin/www
@@ -12,7 +12,8 @@ const required_variables = [
|
||||
process.env.ADMIN_PASS,
|
||||
process.env.NAME,
|
||||
process.env.HOST,
|
||||
process.env.API_KEY
|
||||
process.env.API_KEY,
|
||||
process.env.SESSION_KEY
|
||||
]
|
||||
for (let variable of required_variables) {
|
||||
if (variable === undefined) {
|
||||
|
||||
139
package-lock.json
generated
139
package-lock.json
generated
@@ -11,12 +11,15 @@
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "^2.7.29",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"connect-mongo": "^5.0.0",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"current-git-branch": "^1.1.0",
|
||||
"debug": "~4.3.4",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "~4.18.2",
|
||||
"express-rate-limit": "^7.0.2",
|
||||
"express-recaptcha": "^5.1.0",
|
||||
"express-session": "^1.17.3",
|
||||
"hbs": "~4.2.0",
|
||||
"http-errors": "~1.6.3",
|
||||
"mongoose": "^7.5.3",
|
||||
@@ -395,6 +398,17 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1.js": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
|
||||
"dependencies": {
|
||||
"bn.js": "^4.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynciterator.prototype": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz",
|
||||
@@ -449,6 +463,11 @@
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||
@@ -665,6 +684,22 @@
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/connect-mongo": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-5.0.0.tgz",
|
||||
"integrity": "sha512-s93jiP6GkRApn5duComx6RLwtP23YrulPxShz+8peX7svd6Q+MS8nKLhKCCazbP92C13eTVaIOxgeLt0ezIiCg==",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.1",
|
||||
"kruptein": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express-session": "^1.17.1",
|
||||
"mongodb": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
@@ -1615,6 +1650,75 @@
|
||||
"express": "^4 || ^5"
|
||||
}
|
||||
},
|
||||
"node_modules/express-recaptcha": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/express-recaptcha/-/express-recaptcha-5.1.0.tgz",
|
||||
"integrity": "sha512-L9BSaHbwVzEAf38LNnFwEbk4qpX1LiD1z8viBGYBuxyBWWVKMgBrKDb1mNLq6uSxubJzZbyIz+0ldgTSXfxsvQ==",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session": {
|
||||
"version": "1.17.3",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz",
|
||||
"integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==",
|
||||
"dependencies": {
|
||||
"cookie": "0.4.2",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-headers": "~1.0.2",
|
||||
"parseurl": "~1.3.3",
|
||||
"safe-buffer": "5.2.1",
|
||||
"uid-safe": "~2.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/cookie": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
||||
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/express/node_modules/cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
@@ -2620,6 +2724,17 @@
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/kruptein": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.6.tgz",
|
||||
"integrity": "sha512-EQJjTwAJfQkC4NfdQdo3HXM2a9pmBm8oidzH270cYu1MbgXPNPMJuldN7OPX+qdhPO5rw4X3/iKz0BFBfkXGKA==",
|
||||
"dependencies": {
|
||||
"asn1.js": "^5.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">8"
|
||||
}
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
@@ -2753,6 +2868,11 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@@ -3388,6 +3508,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/random-bytes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
@@ -4327,6 +4455,17 @@
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||
"dependencies": {
|
||||
"random-bytes": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
||||
|
||||
@@ -39,12 +39,15 @@
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "^2.7.29",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"connect-mongo": "^5.0.0",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"current-git-branch": "^1.1.0",
|
||||
"debug": "~4.3.4",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "~4.18.2",
|
||||
"express-rate-limit": "^7.0.2",
|
||||
"express-recaptcha": "^5.1.0",
|
||||
"express-session": "^1.17.3",
|
||||
"hbs": "~4.2.0",
|
||||
"http-errors": "~1.6.3",
|
||||
"mongoose": "^7.5.3",
|
||||
|
||||
2
public/stylesheets/bootstrap.min.css
vendored
2
public/stylesheets/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -9,3 +9,7 @@ body {
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
||||
|
||||
.grecaptcha-badge {
|
||||
margin-bottom: 65px;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,36 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const { RecaptchaV3 } = require('express-recaptcha')
|
||||
|
||||
// --- Google ReCaptcha
|
||||
const site_key = process.env?.CAPTCHA_SITE_KEY
|
||||
const site_secret = process.env?.CAPTCHA_SITE_SECRET
|
||||
const captcha_enabled = (site_key !== undefined) && (site_secret !== undefined)
|
||||
const recaptcha = captcha_enabled ? new RecaptchaV3(site_key, site_secret, {
|
||||
callback: 'cb'
|
||||
}) : undefined
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function (req, res) {
|
||||
res.render('index', { title: 'TechnicFlux' })
|
||||
router.get('/', captcha_enabled ? recaptcha.middleware.render : (req, res, next) => {next()}, function (req, res) {
|
||||
res.render('index', {
|
||||
title: 'TechnicFlux',
|
||||
captcha: captcha_enabled ? res.recaptcha : false
|
||||
})
|
||||
})
|
||||
|
||||
/* POST home page (login info w/ captcha) */
|
||||
router.post('/', captcha_enabled ? recaptcha.middleware.verify : (req, res, next) => {next()}, function (req, res) {
|
||||
// Validate captcha, if enabled.
|
||||
if (captcha_enabled && req.recaptcha.error) {
|
||||
return res.render('index', {
|
||||
title: 'TechnicFlux',
|
||||
captcha: captcha_enabled ? res.recaptcha : false,
|
||||
error: "Captcha validation failed! Ensure that javascript is enabled!"
|
||||
})
|
||||
}
|
||||
|
||||
// Check login info
|
||||
res.json({success: "yay"})
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
|
||||
0
views/dashboard.hbs
Normal file
0
views/dashboard.hbs
Normal file
@@ -1,4 +1,53 @@
|
||||
<img src="/images/technicflux-logo-white.png" width="108px" height="108px" />
|
||||
<h1>{{title}}</h1>
|
||||
<p>Welcome to {{title}}!</p>
|
||||
<p>The dashboard isn't ready yet. We are currently working on the API and backend stuff.</p>
|
||||
<div id="layoutAuthentication_content">
|
||||
<main>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-5">
|
||||
<div class="card border-0 rounded-lg mt-5">
|
||||
<div class="card-header bg-white">
|
||||
<img class="mx-auto my-5 d-block" src="/images/technicflux-logo.png" width="108px" height="108px" alt="TechnicFlux logo"/>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{#if error}}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{{error}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<form id="login-form" method="post" target="/">
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control" id="inputEmail" type="text" placeholder="name@example.com" />
|
||||
<label for="inputEmail" class="text-dark">Username</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control" id="inputPassword" type="password" placeholder="Password" />
|
||||
<label for="inputPassword" class="text-dark">Password</label>
|
||||
</div>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<button class="btn btn-primary w-100 g-recaptcha"
|
||||
data-sitekey="{{site_key}}"
|
||||
data-callback="onSubmit"
|
||||
data-action="submit"
|
||||
>Log In</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{{#if captcha}}
|
||||
{{{captcha}}}
|
||||
<script>
|
||||
function cb(token) {
|
||||
let input = document.createElement('input')
|
||||
input.setAttribute('type', 'text')
|
||||
input.setAttribute('name', 'g-recaptcha-response')
|
||||
input.setAttribute('value', token)
|
||||
input.setAttribute('hidden', true)
|
||||
document.getElementsByTagName('form')[0].appendChild(input)
|
||||
}
|
||||
</script>
|
||||
{{/if}}
|
||||
|
||||
Reference in New Issue
Block a user