From bc75e3f24c609636bcb7668ab6dbe836b0703a00 Mon Sep 17 00:00:00 2001 From: hippoz Date: Wed, 9 Dec 2020 01:49:57 +0200 Subject: [PATCH] [Experimental] add an option for brainlet instance owners to require a special code sign up --- api/v1/content.js | 4 ++-- api/v1/users.js | 35 +++++++++++++++++++++++++-- app/auth.html | 15 +++++++++++- app/resources/js/auth.js | 52 ++++++++++++++++++++++++++++++++++------ 4 files changed, 94 insertions(+), 12 deletions(-) diff --git a/api/v1/content.js b/api/v1/content.js index 3f41348..d62ce82 100755 --- a/api/v1/content.js +++ b/api/v1/content.js @@ -120,7 +120,7 @@ app.get('/category/:category/info', [ users: users } }); -})); +}, undefined, config.roleMap.USER)); app.get('/category/list', authenticateEndpoint(async (req, res, user) => { let count = parseInt(req.query.count); @@ -136,6 +136,6 @@ app.get('/category/list', authenticateEndpoint(async (req, res, user) => { message: 'SUCCESS_CATEGORY_LIST_FETCHED', categories }); -})); +}, undefined, config.roleMap.USER)); module.exports = app; \ No newline at end of file diff --git a/api/v1/users.js b/api/v1/users.js index 7155e1a..30eeb7f 100755 --- a/api/v1/users.js +++ b/api/v1/users.js @@ -22,11 +22,26 @@ const createAccountLimiter = rateLimit({ message: "You are being rate limited" }); +app.get('/account/create/info', async (req, res) => { + const restrictions = config.restrictions.signup; + let requiresCode = false; + if (restrictions && restrictions.specialCode) { + requiresCode = true; + } + + res.json({ + error: false, + message: 'SUCCESS_ACCOUNT_CREATE_INFO_FETCH', + requiresSpecialCode: requiresCode + }); +}); + app.post('/account/create', [ createAccountLimiter, body('username').not().isEmpty().trim().isLength({ min: 3, max: 32 }).isAlphanumeric(), body('email').not().isEmpty().isEmail().normalizeEmail(), - body('password').not().isEmpty().isLength({ min: 8, max: 128 }) + body('password').not().isEmpty().isLength({ min: 8, max: 128 }), + body('specialCode').optional().isLength({ min: 12, max: 12 }).isAlphanumeric() ], async (req, res) => { try { const errors = validationResult(req); @@ -34,6 +49,22 @@ app.post('/account/create', [ res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() }); return; } + + const restrictions = config.restrictions.signup; + if (restrictions && restrictions.specialCode) { + const passedSpecialCode = req.body.specialCode; + const specialCode = restrictions.specialCode; + + if (passedSpecialCode && specialCode) { + if (specialCode !== passedSpecialCode) { + res.status(401).json({ error: true, message: 'ERROR_REQUEST_SPECIAL_CODE_MISSING', errors: [{ msg: 'No specialCode passed', param: 'specialCode', location: 'body' }] }); + return false; + } + } else { + res.status(401).json({ error: true, message: 'ERROR_REQUEST_SPECIAL_CODE_MISSING', errors: [{ msg: 'No specialCode passed', param: 'specialCode', location: 'body' }] }); + return false; + } + } const username = req.body.username; @@ -171,7 +202,7 @@ app.get('/user/:userid/info', [ message: 'SUCCESS_USER_DATA_FETCHED', user: await otherUser.getPublicObject(), }); -})); +}, undefined, config.roleMap.USER)); app.post('/browser/token/clear', authenticateEndpoint((req, res, user) => { res.clearCookie('token'); diff --git a/app/auth.html b/app/auth.html index aa3f41b..4f76fb9 100755 --- a/app/auth.html +++ b/app/auth.html @@ -47,13 +47,26 @@ +
+
+

+ The owner of this Brainlet instance has made it so that signing up requires a special code. +

+ + + + +
+
Log in instead Sign up instead - Sign up + Go back + Sign up + Log in Log out diff --git a/app/resources/js/auth.js b/app/resources/js/auth.js index 5b79b8c..f1e6788 100755 --- a/app/resources/js/auth.js +++ b/app/resources/js/auth.js @@ -30,6 +30,9 @@ const getSignupMessageFromError = (json) => { case 'email': { return 'Invalid email.'; } + case 'specialCode': { + return 'Invalid special code.'; + } default: { return 'Invalid value sent to server. Something went wrong.'; @@ -58,13 +61,15 @@ const app = new Vue({ usernameInput: '', emailInput: '', passwordInput: '', + specialCodeInput: '', mode: 'SIGNUP', showSnackbarNotification: false, snackbarNotification: '', snackbarNotificationDuration: 999999, snackbarButtonText: 'Ok', successfulLogin: false, - loggedInUser: {} + loggedInUser: {}, + requiresSpecialCode: null }, mounted: async function() { const res = await fetch(`${window.location.origin}/api/v1/users/current/info`, { @@ -86,7 +91,21 @@ const app = new Vue({ this.successfulLogin = true; } } else { - this.mode = 'SIGNUP'; + const resInfo = await fetch(`${window.location.origin}/api/v1/users/account/create/info`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + } + }); + + if (resInfo.ok) { + const json = await resInfo.json(); + this.requiresSpecialCode = json.requiresSpecialCode; + + this.mode = 'SIGNUP'; + } else { + this.mode = '_ERROR'; + } } }, computed: { @@ -104,6 +123,9 @@ const app = new Vue({ case 'MANAGE': { return this.loggedInUser.username || 'Unknown account'; } + case 'SPECIAL_CODE': { + return 'Just one more step' + } case 'NONE': { return ''; } @@ -192,17 +214,33 @@ const app = new Vue({ } }, performAccountCreation: async function() { + let jsonData = { + username: this.usernameInput, + email: this.emailInput, + password: this.passwordInput + }; + + if (this.requiresSpecialCode) { + if (this.mode === 'SIGNUP') { + this.mode = 'SPECIAL_CODE'; + return; + } else if (this.mode !== 'SPECIAL_CODE') { + return; + } + + jsonData = { + specialCode: this.specialCodeInput, + ...jsonData + } + } + const res = await fetch(`${window.location.origin}/api/v1/users/account/create`, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, - body: JSON.stringify({ - username: this.usernameInput, - email: this.emailInput, - password: this.passwordInput - }) + body: JSON.stringify(jsonData) }); const json = await res.json();