Browse Source

improve dashboard sessions

Markus-Rost 4 years ago
parent
commit
c38f50aaad
4 changed files with 60 additions and 49 deletions
  1. 7 7
      dashboard/guilds.js
  2. 10 10
      dashboard/index.js
  3. 30 30
      dashboard/oauth.js
  4. 13 2
      dashboard/util.js

+ 7 - 7
dashboard/guilds.js

@@ -25,17 +25,17 @@ const file = require('fs').readFileSync('./dashboard/index.html');
  * @param {import('http').ServerResponse} res - The server response
  * @param {import('http').ServerResponse} res - The server response
  * @param {import('./i18n.js')} dashboardLang - The user language.
  * @param {import('./i18n.js')} dashboardLang - The user language.
  * @param {String} theme - The display theme
  * @param {String} theme - The display theme
- * @param {String} state - The user state
+ * @param {import('./util.js').UserSession} userSession - The user session
  * @param {URL} reqURL - The used url
  * @param {URL} reqURL - The used url
  * @param {String} [action] - The action the user made
  * @param {String} [action] - The action the user made
  * @param {String[]} [actionArgs] - The arguments for the action
  * @param {String[]} [actionArgs] - The arguments for the action
  */
  */
-function dashboard_guilds(res, dashboardLang, theme, state, reqURL, action, actionArgs) {
+function dashboard_guilds(res, dashboardLang, theme, userSession, reqURL, action, actionArgs) {
 	reqURL.pathname = reqURL.pathname.replace( /^(\/(?:guild\/\d+(?:\/(?:settings|verification|rcscript|slash)(?:\/(?:\d+|new))?)?)?)(?:\/.*)?$/, '$1' );
 	reqURL.pathname = reqURL.pathname.replace( /^(\/(?:guild\/\d+(?:\/(?:settings|verification|rcscript|slash)(?:\/(?:\d+|new))?)?)?)(?:\/.*)?$/, '$1' );
 	var args = reqURL.pathname.split('/');
 	var args = reqURL.pathname.split('/');
 	args = reqURL.pathname.split('/');
 	args = reqURL.pathname.split('/');
-	var settings = settingsData.get(state);
-	if ( reqURL.searchParams.get('owner') && process.env.owner.split('|').includes(settings.user.id) ) {
+	var settings = settingsData.get(userSession.user_id);
+	if ( reqURL.searchParams.get('owner') && process.env.owner.split('|').includes(userSession.user_id) ) {
 		args[0] = 'owner';
 		args[0] = 'owner';
 	}
 	}
 	dashboardLang = new Lang(...dashboardLang.fromCookie, settings.user.locale, dashboardLang.lang);
 	dashboardLang = new Lang(...dashboardLang.fromCookie, settings.user.locale, dashboardLang.lang);
@@ -68,7 +68,7 @@ function dashboard_guilds(res, dashboardLang, theme, state, reqURL, action, acti
 	$('#logout span').text(`${settings.user.username} #${settings.user.discriminator}`);
 	$('#logout span').text(`${settings.user.username} #${settings.user.discriminator}`);
 	$('.guild#invite a').attr('href', oauth.generateAuthUrl( {
 	$('.guild#invite a').attr('href', oauth.generateAuthUrl( {
 		scope: ['identify', 'guilds', 'bot', 'applications.commands'],
 		scope: ['identify', 'guilds', 'bot', 'applications.commands'],
-		permissions: defaultPermissions, state
+		permissions: defaultPermissions, state: userSession.state
 	} ));
 	} ));
 	$('.guild#refresh a').attr('href', '/refresh?return=' + reqURL.pathname);
 	$('.guild#refresh a').attr('href', '/refresh?return=' + reqURL.pathname);
 	if ( settings.guilds.isMember.size ) {
 	if ( settings.guilds.isMember.size ) {
@@ -128,7 +128,7 @@ function dashboard_guilds(res, dashboardLang, theme, state, reqURL, action, acti
 		let url = oauth.generateAuthUrl( {
 		let url = oauth.generateAuthUrl( {
 			scope: ['identify', 'guilds', 'bot', 'applications.commands'],
 			scope: ['identify', 'guilds', 'bot', 'applications.commands'],
 			permissions: defaultPermissions,
 			permissions: defaultPermissions,
-			guildId: guild.id, state
+			guildId: guild.id, state: userSession.state
 		} );
 		} );
 		$('#channellist').empty();
 		$('#channellist').empty();
 		$('<a class="channel channel-header">').attr('href', url).append(
 		$('<a class="channel channel-header">').attr('href', url).append(
@@ -204,7 +204,7 @@ function dashboard_guilds(res, dashboardLang, theme, state, reqURL, action, acti
 		if ( !settings.guilds.count ) {
 		if ( !settings.guilds.count ) {
 			let url = oauth.generateAuthUrl( {
 			let url = oauth.generateAuthUrl( {
 				scope: ['identify', 'guilds'],
 				scope: ['identify', 'guilds'],
-				prompt: 'consent', state
+				prompt: 'consent', state: userSession.state
 			} );
 			} );
 			$('<a class="channel channel-header">').attr('href', url).append(
 			$('<a class="channel channel-header">').attr('href', url).append(
 				$('<img>').attr('src', '/src/settings.svg'),
 				$('<img>').attr('src', '/src/settings.svg'),

+ 10 - 10
dashboard/index.js

@@ -1,7 +1,7 @@
 const http = require('http');
 const http = require('http');
 const pages = require('./oauth.js');
 const pages = require('./oauth.js');
 const dashboard = require('./guilds.js');
 const dashboard = require('./guilds.js');
-const {db, settingsData} = require('./util.js');
+const {db, sessionData, settingsData} = require('./util.js');
 const Lang = require('./i18n.js');
 const Lang = require('./i18n.js');
 const allLangs = Lang.allLangs();
 const allLangs = Lang.allLangs();
 
 
@@ -59,8 +59,8 @@ const server = http.createServer( (req, res) => {
 		} )?.map( cookie => cookie.replace( /^wikibot="(\w*(?:-\d+)*)"$/, '$1' ) )?.join();
 		} )?.map( cookie => cookie.replace( /^wikibot="(\w*(?:-\d+)*)"$/, '$1' ) )?.join();
 
 
 		if ( args.length === 5 && ['settings', 'verification', 'rcscript', 'slash'].includes( args[3] )
 		if ( args.length === 5 && ['settings', 'verification', 'rcscript', 'slash'].includes( args[3] )
-		&& /^(?:default|new|\d+)$/.test(args[4]) && settingsData.has(state)
-		&& settingsData.get(state).guilds.isMember.has(args[2]) ) {
+		&& /^(?:default|new|\d+)$/.test(args[4]) && sessionData.has(state) && settingsData.has(sessionData.get(state).user_id)
+		&& settingsData.get(sessionData.get(state).user_id).guilds.isMember.has(args[2]) ) {
 			if ( process.env.READONLY ) return save_response(`${req.url}?save=failed`);
 			if ( process.env.READONLY ) return save_response(`${req.url}?save=failed`);
 			let body = [];
 			let body = [];
 			req.on( 'data', chunk => {
 			req.on( 'data', chunk => {
@@ -83,8 +83,8 @@ const server = http.createServer( (req, res) => {
 						}
 						}
 					}
 					}
 				} );
 				} );
-				if ( isDebug ) console.log( '- Dashboard:', req.url, settings, settingsData.get(state).user.id );
-				return posts[args[3]](save_response, settingsData.get(state), args[2], args[4], settings);
+				if ( isDebug ) console.log( '- Dashboard:', req.url, settings, sessionData.get(state).user_id );
+				return posts[args[3]](save_response, settingsData.get(sessionData.get(state).user_id), args[2], args[4], settings);
 			} );
 			} );
 
 
 			/**
 			/**
@@ -109,7 +109,7 @@ const server = http.createServer( (req, res) => {
 					return '';
 					return '';
 				} ) || [] ));
 				} ) || [] ));
 				dashboardLang.fromCookie = langCookie;
 				dashboardLang.fromCookie = langCookie;
-				return dashboard(res, dashboardLang, themeCookie, state, new URL(resURL, process.env.dashboard), action, actionArgs);
+				return dashboard(res, dashboardLang, themeCookie, sessionData.get(state), new URL(resURL, process.env.dashboard), action, actionArgs);
 			}
 			}
 		}
 		}
 	}
 	}
@@ -170,7 +170,7 @@ const server = http.createServer( (req, res) => {
 	}
 	}
 
 
 	if ( reqURL.pathname === '/logout' ) {
 	if ( reqURL.pathname === '/logout' ) {
-		settingsData.delete(state);
+		sessionData.delete(state);
 		res.setHeader('Set-Cookie', [
 		res.setHeader('Set-Cookie', [
 			...( res.getHeader('Set-Cookie') || [] ),
 			...( res.getHeader('Set-Cookie') || [] ),
 			'wikibot=""; HttpOnly; Path=/; Max-Age=0'
 			'wikibot=""; HttpOnly; Path=/; Max-Age=0'
@@ -192,7 +192,7 @@ const server = http.createServer( (req, res) => {
 		return pages.oauth(res, state, reqURL.searchParams, lastGuild);
 		return pages.oauth(res, state, reqURL.searchParams, lastGuild);
 	}
 	}
 
 
-	if ( !settingsData.has(state) ) {
+	if ( !sessionData.has(state) || !settingsData.has(sessionData.get(state).user_id) ) {
 		if ( reqURL.pathname.startsWith( '/guild/' ) ) {
 		if ( reqURL.pathname.startsWith( '/guild/' ) ) {
 			let pathGuild = reqURL.pathname.split('/').slice(2, 5).join('/');
 			let pathGuild = reqURL.pathname.split('/').slice(2, 5).join('/');
 			if ( /^\d+\/(?:settings|verification|rcscript|slash)(?:\/(?:\d+|new))?$/.test(pathGuild) ) {
 			if ( /^\d+\/(?:settings|verification|rcscript|slash)(?:\/(?:\d+|new))?$/.test(pathGuild) ) {
@@ -207,7 +207,7 @@ const server = http.createServer( (req, res) => {
 		if ( !/^\/guild\/\d+\/(?:settings|verification|rcscript|slash)(?:\/(?:\d+|new))?$/.test(returnLocation) ) {
 		if ( !/^\/guild\/\d+\/(?:settings|verification|rcscript|slash)(?:\/(?:\d+|new))?$/.test(returnLocation) ) {
 			returnLocation = '/';
 			returnLocation = '/';
 		}
 		}
-		return pages.refresh(res, state, returnLocation);
+		return pages.refresh(res, sessionData.get(state), returnLocation);
 	}
 	}
 
 
 	if ( reqURL.pathname === '/api' ) {
 	if ( reqURL.pathname === '/api' ) {
@@ -218,7 +218,7 @@ const server = http.createServer( (req, res) => {
 	let action = '';
 	let action = '';
 	if ( reqURL.searchParams.get('refresh') === 'success' ) action = 'refresh';
 	if ( reqURL.searchParams.get('refresh') === 'success' ) action = 'refresh';
 	if ( reqURL.searchParams.get('refresh') === 'failed' ) action = 'refreshfail';
 	if ( reqURL.searchParams.get('refresh') === 'failed' ) action = 'refreshfail';
-	return dashboard(res, dashboardLang, themeCookie, state, reqURL, action);
+	return dashboard(res, dashboardLang, themeCookie, sessionData.get(state), reqURL, action);
 } );
 } );
 
 
 server.listen( 8080, 'localhost', () => {
 server.listen( 8080, 'localhost', () => {

+ 30 - 30
dashboard/oauth.js

@@ -3,7 +3,7 @@ const cheerio = require('cheerio');
 const {defaultPermissions} = require('../util/default.json');
 const {defaultPermissions} = require('../util/default.json');
 const Wiki = require('../util/wiki.js');
 const Wiki = require('../util/wiki.js');
 const allLangs = require('./i18n.js').allLangs().names;
 const allLangs = require('./i18n.js').allLangs().names;
-const {got, settingsData, sendMsg, addWidgets, createNotice, hasPerm} = require('./util.js');
+const {got, sessionData, settingsData, sendMsg, addWidgets, createNotice, hasPerm} = require('./util.js');
 
 
 const DiscordOauth2 = require('discord-oauth2');
 const DiscordOauth2 = require('discord-oauth2');
 const oauth = new DiscordOauth2( {
 const oauth = new DiscordOauth2( {
@@ -23,12 +23,12 @@ const file = require('fs').readFileSync('./dashboard/login.html');
  * @param {String} [action] - The action the user made
  * @param {String} [action] - The action the user made
  */
  */
 function dashboard_login(res, dashboardLang, theme, state, action) {
 function dashboard_login(res, dashboardLang, theme, state, action) {
-	if ( state && settingsData.has(state) ) {
+	if ( state && sessionData.has(state) ) {
 		if ( !action ) {
 		if ( !action ) {
 			res.writeHead(302, {Location: '/'});
 			res.writeHead(302, {Location: '/'});
 			return res.end();
 			return res.end();
 		}
 		}
-		settingsData.delete(state);
+		sessionData.delete(state);
 	}
 	}
 	var $ = cheerio.load(file);
 	var $ = cheerio.load(file);
 	$('html').attr('lang', dashboardLang.lang);
 	$('html').attr('lang', dashboardLang.lang);
@@ -54,9 +54,9 @@ function dashboard_login(res, dashboardLang, theme, state, action) {
 	);
 	);
 	if ( action === 'logout' ) prompt = 'consent';
 	if ( action === 'logout' ) prompt = 'consent';
 	if ( action === 'loginfail' ) responseCode = 400;
 	if ( action === 'loginfail' ) responseCode = 400;
-	state = crypto.randomBytes(16).toString("hex");
-	while ( settingsData.has(state) ) {
-		state = crypto.randomBytes(16).toString("hex");
+	state = Date.now().toString(16) + crypto.randomBytes(16).toString("hex");
+	while ( sessionData.has(state) ) {
+		state = Date.now().toString(16) + crypto.randomBytes(16).toString("hex");
 	}
 	}
 	let invite = oauth.generateAuthUrl( {
 	let invite = oauth.generateAuthUrl( {
 		scope: ['identify', 'guilds', 'bot', 'applications.commands'],
 		scope: ['identify', 'guilds', 'bot', 'applications.commands'],
@@ -89,7 +89,7 @@ function dashboard_login(res, dashboardLang, theme, state, action) {
  * @param {String} [lastGuild] - The guild to return to
  * @param {String} [lastGuild] - The guild to return to
  */
  */
 function dashboard_oauth(res, state, searchParams, lastGuild) {
 function dashboard_oauth(res, state, searchParams, lastGuild) {
-	if ( searchParams.get('error') === 'access_denied' && state === searchParams.get('state') && settingsData.has(state) ) {
+	if ( searchParams.get('error') === 'access_denied' && state === searchParams.get('state') && sessionData.has(state) ) {
 		res.writeHead(302, {Location: '/'});
 		res.writeHead(302, {Location: '/'});
 		return res.end();
 		return res.end();
 	}
 	}
@@ -97,7 +97,7 @@ function dashboard_oauth(res, state, searchParams, lastGuild) {
 		res.writeHead(302, {Location: '/login?action=failed'});
 		res.writeHead(302, {Location: '/login?action=failed'});
 		return res.end();
 		return res.end();
 	}
 	}
-	settingsData.delete(state);
+	sessionData.delete(state);
 	return oauth.tokenRequest( {
 	return oauth.tokenRequest( {
 		scope: ['identify', 'guilds'],
 		scope: ['identify', 'guilds'],
 		code: searchParams.get('code'),
 		code: searchParams.get('code'),
@@ -124,25 +124,25 @@ function dashboard_oauth(res, state, searchParams, lastGuild) {
 				member: user.id,
 				member: user.id,
 				guilds: guilds.map( guild => guild.id )
 				guilds: guilds.map( guild => guild.id )
 			} ).then( response => {
 			} ).then( response => {
-				var settings = {
+				var userSession = {
 					state: `${state}-${user.id}`,
 					state: `${state}-${user.id}`,
 					access_token,
 					access_token,
-					user: {
-						id: user.id,
-						username: user.username,
-						discriminator: user.discriminator,
-						avatar: 'https://cdn.discordapp.com/' + ( user.avatar ? 
-							`avatars/${user.id}/${user.avatar}.` + 
-							( user.avatar.startsWith( 'a_' ) ? 'gif' : 'png' ) : 
-							`embed/avatars/${user.discriminator % 5}.png` ) + '?size=64',
-						locale: user.locale
-					},
-					guilds: {
-						count: guilds.length,
-						isMember: new Map(),
-						notMember: new Map()
-					}
+					user_id: user.id
 				};
 				};
+				sessionData.set(userSession.state, userSession);
+				/** @type {import('./util.js').Settings} */
+				var settings = ( settingsData.has(user.id) ? settingsData.get(user.id) : {
+					user: {},
+					guilds: {}
+				} );
+				settings.user.id = user.id;
+				settings.user.username = user.username;
+				settings.user.discriminator = user.discriminator;
+				settings.user.avatar = 'https://cdn.discordapp.com/' + ( user.avatar ? `avatars/${user.id}/${user.avatar}.` + ( user.avatar.startsWith( 'a_' ) ? 'gif' : 'png' ) : `embed/avatars/${user.discriminator % 5}.png` ) + '?size=64';
+				settings.user.locale = user.locale;
+				settings.guilds.count = guilds.length;
+				settings.guilds.isMember = new Map();
+				settings.guilds.notMember = new Map();
 				response.forEach( (guild, i) => {
 				response.forEach( (guild, i) => {
 					if ( guild ) {
 					if ( guild ) {
 						if ( guild === 'noMember' ) return;
 						if ( guild === 'noMember' ) return;
@@ -150,13 +150,13 @@ function dashboard_oauth(res, state, searchParams, lastGuild) {
 					}
 					}
 					else settings.guilds.notMember.set(guilds[i].id, guilds[i]);
 					else settings.guilds.notMember.set(guilds[i].id, guilds[i]);
 				} );
 				} );
-				settingsData.set(settings.state, settings);
+				settingsData.set(user.id, settings);
 				if ( searchParams.has('guild_id') ) {
 				if ( searchParams.has('guild_id') ) {
 					lastGuild = searchParams.get('guild_id') + '/settings';
 					lastGuild = searchParams.get('guild_id') + '/settings';
 				}
 				}
 				res.writeHead(302, {
 				res.writeHead(302, {
 					Location: ( lastGuild && /^\d+\/(?:settings|verification|rcscript|slash)(?:\/(?:\d+|new))?$/.test(lastGuild) ? `/guild/${lastGuild}` : '/' ),
 					Location: ( lastGuild && /^\d+\/(?:settings|verification|rcscript|slash)(?:\/(?:\d+|new))?$/.test(lastGuild) ? `/guild/${lastGuild}` : '/' ),
-					'Set-Cookie': [`wikibot="${settings.state}"; HttpOnly; Path=/`]
+					'Set-Cookie': [`wikibot="${userSession.state}"; HttpOnly; Path=/`]
 				});
 				});
 				return res.end();
 				return res.end();
 			}, error => {
 			}, error => {
@@ -179,12 +179,11 @@ function dashboard_oauth(res, state, searchParams, lastGuild) {
 /**
 /**
  * Reload the guild of a user
  * Reload the guild of a user
  * @param {import('http').ServerResponse} res - The server response
  * @param {import('http').ServerResponse} res - The server response
- * @param {String} state - The user state
+ * @param {import('./util.js').UserSession} userSession - The user session
  * @param {String} [returnLocation] - The return location
  * @param {String} [returnLocation] - The return location
  */
  */
-function dashboard_refresh(res, state, returnLocation = '/') {
-	var settings = settingsData.get(state);
-	return oauth.getUserGuilds(settings.access_token).then( guilds => {
+function dashboard_refresh(res, userSession, returnLocation = '/') {
+	return oauth.getUserGuilds(userSession.access_token).then( guilds => {
 		guilds = guilds.filter( guild => {
 		guilds = guilds.filter( guild => {
 			return ( guild.owner || hasPerm(guild.permissions, 'MANAGE_GUILD') );
 			return ( guild.owner || hasPerm(guild.permissions, 'MANAGE_GUILD') );
 		} ).map( guild => {
 		} ).map( guild => {
@@ -197,6 +196,7 @@ function dashboard_refresh(res, state, returnLocation = '/') {
 				userPermissions: guild.permissions
 				userPermissions: guild.permissions
 			};
 			};
 		} );
 		} );
+		var settings = settingsData.get(userSession.user_id);
 		sendMsg( {
 		sendMsg( {
 			type: 'getGuilds',
 			type: 'getGuilds',
 			member: settings.user.id,
 			member: settings.user.id,

+ 13 - 2
dashboard/util.js

@@ -38,9 +38,14 @@ got.get( `https://discord.com/api/v8/applications/${process.env.bot}/commands`,
 } );
 } );
 
 
 /**
 /**
- * @typedef Settings
+ * @typedef UserSession
  * @property {String} state
  * @property {String} state
  * @property {String} access_token
  * @property {String} access_token
+ * @property {String} user_id
+ */
+
+/**
+ * @typedef Settings
  * @property {User} user
  * @property {User} user
  * @property {Object} guilds
  * @property {Object} guilds
  * @property {Number} guilds.count
  * @property {Number} guilds.count
@@ -59,6 +64,7 @@ got.get( `https://discord.com/api/v8/applications/${process.env.bot}/commands`,
 
 
 /**
 /**
  * @typedef Guild
  * @typedef Guild
+ * @extends PartialGuild
  * @property {String} id
  * @property {String} id
  * @property {String} name
  * @property {String} name
  * @property {String} acronym
  * @property {String} acronym
@@ -87,6 +93,11 @@ got.get( `https://discord.com/api/v8/applications/${process.env.bot}/commands`,
  * @property {Boolean} lower
  * @property {Boolean} lower
  */
  */
 
 
+/**
+ * @type {Map<String, UserSession>}
+ */
+const sessionData = new Map();
+
 /**
 /**
  * @type {Map<String, Settings>}
  * @type {Map<String, Settings>}
  */
  */
@@ -347,4 +358,4 @@ function hasPerm(all = 0, ...permission) {
 	} ).every( perm => perm );
 	} ).every( perm => perm );
 }
 }
 
 
-module.exports = {got, db, slashCommands, settingsData, sendMsg, addWidgets, createNotice, escapeText, hasPerm};
+module.exports = {got, db, slashCommands, sessionData, settingsData, sendMsg, addWidgets, createNotice, escapeText, hasPerm};