[Experimental] add an option for brainlet instance owners to require a special code sign up

This commit is contained in:
hippoz 2020-12-09 01:49:57 +02:00
parent 6d142767b0
commit bc75e3f24c
4 changed files with 94 additions and 12 deletions

View file

@ -120,7 +120,7 @@ app.get('/category/:category/info', [
users: users users: users
} }
}); });
})); }, undefined, config.roleMap.USER));
app.get('/category/list', authenticateEndpoint(async (req, res, user) => { app.get('/category/list', authenticateEndpoint(async (req, res, user) => {
let count = parseInt(req.query.count); let count = parseInt(req.query.count);
@ -136,6 +136,6 @@ app.get('/category/list', authenticateEndpoint(async (req, res, user) => {
message: 'SUCCESS_CATEGORY_LIST_FETCHED', message: 'SUCCESS_CATEGORY_LIST_FETCHED',
categories categories
}); });
})); }, undefined, config.roleMap.USER));
module.exports = app; module.exports = app;

View file

@ -22,11 +22,26 @@ const createAccountLimiter = rateLimit({
message: "You are being rate limited" 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', [ app.post('/account/create', [
createAccountLimiter, createAccountLimiter,
body('username').not().isEmpty().trim().isLength({ min: 3, max: 32 }).isAlphanumeric(), body('username').not().isEmpty().trim().isLength({ min: 3, max: 32 }).isAlphanumeric(),
body('email').not().isEmpty().isEmail().normalizeEmail(), 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) => { ], async (req, res) => {
try { try {
const errors = validationResult(req); 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() }); res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() });
return; 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; const username = req.body.username;
@ -171,7 +202,7 @@ app.get('/user/:userid/info', [
message: 'SUCCESS_USER_DATA_FETCHED', message: 'SUCCESS_USER_DATA_FETCHED',
user: await otherUser.getPublicObject(), user: await otherUser.getPublicObject(),
}); });
})); }, undefined, config.roleMap.USER));
app.post('/browser/token/clear', authenticateEndpoint((req, res, user) => { app.post('/browser/token/clear', authenticateEndpoint((req, res, user) => {
res.clearCookie('token'); res.clearCookie('token');

View file

@ -47,13 +47,26 @@
</md-field> </md-field>
</div> </div>
</div> </div>
<div v-if="mode === 'SPECIAL_CODE'">
<div>
<p>
The owner of this Brainlet instance has made it so that signing up requires a special code.
</p>
<md-field>
<label>Special code</label>
<md-input v-model="specialCodeInput" type="password"></md-input>
</md-field>
</div>
</div>
</md-card-content> </md-card-content>
<md-card-actions> <md-card-actions>
<md-button v-if="mode === 'SIGNUP'" @click="mode='LOGIN'" class="md-dense">Log in instead</md-button> <md-button v-if="mode === 'SIGNUP'" @click="mode='LOGIN'" class="md-dense">Log in instead</md-button>
<md-button v-if="mode === 'LOGIN'" @click="mode='SIGNUP'" class="md-dense">Sign up instead</md-button> <md-button v-if="mode === 'LOGIN'" @click="mode='SIGNUP'" class="md-dense">Sign up instead</md-button>
<md-button v-if="mode === 'SIGNUP'" class="md-dense md-raised md-primary" @click="performAccountCreation()">Sign up</md-button> <md-button v-if="mode === 'SPECIAL_CODE'" class="md-dense" @click="mode='SIGNUP'">Go back</md-button>
<md-button v-if="mode === 'SIGNUP' || mode === 'SPECIAL_CODE'" class="md-dense md-raised md-primary" @click="performAccountCreation()">Sign up</md-button>
<md-button v-if="mode === 'LOGIN'" class="md-dense md-raised md-primary" @click="performTokenCreation()">Log in</md-button> <md-button v-if="mode === 'LOGIN'" class="md-dense md-raised md-primary" @click="performTokenCreation()">Log in</md-button>
<md-button v-if="mode === 'MANAGE'" class="md-dense md-raised" @click="performTokenRemoval()">Log out</md-button> <md-button v-if="mode === 'MANAGE'" class="md-dense md-raised" @click="performTokenRemoval()">Log out</md-button>

View file

@ -30,6 +30,9 @@ const getSignupMessageFromError = (json) => {
case 'email': { case 'email': {
return 'Invalid email.'; return 'Invalid email.';
} }
case 'specialCode': {
return 'Invalid special code.';
}
default: { default: {
return 'Invalid value sent to server. Something went wrong.'; return 'Invalid value sent to server. Something went wrong.';
@ -58,13 +61,15 @@ const app = new Vue({
usernameInput: '', usernameInput: '',
emailInput: '', emailInput: '',
passwordInput: '', passwordInput: '',
specialCodeInput: '',
mode: 'SIGNUP', mode: 'SIGNUP',
showSnackbarNotification: false, showSnackbarNotification: false,
snackbarNotification: '', snackbarNotification: '',
snackbarNotificationDuration: 999999, snackbarNotificationDuration: 999999,
snackbarButtonText: 'Ok', snackbarButtonText: 'Ok',
successfulLogin: false, successfulLogin: false,
loggedInUser: {} loggedInUser: {},
requiresSpecialCode: null
}, },
mounted: async function() { mounted: async function() {
const res = await fetch(`${window.location.origin}/api/v1/users/current/info`, { const res = await fetch(`${window.location.origin}/api/v1/users/current/info`, {
@ -86,7 +91,21 @@ const app = new Vue({
this.successfulLogin = true; this.successfulLogin = true;
} }
} else { } 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: { computed: {
@ -104,6 +123,9 @@ const app = new Vue({
case 'MANAGE': { case 'MANAGE': {
return this.loggedInUser.username || 'Unknown account'; return this.loggedInUser.username || 'Unknown account';
} }
case 'SPECIAL_CODE': {
return 'Just one more step'
}
case 'NONE': { case 'NONE': {
return ''; return '';
} }
@ -192,17 +214,33 @@ const app = new Vue({
} }
}, },
performAccountCreation: async function() { 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`, { const res = await fetch(`${window.location.origin}/api/v1/users/account/create`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify(jsonData)
username: this.usernameInput,
email: this.emailInput,
password: this.passwordInput
})
}); });
const json = await res.json(); const json = await res.json();