Merge pull request #101 from AlecM33/strict-content-security

stricter content security policy
This commit is contained in:
Alec
2022-02-26 00:32:31 -05:00
committed by GitHub
15 changed files with 153 additions and 60 deletions

View File

@@ -36,7 +36,7 @@ export class DeckStateManager {
if (option) {
this.customRoleOptions.splice(this.customRoleOptions.indexOf(option), 1);
localStorage.setItem('play-werewolf-custom-roles', JSON.stringify(this.customRoleOptions.concat(this.deck.filter(card => card.custom === true))));
toast('"' + name + '" deleted.', 'error', true, true, 3);
toast('"' + name + '" deleted.', 'error', true, true, 'short');
}
}
@@ -88,7 +88,7 @@ export class DeckStateManager {
loadCustomRolesFromFile (file, updateRoleListFunction, loadDefaultCardsFn, showIncludedCardsFn) {
const reader = new FileReader();
reader.onerror = (e) => {
toast(reader.error.message, 'error', true, true, 5);
toast(reader.error.message, 'error', true, true, 'medium');
};
reader.onload = (e) => {
let string;
@@ -100,13 +100,19 @@ export class DeckStateManager {
if (validateCustomRoleCookie(string)) {
this.customRoleOptions = JSON.parse(string); // we know it is valid JSON from the validate function
ModalManager.dispelModal('upload-custom-roles-modal', 'modal-background');
toast('Roles imported successfully', 'success', true, true, 3);
toast('Roles imported successfully', 'success', true, true, 'short');
localStorage.setItem('play-werewolf-custom-roles', JSON.stringify(this.customRoleOptions));
updateRoleListFunction(this, document.getElementById('deck-select'));
// loadDefaultCardsFn(this);
// showIncludedCardsFn(this);
} else {
toast('Invalid formatting. Make sure you import the file as downloaded from this page.', 'error', true, true, 5);
toast(
'Invalid formatting. Make sure you import the file as downloaded from this page.',
'error',
true,
true,
'medium'
);
}
};
reader.readAsText(file);

View File

@@ -132,7 +132,7 @@ export class GameCreationStepManager {
button.classList.remove('submitted');
button.addEventListener('click', this.steps['4'].forwardHandler);
if (e.status === 429) {
toast('You\'ve sent this request too many times.', 'error', true, true, 6);
toast('You\'ve sent this request too many times.', 'error', true, true, 'medium');
}
});
}
@@ -280,23 +280,23 @@ function renderRoleSelectionStep (game, containerId, step, deckManager) {
if (fileList.length > 0) {
const file = fileList[0];
if (file.size > 1000000) {
toast('Your file is too large (max 1MB)', 'error', true, true, 5);
toast('Your file is too large (max 1MB)', 'error', true, true, 'medium');
return;
}
if (file.type !== 'text/plain') {
toast('Your file must be a text file', 'error', true, true, 5);
toast('Your file must be a text file', 'error', true, true, 'medium');
return;
}
deckManager.loadCustomRolesFromFile(file, updateCustomRoleOptionsList, loadDefaultCards, showIncludedCards);
} else {
toast('You must upload a text file', 'error', true, true, 5);
toast('You must upload a text file', 'error', true, true, 'medium');
}
};
const clickHandler = () => {
const actions = document.getElementById('custom-role-actions');
if (actions.style.display !== 'none') {
if (window.getComputedStyle(actions, null).display !== 'none') {
actions.style.display = 'none';
} else {
actions.style.display = 'block';
@@ -563,7 +563,7 @@ function initializeRemainingEventListeners (deckManager) {
if (!deckManager.getCustomRoleOption(name) && !deckManager.getCard(name)) { // confirm there is no existing custom role with the same name
processNewCustomRoleSubmission(name, description, team, deckManager, false);
} else {
toast('There is already a role with this name', 'error', true, true, 3);
toast('There is already a role with this name', 'error', true, true, 'short');
}
} else {
const option = deckManager.getCustomRoleOption(deckManager.currentlyEditingRoleName);
@@ -573,7 +573,7 @@ function initializeRemainingEventListeners (deckManager) {
if (!deckManager.getCustomRoleOption(name) && !deckManager.getCard(name)) {
processNewCustomRoleSubmission(name, description, team, deckManager, true, option);
} else {
toast('There is already a role with this name', 'error', true, true, 3);
toast('There is already a role with this name', 'error', true, true, 'short');
}
}
}
@@ -653,7 +653,7 @@ function addCustomRoleEventListeners (deckManager, select) {
if (!deckManager.getCard(name)) {
deckManager.addToDeck(name);
const cardEl = constructCompactDeckBuilderElement(deckManager.getCard(name), deckManager);
toast('"' + name + '" made available below.', 'success', true, true, 4);
toast('"' + name + '" made available below.', 'success', true, true, 'medium');
if (deckManager.getCard(name).team === globals.ALIGNMENT.GOOD) {
document.getElementById('deck-good').appendChild(cardEl);
} else {
@@ -661,7 +661,7 @@ function addCustomRoleEventListeners (deckManager, select) {
}
updateCustomRoleOptionsList(deckManager, select);
} else {
toast('"' + select.value + '" already included.', 'error', true, true, 3);
toast('"' + select.value + '" already included.', 'error', true, true, 'short');
}
}
};

View File

@@ -53,7 +53,7 @@ export const HTMLFragments = {
<button id='role-info-button' class='app-button'>View Role Info <img src='/images/info.svg'/></button>
</div>
</div>
<div id='game-role' style='display:none'>
<div id='game-role'>
<h4 id='role-name'></h4>
<img alt='role' id='role-image'/>
<p id='role-description'></p>
@@ -81,8 +81,8 @@ export const HTMLFragments = {
<div id='game-player-list'></div>
</div>`,
MODERATOR_GAME_VIEW:
`<div id='transfer-mod-modal-background' class='modal-background' style='display: none'></div>
<div tabindex='-1' id='transfer-mod-modal' class='modal' style='display: none'>
`<div id='transfer-mod-modal-background' class='modal-background'></div>
<div tabindex='-1' id='transfer-mod-modal' class='modal'>
<h3>Transfer Mod Powers &#128081;</h3>
<div id='transfer-mod-modal-content'></div>
<div class='modal-button-container'>
@@ -116,8 +116,8 @@ export const HTMLFragments = {
</div>
</div>`,
TEMP_MOD_GAME_VIEW:
`<div id='transfer-mod-modal-background' class='modal-background' style='display: none'></div>
<div id='transfer-mod-modal' class='modal' style='display: none'>
`<div id='transfer-mod-modal-background' class='modal-background'></div>
<div id='transfer-mod-modal' class='modal'>
<form id='transfer-mod-form'>
<div id='transfer-mod-form-content'>
<h3>Transfer Mod Powers &#128081;</h3>
@@ -139,7 +139,7 @@ export const HTMLFragments = {
<button id='role-info-button' class='app-button'>View Role Info <img src='/images/info.svg'/></button>
</div>
</div>
<div id='game-role' style='display:none'>
<div id='game-role'>
<h4 id='role-name'></h4>
<img alt='role' id='role-image'/>
<p id='role-description'></p>
@@ -249,7 +249,7 @@ export const HTMLFragments = {
<span class="hamburger-inner"></span>
</span>
</button>
<div id="custom-role-actions" style="display:none">
<div id="custom-role-actions">
<div tabindex="0" class="custom-role-action" id="custom-roles-export">Export</div>
<div tabindex="0" class="custom-role-action" id="custom-roles-import">Import</div>
</div>

View File

@@ -1,5 +1,3 @@
import { globals } from '../config/globals.js';
export const toast = (message, type, positionAtTop = true, dispelAutomatically = true, duration = null) => {
if (message && type) {
buildAndInsertMessageElement(message, type, positionAtTop, dispelAutomatically, duration);
@@ -8,35 +6,49 @@ export const toast = (message, type, positionAtTop = true, dispelAutomatically =
function buildAndInsertMessageElement (message, type, positionAtTop, dispelAutomatically, duration) {
cancelCurrentToast();
let backgroundColor, border;
const position = positionAtTop ? 'top:2rem;' : 'bottom: 90px;';
const messageEl = document.createElement('div');
messageEl.classList.add('info-message');
const positionClass = positionAtTop ? 'toast-top' : 'toast-bottom';
messageEl.classList.add(positionClass);
switch (type) {
case 'warning':
backgroundColor = '#fff5b1';
border = '3px solid #c7c28a';
messageEl.classList.add('toast-warning');
break;
case 'error':
backgroundColor = '#fdaeb7';
border = '3px solid #c78a8a';
messageEl.classList.add('toast-error');
break;
case 'success':
backgroundColor = '#bef5cb';
border = '3px solid #8ac78a;';
messageEl.classList.add('toast-success');
break;
}
const durationInSeconds = duration ? duration + 's' : globals.TOAST_DURATION_DEFAULT + 's';
let animation = '';
if (dispelAutomatically) {
animation = 'animation:fade-in-slide-down-then-exit ' + durationInSeconds + ' ease normal forwards';
} else {
animation = 'animation:fade-in-slide-down ' + durationInSeconds + ' ease normal forwards';
switch (duration) {
case null:
case undefined:
messageEl.classList.add('toast-medium');
break;
case 'short':
messageEl.classList.add('toast-short');
break;
case 'medium':
messageEl.classList.add('toast-medium');
break;
case 'long':
messageEl.classList.add('toast-long');
break;
default:
break;
}
const messageEl = document.createElement('div');
if (dispelAutomatically) {
messageEl.classList.add('toast-dispel-automatically');
} else {
messageEl.classList.add('toast-not-dispelled-automatically');
}
messageEl.setAttribute('id', 'current-info-message');
messageEl.setAttribute('style', 'background-color:' + backgroundColor + ';' + 'border:' + border + ';' + position + animation);
messageEl.setAttribute('class', 'info-message');
messageEl.innerText = message;
document.body.prepend(messageEl);
}

View File

@@ -55,7 +55,7 @@ function syncWithGame (stateBucket, gameTimerManager, gameStateRenderer, timerWo
document.querySelector('.spinner-container')?.remove();
document.querySelector('.spinner-background')?.remove();
document.getElementById('game-content').innerHTML = HTMLFragments.INITIAL_GAME_DOM;
toast('You are connected.', 'success', true, true, 2);
toast('You are connected.', 'success', true, true, 'short');
processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker);
}
});
@@ -142,7 +142,7 @@ function displayClientInfo (name, userType) {
function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager) {
socket.on(globals.EVENTS.PLAYER_JOINED, (player, gameIsFull) => {
toast(player.name + ' joined!', 'success', false, true, 3);
toast(player.name + ' joined!', 'success', false, true, 'short');
stateBucket.currentGameState.people.push(player);
stateBucket.currentGameState.isFull = gameIsFull;
gameStateRenderer.renderLobbyPlayers();
@@ -163,7 +163,7 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
socket.on(globals.EVENTS.PLAYER_LEFT, (player) => {
removeStartGameFunctionalityIfPresent(gameStateRenderer);
toast(player.name + ' has left!', 'error', false, true, 3);
toast(player.name + ' has left!', 'error', false, true, 'short');
const index = stateBucket.currentGameState.people.findIndex(person => person.id === player.id);
if (index >= 0) {
stateBucket.currentGameState.people.splice(
@@ -221,7 +221,7 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
if (killedPerson) {
killedPerson.out = true;
if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
toast(killedPerson.name + ' killed.', 'success', true, true, 6);
toast(killedPerson.name + ' killed.', 'success', true, true, 'medium');
gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(stateBucket.currentGameState.status === globals.STATUS.ENDED);
} else {
if (killedPerson.id === stateBucket.currentGameState.client.id) {
@@ -230,9 +230,9 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
}
gameStateRenderer.updatePlayerCardToKilledState();
toast('You have been killed!', 'warning', true, true, 6);
toast('You have been killed!', 'warning', true, true, 'medium');
} else {
toast(killedPerson.name + ' was killed!', 'warning', true, true, 6);
toast(killedPerson.name + ' was killed!', 'warning', true, true, 'medium');
}
if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true);
@@ -250,13 +250,13 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
revealedPerson.gameRole = revealData.gameRole;
revealedPerson.alignment = revealData.alignment;
if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
toast(revealedPerson.name + ' revealed.', 'success', true, true, 6);
toast(revealedPerson.name + ' revealed.', 'success', true, true, 'medium');
gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(stateBucket.currentGameState.status === globals.STATUS.ENDED);
} else {
if (revealedPerson.id === stateBucket.currentGameState.client.id) {
toast('Your role has been revealed!', 'warning', true, true, 6);
toast('Your role has been revealed!', 'warning', true, true, 'medium');
} else {
toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', true, true, 6);
toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', true, true, 'medium');
}
if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true);

View File

@@ -51,9 +51,9 @@ const joinHandler = (e) => {
document.getElementById('submit-new-name').classList.remove('submitted');
document.getElementById('submit-new-name').setAttribute('value', 'Join Game');
if (res.status === 404) {
toast('This game was not found.', 'error', true, true, 8);
toast('This game was not found.', 'error', true, true, 'long');
} else if (res.status === 400) {
toast('This name is already taken.', 'error', true, true, 8);
toast('This name is already taken.', 'error', true, true, 'long');
} else if (res.status >= 500) {
toast(
'The server is experiencing problems. Please try again later',
@@ -63,7 +63,7 @@ const joinHandler = (e) => {
}
});
} else {
toast('Name must be between 1 and 30 characters.', 'error', true, true, 8);
toast('Name must be between 1 and 30 characters.', 'error', true, true, 'long');
}
};

View File

@@ -65,6 +65,53 @@ h3 {
margin: 0.5em 0;
}
textarea {
resize: none;
}
.toast-top {
top: 2rem;
}
.toast-bottom {
bottom: 90px;
}
.toast-success {
background-color: #bef5cb;
border: 3px solid #8ac78a;
}
.toast-warning {
background-color: #fff5b1;
border: 3px solid #c7c28a;
}
.toast-error {
background-color: #fdaeb7;
border: 3px solid #c78a8a;
}
.toast-dispel-automatically {
animation: fade-in-slide-down-then-exit ease normal forwards;
}
.toast-not-dispelled-automatically {
animation: fade-in-slide-down ease normal forwards;
}
.toast-short {
animation-duration: 3s;
}
.toast-medium {
animation-duration: 5s;
}
.toast-long {
animation-duration: 8s;
}
#footer {
bottom: 0;
width: 100%;
@@ -78,10 +125,14 @@ h3 {
margin-top: 4em;
margin-bottom: 0.5em;
}
#footer a img {
#footer a:not([href='https://www.buymeacoffee.com/alecm33']) img {
width: 32px;
}
#footer a[href='https://www.buymeacoffee.com/alecm33'] img {
width: 200px;
}
#footer a {
color: #f7f7f7;
text-decoration: none;

View File

@@ -166,6 +166,7 @@
}
#custom-role-actions {
display: none;
color: whitesmoke;
position: absolute;
top: 38px;

View File

@@ -219,10 +219,10 @@ h1 {
}
#game-role {
display: none;
position: relative;
border: 5px solid transparent;
background-color: #e7e7e7;
display: flex;
flex-direction: column;
cursor: pointer;
justify-content: space-between;

View File

@@ -2,6 +2,11 @@
border-left: 5px solid #b1afcd;
animation: entrance 0.5s forwards;
transform-origin: center;
display: block;
}
#player-new-name {
max-width: 17em;
}
#game-parameters div:nth-child(1) {

View File

@@ -14,9 +14,11 @@
font-family: sans-serif;
flex-direction: column;
padding: 1em;
display: none;
}
.modal-background {
display: none;
position: fixed;
top: 0;
left: 0;
@@ -50,7 +52,6 @@
}
#custom-role-info-modal {
display: flex;
color: #d7d7d7;
text-align: left;
font-family: signika-negative, sans-serif;

View File

@@ -1,6 +1,6 @@
const template =
`<div id="modal-background" class="modal-background" style="display: none"></div>
<div tabindex="-1" id="role-modal" class="modal" style="display: none">
`<div id="modal-background" class="modal-background"></div>
<div tabindex="-1" id="role-modal" class="modal">
<form id="role-form">
<div>
<label for="role-name">Role Name</label>
@@ -15,7 +15,7 @@ const template =
</div>
<div>
<label for="role-description">Description</label>
<textarea style="resize:none" id="role-description" rows="4" cols="1" placeholder="Describe your role..." required></textarea>
<textarea id="role-description" rows="4" cols="1" placeholder="Describe your role..." required></textarea>
</div>
<div class="modal-button-container">
<button id="close-modal-button" class="cancel app-button">Close</button>
@@ -23,7 +23,7 @@ const template =
</div>
</form>
</div>
<div tabindex="-1" id="upload-custom-roles-modal" class="modal" style="display:none">
<div tabindex="-1" id="upload-custom-roles-modal" class="modal">
<h3>Import Custom Roles</h3>
<form id="upload-custom-roles-form">
<input type="file" id="upload-custom-roles" name="Upload Custom Roles" accept="text/plain"/>
@@ -33,7 +33,7 @@ const template =
</div>
</form>
</div>
<div tabindex="-1" id="custom-role-info-modal" class="modal" style="display:none">
<div tabindex="-1" id="custom-role-info-modal" class="modal">
<h3 id="custom-role-info-modal-name"></h3>
<div id="custom-role-info-modal-alignment"></div>
<div id="custom-role-info-modal-description"></div>

View File

@@ -41,7 +41,9 @@
</form>
</div>
<footer id="footer">
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="alecm33" data-color="#333243" data-emoji="" data-font="Lato" data-text="Buy me a coffee" data-outline-color="#ffffff" data-font-color="#ffffff" data-coffee-color="#FFDD00" ></script>
<a href="https://www.buymeacoffee.com/alecm33"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=alecm33&button_colour=333243&font_colour=ffffff&font_family=Lato&outline_colour=b1afcd&coffee_colour=b1afcd" /></a>
<div>
<a href="https://github.com/AlecM33/Werewolf"><img src='/images/GitHub-Mark-Light-120px-plus.png'/></a>
<a href="mailto:play.werewolf.contact@gmail.com?Subject=Werewolf App" target="_top"><img src='/images/email.svg'/></a>

View File

@@ -148,6 +148,11 @@ class GameManager {
const game = this.activeGameRunner.activeGames[accessCode];
if (game) {
game.status = globals.STATUS.ENDED;
if (this.activeGameRunner.timerThreads[accessCode]) {
this.logger.trace('KILLING TIMER PROCESS FOR ENDED GAME ' + accessCode);
this.activeGameRunner.timerThreads[accessCode].kill();
delete this.activeGameRunner.timerThreads[accessCode];
}
for (const person of game.people) {
person.revealed = true;
}

View File

@@ -3,6 +3,7 @@ const http = require('http');
const https = require('https');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const ServerBootstrapper = {
processCLIArgs: () => {
@@ -64,6 +65,15 @@ const ServerBootstrapper = {
next();
}
});
app.use(function (req, res, next) {
const nonce = crypto.randomBytes(16).toString('base64');
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; font-src 'self' https://fonts.gstatic.com/; img-src 'self' https://img.buymeacoffee.com;" +
" script-src 'self' https://cdnjs.buymeacoffee.com; style-src 'self' https://cdnjs.buymeacoffee.com https://fonts.googleapis.com/ 'nonce-" + nonce + "'; frame-src 'self'"
);
next();
});
}
return main;