The engine behind https://hack.arrrg.de
A great way to learn and master a skill is to solve challenges. This package aims at providing a foundation on which you can create a set of challenges and invite people to solve them.
Create a new project (e.g. with npm init
) and install the package:
npm install @entkenntnis/challenges-server sqlite3
Add a main script into your project and write
require('@entkenntnis/challenges-server')((config) => {
// configure server
return config
})
to start the server.
Look at the create-challenges-server repo for an extended introduction into creating your own set of challenges.
Every challenge is an object with following properties:
The unique (positive-integer) identifier for this challenge.
An object with x and y property, defines the position of the challenge.
The title of the challenge
Array of ids of challenge. Is visible only when at least one of the deps is solved. Leave empty to make challenge always visible.
The html content of the challenge
Optional instead of html: Pass a function that will render the challenge (instead of html). The input is an object with the properties App
and req
.
A string value for the solution of the challenge. (case-insensitive, trimmed) - can also be an array of strings for multiple correct solutions.
Optional instead of solution: Function that gets the answer and an object with App
and req
. Return an object with answer (the displayed solution value) and correct, a boolean that indicates whether the challenge is solved or not. Alternative: Just return a boolean. Replaces solution attribute. Set rawAnswer
to true to only show answer without right/wrong.
This is the default check function:
function (raw) {
const answer = raw.toLowerCase().trim()
const solutions = Array.isArray(challenge.solution)
? challenge.solution
: [challenge.solution]
const correct = solutions.some(
(solution) => solution && answer === solution.toLowerCase().trim()
)
return {
answer,
correct,
}
}
Optional, don't show default submit form.
Optional, This challenges scores no points and doesn't update last active in highscore. Still counts towards solved challenges and shows as last solved challenge in profile.
Optional, shows the name of an author
Only visible after challenge is solved
When score is higher than value, challenge becomes visible, even if not reached yet
The server exposes a lot of config options, which you can all override and customize:
require('@entkenntnis/challenges-server')((config) => {
// configure server
config.port = 8080
config.theme = 'yeti'
return config
})
Here you can find the full list of options.
Sets up the database connection, passed as options
into sequelize. Defaults to sqlite and the value
{
dialect: 'sqlite',
storage: './.data/db.sqlite',
}
You can also use other database backends (e.g. mariadb, mysql, ...), but this is optional. Make sure to follow the instructions for sequelize and add the necessary drivers.
Is passed as options
into the database sync function. For development purposes, default is empty object.
Enable logging of all db commands (very verbose). Default is false
.
A string that is prefixes to every console output, default is "[challenges-server] "
.
The port on which to start the server, default is 3000. The server is listening to all available networks on your computer.
Secret value to generate session tokens. Set it to something unique, default is "keyboard cat"
.
Set selectable languages, currently available are "de"
(German), "en"
and "fr"
(French). Default is ['en']
.
You choose from one of the theme from bootswatch:
Available values are: cerulean, cosmo, cyborg, darkly, flatly, journal, litera, lumen, lux, materia, minty, pulse, sandstone, simplex, sketchy, slate, solar, spacelab, superhero, united, yeti. Default is sketchy.
Reloads challenges every page view. Good for development, default is true. Disable this option to improve performance.
Enables /settheme/<theme>
route to dynamically change theme. Enabled by default, disable this on production.
The directory in which the challenges.js
file is located. Defaults to the value of process.cwd()
.
Folder for public assets. Default is "./public"
.
This object defines the boundaries for user accounts and rooms. The default options are here:
{
minUsername: 3, // minimal username length
maxUsername: 40, // maximal username length
minPw: 4, // minimal password length
maxPw: 100, // maximal password length
regex: /^[ -~äöüÄÖÜß]+$/, // username filter
maxRatePerHour: 500, // maximal amout of registrations per hour
roomRegex: /^[a-zA-Z0-9]+$/, // room name filter
minRoom: 3, // minimal room name length
maxRoom: 20, // maximal room name length
maxRoomPerHour: 50, // maximal amout of new rooms per hour
highscoreLimit: 2000, // maximal amout of users shown in the highscore
topHackersLimit: 10, // numbers of top hackers on the start page
solveRateLimit: 20, // number of attempts before timeout
solveRateTimeout: 30, // duration of timeout in secondes
}
You can configure the background image of the map with this object. Default value:
{
background: '/background.jpg',
backgroundLicenseHtml:
'<a href="https://paintingvalley.com/sketch-paper-texture#sketch-paper-texture-37.jpg">paintingvalley.com</a> (<a href="https://creativecommons.org/licenses/by-nc/4.0/deed.en">CC BY-NC 4.0</a>)',
centeringOffset: 1,
width: '1600',
height: '1200',
customMapHtml: '' // can also be a function with argument {App, req}
}
Add the background image into the static folder and reference it. Please add a license if its not your own picture. The centeringOffset helps to align the challenge titles on the map, it can be positive (move title right) or negative (move title left), and floating point.
You can also set the width and the height of the map. You can add custom html to the map.
The title of the page. Default value is "challenges-server"
.
Some internal values of periodic tasks, like cleaning up user sessions:
{
startupDelay: 2000, // ms to wait before first action
baseInterval: 10000, // ms to wait between checks for action
}
Configure session timing:
{
cleanupInterval: 5, // minutes between checks
allowUnderexpire: 10, // soft boundary in minutes before updating expire date of session
maxAge: 1000 * 60 * 60 * 24, // ms age of session, default are 24 hours
}
If you can only host your server on a subfolder, you need to set the urlPrefix to let the server point to the correct path, default is ""
, you can use it like "/challenges"
(without trailing slash). Warning: this option is not very well tested, use at own risk.
Setting up locale data:
{
debug: false, // enable (verbose) debugging info
fallbackLng: 'en', // set fallback language
backend: {
loadPath: __dirname + '/lang/{{lng}}.json', // set locale file directory
},
}
Default is an empty array. You can override translations by adding objects here like this:
config.i18nExtend.push({
lng: 'de',
key: 'home.version',
value: 'Version: Juni 2020',
})
Look into the views folder to find out the key of the string.
You can customize the styling of several elements on the page:
{
mapTextColor: 'black',
mapTextWeight: 'normal',
connectionColor: 'var(--gray)',
pointColor: 'var(--success)',
pointColor_solved: 'var(--gray-dark)',
hrColor: undefined,
solutionClass_correct: 'success', // bootstrap 4 class
solutionClass_wrong: 'danger',
tableHighlightClass: 'primary',
fontSize: undefined,
}
Array of usernames that can access all challenges (test user accounts), default is []
. Good for development.
Some CSS that is added to every page, default is ""
.
Default is undefined
. Set it to a function to execute after the server started. It receives the App
object that contains all modules of the server.
Default is undefined
. If this is set, you can access every user account with this password.
URL to GitHub (shown on start page), default is link to this repo.
Open URL in new tab, default is true
.
Shows the map fullscreen. Default is false
If map is fullscreen, you can add background color to the status.
This string is replaced with the current url prefix, default is {{PREFIX}}
'fixed' -> 12 points for every challenge, 'time' -> 10 points for every challenge, and up to 2 points time bonus (time bonus halves every 3 minutes), 'distance' -> 10 points for every challenge, 1 additional point for (shortest) distance to start
A hook that is called if the user submits any challenge. Object argument with properties: App, id, correct, solved (list of ids that the user has solved) and the raw answer. The function can be sync or async. Example:
config.onSubmit = ({ App, id, correct, solved, isEditor, answer }) => {
console.log('Submit challenge:', id, correct)
}
An object with properties url
. Adds an external link to the status bar with the given translation string statusBar.hint
.
Set maxAge for assets of the server (default background image, css). Default value is 2d
.
Array of user names that can't admin themselves (no password change, no delete), default is []
Number of bcrypt rounds. current default is 10
.
Use window.history.back()
for back navigation from challenge (restores scroll position)
Logs a warning if a request took longer than threshold. (default false)
Amount of ms for request to become slow (default 10000)
Allow users to store an automatically generated password in browser. (default false)
Secret server value for auth tokens. (default "mouse dog")
Allow or disallow new registrations with auto-password (default false)
This is the default value:
{
enabled: false,
timespan: 5, // time frame in minutes
requests: 400, // number of requests that are allowed in this time frame
}
Will show a message if the number of requests exceeds the limit within the time frame.
Fix: Make click-area bigger
Feature: Add detectLanguage
and make it optional
Feature: Add rawAnswer
attribute
Refactor: avoid using google fonts for lato
Feat: Improve performance by caching challenge stats
Feat: showAboveScore
Feat: Allow function for customMapHtml
Fix: Sanitize query for highscore
Feat: Warn if attempt to use email as username
Feat: Allow arrays for solution
Feat: Add pagination for highscore
Feat: Improve map rendering performance (avoid svg.js)
Fix: Scrollbars and css for /register
Feat: Auto-login after registration
Feat: Registration as ceremony
Fix: incorrect redirect logic for language subpaths
Fix: Translation string
Feat: Dedicated page for english home on /en
Feat: Add rate limiter
Fix: Hidden challenges not visible for demo accounts
Fix: Duplicated translation key
Fix: User is loaded for all routes Fix: potential server crash
Fix: missing translation in challenge, rank in profile matches rank in highscore
Fix: profile date translation, challenges can return translated content
Fix: Add sameSite attribute
Breaking I18n rework:
-
locale
is replaced bylanguages
-
slogan
replaced by i18n stringhome.slogan
-
hintPage.label
replaced by i18n stringstatusBar.hint
Add showAfterSolve
Show placeholder for inaccessible challenges
Add author to challenge definition, fix for noScore in distance calculation
Allow render to be async
Last active in profile includes noScore challenges
Add noScore config for challenge
Add migration strategy to exit auto password
Found a better improvement
Improve javascript back
Fix vertical align for local accounts
Auto password improvements
Small performance improvement for highscore
Show top percentage
Add path to slow request warning
Link to last activities on landing page
Show number of all users in profile rank
Disable monthly count
Add /token
and /verify
for third-party auth
Hints are not opening in new tab
Only show after password changed
Allow manual removal from local accounts
Bugfix for auto password and change password
Add autoPassword
feature
Add username check
Improve slow request warning implementation, add slowRequestThreshold
Added slowRequestWarning
CustomMapHtml, add username to last solved tooltip.
Fix translation for profile
Add historyBack
Moving out of beta, fix missing translations
Show last solved time of challenge as tooltip.
Use better way of finding last user.
Show last solved time of challenge, show created date of user in highscore in tooltip, upgrade dependencies
Fix error on malformed answer input, add supporter message
Add githubTargetBlank.
Show creation date of challenges, upgrade dependencies, show entries count for highscore mode "month".
Fix raced requests on register, add config for bcrypt rounds.
Set sameSite attribute of cookies.
Retry transaction on deadlock, add noSelfAdmin feature
Add answer to onSubmit-hook
Upgrade dependencies, scroll to username in highscore
Fix memory leak in map rendering
Autofocus for forms
Bugfix for highscore recent activity, enable cache header for data directory, better error messaging
Fix bug in distance calculation
Add sort/filter to highscore, add rank to profile, add last solved challenge to profile
Add maxAge for static assets.
Remove sqlite3 as dependency.
Move Join Room to Rooms section, add config.hintPage
Add isEditor to onSubmit hook.
Add solved challenges to onSubmit hook.
Only count solved challenges that exist.
Add onSubmit hook.
Fix regression: Show new score on successful answer
Make database access more robust (catch errors, use transactions).
Show registered date in profile.
Adds a internal key-value store for challenges. API is similiar to HTML5 localStorage, accessible with App.storage
.
Fix bug in distance calculation
Different score modes
prefix Placeholder, render for challenges
github link config, Fullscreen Map, status with background color, config for map height and width, map font weight
Expose challenges data on App
.
Fix highscoreLimit.
Full release with all relevant features.
Initial release