reorganize routes

add adminapi
remove body-parser in favor of express methods
add id to captchas
add testmodule for matrix-synapse to login with ppfun credentials (just
a test)
This commit is contained in:
HF 2022-01-10 13:54:07 +01:00
parent ee62c57b34
commit 2bc1aa9591
38 changed files with 1126 additions and 770 deletions

4
API.md
View File

@ -1,9 +1,9 @@
# API Websocket # API Websocket
This websocket provides unlimited access to many functions of the site, it is used for Discord Chat Bridge and Minecraft Bridge. This websocket provides unlimited access to many functions of the site, it is used for Discord Chat Bridge and similar stuff.
Websocket url: Websocket url:
`https://[old.]pixelplanet.fun/mcws` `https://[dev.]pixelplanet.fun/mcws`
Connection just possible with header: Connection just possible with header:

View File

@ -64,30 +64,6 @@ msgstr "Dein Land ist temporär stummgeschaltet"
msgid "Stop flooding." msgid "Stop flooding."
msgstr "Stoppe zu spamen." msgstr "Stoppe zu spamen."
#: src/ssr-components/Globe.jsx:44
msgid "Double click on globe to go back."
msgstr "Doppelklick um zurück zu gehen."
#: src/ssr-components/Globe.jsx:45
msgid "Loading..."
msgstr "Lade..."
#: src/ssr-components/Globe.jsx:51
msgid "PixelPlanet.Fun 3DGlobe"
msgstr "PixelPlanet.Fun 3D Globus"
#: src/ssr-components/Globe.jsx:52
msgid "A 3D globe of our whole map"
msgstr "Ein interaktvier 3D Globus unserer gesamten Karte"
#: src/ssr-components/Main.jsx:70
msgid "PixelPlanet.fun"
msgstr "PixelPlanet.Fun"
#: src/ssr-components/Main.jsx:72
msgid "Place color pixels on an map styled canvas with other players online"
msgstr "Zeichne mit farbigen Pixel auf einer Weltkarte mit anderen Spielern"
#: src/routes/reset_password.js:41 #: src/routes/reset_password.js:41
msgid "You sent an empty password or invalid data :(" msgid "You sent an empty password or invalid data :("
msgstr "Du hast ein ungültiges Passwort oder Daten gesendet :(" msgstr "Du hast ein ungültiges Passwort oder Daten gesendet :("
@ -120,6 +96,30 @@ msgstr ""
"Dieser Passwort Wiederherstellungslink ist falsch oder abgelaufen, bitte " "Dieser Passwort Wiederherstellungslink ist falsch oder abgelaufen, bitte "
"beantrage einen neuen (Hinweis: Du kannst diese Links nur einmal verwenden)" "beantrage einen neuen (Hinweis: Du kannst diese Links nur einmal verwenden)"
#: src/ssr-components/Globe.jsx:44
msgid "Double click on globe to go back."
msgstr "Doppelklick um zurück zu gehen."
#: src/ssr-components/Globe.jsx:45
msgid "Loading..."
msgstr "Lade..."
#: src/ssr-components/Globe.jsx:51
msgid "PixelPlanet.Fun 3DGlobe"
msgstr "PixelPlanet.Fun 3D Globus"
#: src/ssr-components/Globe.jsx:52
msgid "A 3D globe of our whole map"
msgstr "Ein interaktvier 3D Globus unserer gesamten Karte"
#: src/ssr-components/Main.jsx:70
msgid "PixelPlanet.fun"
msgstr "PixelPlanet.Fun"
#: src/ssr-components/Main.jsx:72
msgid "Place color pixels on an map styled canvas with other players online"
msgstr "Zeichne mit farbigen Pixel auf einer Weltkarte mit anderen Spielern"
#: src/core/mail.js:65 #: src/core/mail.js:65
#, javascript-format #, javascript-format
msgid "" msgid ""
@ -243,6 +243,38 @@ msgstr "PixelPlanet.Fun Passwort Wiederherstellung"
msgid "Reset your password here" msgid "Reset your password here"
msgstr "Setze hier dein Passwort zurück" msgstr "Setze hier dein Passwort zurück"
#: src/routes/api/captcha.js:22
msgid "No captcha text given"
msgstr "Keinen Captcha Text eingegeben"
#: src/routes/api/captcha.js:36
msgid "You took too long, try again."
msgstr "Do hast zu lange benötigt."
#: src/routes/api/captcha.js:42
msgid "You failed your captcha"
msgstr "Captcha Text ist falsch"
#: src/routes/api/captcha.js:48
msgid "Unknown Captcha Error"
msgstr "Unbekannter Captcha Fehler"
#: src/routes/api/captcha.js:55
msgid "Server error occured"
msgstr "Server Fehler"
#: src/routes/api/modtools.js:51
msgid "You are not logged in"
msgstr "Du bist nicht angemeldet."
#: src/routes/api/modtools.js:63
msgid "You are not allowed to access this page"
msgstr "Du hast keinen Zugriff zu diese Seite"
#: src/routes/api/modtools.js:129
msgid "Just admins can do that"
msgstr "Nur Administratoren können dies tun"
#: src/utils/validation.js:18 #: src/utils/validation.js:18
msgid "Email can't be empty." msgid "Email can't be empty."
msgstr "E-Mail Feld kann nicht leer sein." msgstr "E-Mail Feld kann nicht leer sein."
@ -291,26 +323,6 @@ msgstr "Passwort muss mindestens 6 Zeichen lang sein."
msgid "Password must be shorter than 60 characters." msgid "Password must be shorter than 60 characters."
msgstr "Passwort muss kürzer als 60 Zeichen sein." msgstr "Passwort muss kürzer als 60 Zeichen sein."
#: src/routes/api/captcha.js:22
msgid "No captcha text given"
msgstr "Keinen Captcha Text eingegeben"
#: src/routes/api/captcha.js:36
msgid "You took too long, try again."
msgstr "Do hast zu lange benötigt."
#: src/routes/api/captcha.js:42
msgid "You failed your captcha"
msgstr "Captcha Text ist falsch"
#: src/routes/api/captcha.js:48
msgid "Unknown Captcha Error"
msgstr "Unbekannter Captcha Fehler"
#: src/routes/api/captcha.js:55
msgid "Server error occured"
msgstr "Server Fehler"
#: src/routes/api/auth/register.js:31 #: src/routes/api/auth/register.js:31
msgid "E-Mail already in use." msgid "E-Mail already in use."
msgstr "E-Mail wird bereits verwendet." msgstr "E-Mail wird bereits verwendet."

View File

@ -53,6 +53,44 @@ msgstr ""
msgid "Stop flooding." msgid "Stop flooding."
msgstr "" msgstr ""
#: src/routes/reset_password.js:40
msgid "You sent an empty password or invalid data :("
msgstr ""
#: src/routes/reset_password.js:52
msgid "This password-reset link isn't valid anymore :("
msgstr ""
#: src/routes/reset_password.js:63
msgid "Your passwords do not match :("
msgstr ""
#: src/routes/reset_password.js:78
msgid "User doesn't exist in our database :("
msgstr ""
#: src/routes/reset_password.js:90
msgid "Passowrd successfully changed."
msgstr ""
#: src/routes/reset_password.js:109
msgid "Invalid url :( Please check your mail again."
msgstr ""
#: src/routes/reset_password.js:122
msgid ""
"This passwort reset link is wrong or already expired, please request a new "
"one (Note: you can use those links just once)"
msgstr ""
#: src/ssr-components/Main.jsx:70
msgid "PixelPlanet.fun"
msgstr ""
#: src/ssr-components/Main.jsx:72
msgid "Place color pixels on an map styled canvas with other players online"
msgstr ""
#: src/ssr-components/Globe.jsx:44 #: src/ssr-components/Globe.jsx:44
msgid "Double click on globe to go back." msgid "Double click on globe to go back."
msgstr "" msgstr ""
@ -69,44 +107,6 @@ msgstr ""
msgid "A 3D globe of our whole map" msgid "A 3D globe of our whole map"
msgstr "" msgstr ""
#: src/ssr-components/Main.jsx:70
msgid "PixelPlanet.fun"
msgstr ""
#: src/ssr-components/Main.jsx:72
msgid "Place color pixels on an map styled canvas with other players online"
msgstr ""
#: src/routes/reset_password.js:41
msgid "You sent an empty password or invalid data :("
msgstr ""
#: src/routes/reset_password.js:53
msgid "This password-reset link isn't valid anymore :("
msgstr ""
#: src/routes/reset_password.js:64
msgid "Your passwords do not match :("
msgstr ""
#: src/routes/reset_password.js:79
msgid "User doesn't exist in our database :("
msgstr ""
#: src/routes/reset_password.js:91
msgid "Passowrd successfully changed."
msgstr ""
#: src/routes/reset_password.js:110
msgid "Invalid url :( Please check your mail again."
msgstr ""
#: src/routes/reset_password.js:123
msgid ""
"This passwort reset link is wrong or already expired, please request a new "
"one (Note: you can use those links just once)"
msgstr ""
#: src/core/mail.js:65 #: src/core/mail.js:65
#, javascript-format #, javascript-format
msgid "" msgid ""
@ -218,26 +218,6 @@ msgstr ""
msgid "Reset your password here" msgid "Reset your password here"
msgstr "" msgstr ""
#: src/routes/api/captcha.js:22
msgid "No captcha text given"
msgstr ""
#: src/routes/api/captcha.js:36
msgid "You took too long, try again."
msgstr ""
#: src/routes/api/captcha.js:42
msgid "You failed your captcha"
msgstr ""
#: src/routes/api/captcha.js:48
msgid "Unknown Captcha Error"
msgstr ""
#: src/routes/api/captcha.js:55
msgid "Server error occured"
msgstr ""
#: src/utils/validation.js:18 #: src/utils/validation.js:18
msgid "Email can't be empty." msgid "Email can't be empty."
msgstr "" msgstr ""
@ -286,6 +266,38 @@ msgstr ""
msgid "Password must be shorter than 60 characters." msgid "Password must be shorter than 60 characters."
msgstr "" msgstr ""
#: src/routes/api/captcha.js:22
msgid "No captcha text given"
msgstr ""
#: src/routes/api/captcha.js:36
msgid "You took too long, try again."
msgstr ""
#: src/routes/api/captcha.js:42
msgid "You failed your captcha"
msgstr ""
#: src/routes/api/captcha.js:48
msgid "Unknown Captcha Error"
msgstr ""
#: src/routes/api/captcha.js:55
msgid "Server error occured"
msgstr ""
#: src/routes/api/modtools.js:50
msgid "You are not logged in"
msgstr ""
#: src/routes/api/modtools.js:62
msgid "You are not allowed to access this page"
msgstr ""
#: src/routes/api/modtools.js:128
msgid "Just admins can do that"
msgstr ""
#: src/routes/api/auth/register.js:31 #: src/routes/api/auth/register.js:31
msgid "E-Mail already in use." msgid "E-Mail already in use."
msgstr "" msgstr ""
@ -302,6 +314,10 @@ msgstr ""
msgid "Failed to establish session after register :(" msgid "Failed to establish session after register :("
msgstr "" msgstr ""
#: src/routes/api/auth/logout.js:13
msgid "You are not even logged in."
msgstr ""
#: src/routes/api/auth/change_mail.js:41 #: src/routes/api/auth/change_mail.js:41
#: src/routes/api/auth/change_passwd.js:37 #: src/routes/api/auth/change_passwd.js:37
#: src/routes/api/auth/delete_account.js:38 #: src/routes/api/auth/delete_account.js:38
@ -314,23 +330,6 @@ msgstr ""
msgid "Incorrect password!" msgid "Incorrect password!"
msgstr "" msgstr ""
#: src/ssr-components/RedirectionPage.jsx:20
msgid "You will be automatically redirected after 15s"
msgstr ""
#: src/ssr-components/RedirectionPage.jsx:21
#, javascript-format
msgid "Or ${ clickHere } to go back to pixelplanet"
msgstr ""
#: src/ssr-components/RedirectionPage.jsx:25
msgid "PixelPlanet.fun Accounts"
msgstr ""
#: src/routes/api/auth/logout.js:13
msgid "You are not even logged in."
msgstr ""
#: src/routes/api/auth/verify.js:25 #: src/routes/api/auth/verify.js:25
#: src/routes/api/auth/verify.js:32 #: src/routes/api/auth/verify.js:32
msgid "Mail verification" msgid "Mail verification"
@ -346,6 +345,19 @@ msgid ""
"request a new one." "request a new one."
msgstr "" msgstr ""
#: src/ssr-components/RedirectionPage.jsx:20
msgid "You will be automatically redirected after 15s"
msgstr ""
#: src/ssr-components/RedirectionPage.jsx:21
#, javascript-format
msgid "Or ${ clickHere } to go back to pixelplanet"
msgstr ""
#: src/ssr-components/RedirectionPage.jsx:25
msgid "PixelPlanet.fun Accounts"
msgstr ""
#: src/canvasesDesc.js:18 #: src/canvasesDesc.js:18
msgid "Earth" msgid "Earth"
msgstr "" msgstr ""

View File

@ -45,22 +45,6 @@ msgstr ""
msgid "Hide Hidden Canvases" msgid "Hide Hidden Canvases"
msgstr "" msgstr ""
#: src/actions/index.js:632
msgid "Register New Account"
msgstr ""
#: src/actions/index.js:639
msgid "Restore my Password"
msgstr ""
#: src/actions/index.js:646
msgid "Welcome to PixelPlanet.fun"
msgstr ""
#: src/actions/index.js:652
msgid "Look at past Canvases"
msgstr ""
#: src/ui/placePixel.js:53 #: src/ui/placePixel.js:53
msgid "Error :(" msgid "Error :("
msgstr "" msgstr ""
@ -170,6 +154,22 @@ msgstr ""
msgid "Can't render 3D canvas, do you have WebGL2 disabled?" msgid "Can't render 3D canvas, do you have WebGL2 disabled?"
msgstr "" msgstr ""
#: src/actions/index.js:628
msgid "Register New Account"
msgstr ""
#: src/actions/index.js:635
msgid "Restore my Password"
msgstr ""
#: src/actions/index.js:642
msgid "Welcome to PixelPlanet.fun"
msgstr ""
#: src/actions/index.js:648
msgid "Look at past Canvases"
msgstr ""
#: src/components/Converter.jsx:559 #: src/components/Converter.jsx:559
#: src/components/CoordinatesBox.jsx:32 #: src/components/CoordinatesBox.jsx:32
msgid "Copy to Clipboard" msgid "Copy to Clipboard"
@ -187,8 +187,8 @@ msgstr ""
msgid "Pixels placed" msgid "Pixels placed"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:224
#: src/components/ModalRoot.jsx:69 #: src/components/ModalRoot.jsx:69
#: src/components/Modtools.jsx:224
#: src/components/Window.jsx:138 #: src/components/Window.jsx:138
#: src/components/contextmenus/ChannelContextMenu.jsx:67 #: src/components/contextmenus/ChannelContextMenu.jsx:67
msgid "Close" msgid "Close"
@ -198,27 +198,6 @@ msgstr ""
msgid "Restore" msgid "Restore"
msgstr "" msgstr ""
#: src/components/buttons/CanvasSwitchButton.jsx:22
#: src/components/windows/index.js:22
msgid "Canvas Selection"
msgstr ""
#: src/components/buttons/ChatButton.jsx:92
msgid "Close Chat"
msgstr ""
#: src/components/buttons/ChatButton.jsx:92
msgid "Open Chat"
msgstr ""
#: src/components/buttons/ExpandMenuButton.jsx:23
msgid "Close Menu"
msgstr ""
#: src/components/buttons/ExpandMenuButton.jsx:23
msgid "Open Menu"
msgstr ""
#: src/actions/fetch.js:39 #: src/actions/fetch.js:39
msgid "You made too many requests" msgid "You made too many requests"
msgstr "" msgstr ""
@ -248,6 +227,27 @@ msgstr ""
msgid "Server answered with gibberish :(" msgid "Server answered with gibberish :("
msgstr "" msgstr ""
#: src/components/buttons/CanvasSwitchButton.jsx:22
#: src/components/windows/index.js:22
msgid "Canvas Selection"
msgstr ""
#: src/components/buttons/ChatButton.jsx:92
msgid "Close Chat"
msgstr ""
#: src/components/buttons/ChatButton.jsx:92
msgid "Open Chat"
msgstr ""
#: src/components/buttons/ExpandMenuButton.jsx:23
msgid "Close Menu"
msgstr ""
#: src/components/buttons/ExpandMenuButton.jsx:23
msgid "Open Menu"
msgstr ""
#: src/components/HistorySelect.jsx:144 #: src/components/HistorySelect.jsx:144
msgid "Loading" msgid "Loading"
msgstr "" msgstr ""
@ -256,6 +256,25 @@ msgstr ""
msgid "Select Date above" msgid "Select Date above"
msgstr "" msgstr ""
#: src/components/buttons/HelpButton.jsx:23
#: src/components/windows/index.js:16
msgid "Help"
msgstr ""
#: src/components/buttons/SettingsButton.jsx:23
#: src/components/windows/index.js:17
msgid "Settings"
msgstr ""
#: src/components/buttons/LogInButton.jsx:23
#: src/components/windows/index.js:18
msgid "User Area"
msgstr ""
#: src/components/buttons/DownloadButton.jsx:37
msgid "Make Screenshot"
msgstr ""
#: src/components/Window.jsx:117 #: src/components/Window.jsx:117
msgid "Clone" msgid "Clone"
msgstr "" msgstr ""
@ -272,29 +291,6 @@ msgstr ""
msgid "Resize" msgid "Resize"
msgstr "" msgstr ""
#: src/components/buttons/HelpButton.jsx:23
#: src/components/windows/index.js:16
msgid "Help"
msgstr ""
#: src/components/buttons/LogInButton.jsx:23
#: src/components/windows/index.js:18
msgid "User Area"
msgstr ""
#: src/components/buttons/SettingsButton.jsx:23
#: src/components/windows/index.js:17
msgid "Settings"
msgstr ""
#: src/components/buttons/GlobeButton.jsx:35
msgid "Globe View"
msgstr ""
#: src/components/buttons/DownloadButton.jsx:37
msgid "Make Screenshot"
msgstr ""
#: src/components/buttons/PalselButton.jsx:31 #: src/components/buttons/PalselButton.jsx:31
msgid "Close Palette" msgid "Close Palette"
msgstr "" msgstr ""
@ -303,6 +299,10 @@ msgstr ""
msgid "Open Palette" msgid "Open Palette"
msgstr "" msgstr ""
#: src/components/buttons/GlobeButton.jsx:35
msgid "Globe View"
msgstr ""
#: src/components/contextmenus/ChannelContextMenu.jsx:55 #: src/components/contextmenus/ChannelContextMenu.jsx:55
msgid "Mute" msgid "Mute"
msgstr "" msgstr ""
@ -384,6 +384,225 @@ msgstr ""
msgid "Send" msgid "Send"
msgstr "" msgstr ""
#: src/components/windows/Register.jsx:81
msgid "Register new account here"
msgstr ""
#: src/components/windows/Register.jsx:92
msgid "Name"
msgstr ""
#: src/components/windows/ForgotPassword.jsx:82
#: src/components/windows/Register.jsx:100
msgid "Email"
msgstr ""
#: src/components/ChangeMail.jsx:80
#: src/components/DeleteAccount.jsx:62
#: src/components/LogInForm.jsx:83
#: src/components/windows/Register.jsx:108
msgid "Password"
msgstr ""
#: src/components/windows/Register.jsx:116
msgid "Confirm Password"
msgstr ""
#: src/components/Modtools.jsx:311
#: src/components/Modtools.jsx:392
#: src/components/Modtools.jsx:467
#: src/components/Modtools.jsx:512
#: src/components/Modtools.jsx:595
#: src/components/windows/ForgotPassword.jsx:86
#: src/components/windows/Register.jsx:119
msgid "Submit"
msgstr ""
#: src/components/windows/Settings.jsx:133
msgid "Show Grid"
msgstr ""
#: src/components/windows/Settings.jsx:138
msgid "Turn on grid to highlight pixel borders."
msgstr ""
#: src/components/windows/Settings.jsx:141
msgid "Show Pixel Activity"
msgstr ""
#: src/components/windows/Settings.jsx:146
msgid "Show circles where pixels are placed."
msgstr ""
#: src/components/windows/Settings.jsx:149
msgid "Disable Game Sounds"
msgstr ""
#: src/components/windows/Settings.jsx:155
msgid "All sound effects will be disabled."
msgstr ""
#: src/components/windows/Settings.jsx:159
msgid ""
"Your Browser doesn't allow us to use AudioContext to play sounds. Do you "
"have some privacy feature blocking us?"
msgstr ""
#: src/components/windows/Settings.jsx:165
msgid "Enable chat notifications"
msgstr ""
#: src/components/windows/Settings.jsx:169
msgid "Play a sound when new chat messages arrive"
msgstr ""
#: src/components/windows/Settings.jsx:172
msgid "Auto Zoom In"
msgstr ""
#: src/components/windows/Settings.jsx:177
msgid ""
"Zoom in instead of placing a pixel when you tap the canvas and your zoom is "
"small."
msgstr ""
#: src/components/windows/Settings.jsx:180
msgid "Compact Palette"
msgstr ""
#: src/components/windows/Settings.jsx:185
msgid "Display Palette in a compact form that takes less screen space."
msgstr ""
#: src/components/windows/Settings.jsx:188
msgid "Potato Mode"
msgstr ""
#: src/components/windows/Settings.jsx:192
msgid "For when you are playing on a potato."
msgstr ""
#: src/components/Converter.jsx:374
#: src/components/windows/Settings.jsx:195
msgid "Light Grid"
msgstr ""
#: src/components/windows/Settings.jsx:199
msgid "Show Grid in white instead of black."
msgstr ""
#: src/components/windows/Settings.jsx:203
msgid "Historical View"
msgstr ""
#: src/components/windows/Settings.jsx:208
msgid "Check out past versions of the canvas."
msgstr ""
#: src/components/windows/Settings.jsx:213
msgid "Themes"
msgstr ""
#: src/components/windows/Settings.jsx:218
msgid "How pixelplanet should look like."
msgstr ""
#: src/components/windows/Settings.jsx:225
msgid "Select Language"
msgstr ""
#: src/components/windows/CanvasSelect.jsx:33
msgid ""
"Select the canvas you want to use. Every canvas is unique and has "
"different palettes, cooldown and requirements. Archive of closed canvases "
"can be accessed here:"
msgstr ""
#: src/components/windows/CanvasSelect.jsx:41
msgid "Archive"
msgstr ""
#: src/components/windows/Chat.jsx:146
msgid "Channel settings"
msgstr ""
#: src/components/windows/Chat.jsx:161
msgid "Start chatting here"
msgstr ""
#: src/components/windows/Chat.jsx:198
msgid "Chat here"
msgstr ""
#: src/components/windows/Chat.jsx:221
msgid "You must be logged in to chat"
msgstr ""
#: src/components/windows/Archive.jsx:20
msgid ""
"While we tend to not delete canvases, some canvases are started for fun or "
"as a request by users who currently like a meme. Those canvases can get "
"boring after a while and after weeks of no major change and if they really "
"aren't worth being kept active, we decide to remove them."
msgstr ""
#: src/components/windows/Archive.jsx:22
msgid ""
"Here we collect those canvases to archive them in a proper way (which is "
"currently just one)."
msgstr ""
#: src/components/windows/Archive.jsx:24
msgid "Political Compass Canvas"
msgstr ""
#: src/components/windows/Archive.jsx:31
msgid ""
"This canvas got requested during a time of political conflicts on the main "
"Earth canvas. It was a 1024x1024 representation of the political compass "
"with a 5s cooldown and 60s stacking. It got launched on May 11th and "
"remained active for months till it got shut down on November 30th."
msgstr ""
#: src/components/windows/Archive.jsx:32
msgid ""
"We decided to archive it as a timelapse with lossless encoded webm. Taking "
"a screenshot from the timelapse results in a perfect 1:1 representation of "
"how the canvas was at that time."
msgstr ""
#: src/components/windows/ForgotPassword.jsx:60
msgid "Sent you a mail with instructions to reset your password."
msgstr ""
#: src/components/windows/ForgotPassword.jsx:71
msgid "Enter your mail address and we will send you a new password:"
msgstr ""
#: src/components/windows/UserArea.jsx:27
msgid "Profile"
msgstr ""
#: src/components/windows/UserArea.jsx:30
msgid "Ranking"
msgstr ""
#: src/components/windows/UserArea.jsx:33
msgid "Converter"
msgstr ""
#: src/components/windows/UserArea.jsx:39
msgid "Modtools"
msgstr ""
#: src/components/windows/UserArea.jsx:40
msgid "Loading..."
msgstr ""
#: src/components/windows/UserArea.jsx:47
msgid "Consider joining us on Guilded:"
msgstr ""
#: src/components/windows/Help.jsx:36 #: src/components/windows/Help.jsx:36
msgid "your IP" msgid "your IP"
msgstr "" msgstr ""
@ -561,229 +780,6 @@ msgstr ""
msgid "Credit for the Palette of the Top10 canvas goes to ${ vinikLink }." msgid "Credit for the Palette of the Top10 canvas goes to ${ vinikLink }."
msgstr "" msgstr ""
#: src/components/windows/Register.jsx:81
msgid "Register new account here"
msgstr ""
#: src/components/windows/Register.jsx:92
msgid "Name"
msgstr ""
#: src/components/windows/ForgotPassword.jsx:82
#: src/components/windows/Register.jsx:100
msgid "Email"
msgstr ""
#: src/components/ChangeMail.jsx:80
#: src/components/DeleteAccount.jsx:62
#: src/components/LogInForm.jsx:83
#: src/components/windows/Register.jsx:108
msgid "Password"
msgstr ""
#: src/components/windows/Register.jsx:116
msgid "Confirm Password"
msgstr ""
#: src/components/Admintools.jsx:311
#: src/components/Admintools.jsx:392
#: src/components/Admintools.jsx:467
#: src/components/Admintools.jsx:512
#: src/components/Admintools.jsx:595
#: src/components/windows/ForgotPassword.jsx:86
#: src/components/windows/Register.jsx:119
msgid "Submit"
msgstr ""
#: src/components/windows/CanvasSelect.jsx:33
msgid ""
"Select the canvas you want to use. Every canvas is unique and has "
"different palettes, cooldown and requirements. Archive of closed canvases "
"can be accessed here:"
msgstr ""
#: src/components/windows/CanvasSelect.jsx:41
msgid "Archive"
msgstr ""
#: src/components/windows/Archive.jsx:20
msgid ""
"While we tend to not delete canvases, some canvases are started for fun or "
"as a request by users who currently like a meme. Those canvases can get "
"boring after a while and after weeks of no major change and if they really "
"aren't worth being kept active, we decide to remove them."
msgstr ""
#: src/components/windows/Archive.jsx:22
msgid ""
"Here we collect those canvases to archive them in a proper way (which is "
"currently just one)."
msgstr ""
#: src/components/windows/Archive.jsx:24
msgid "Political Compass Canvas"
msgstr ""
#: src/components/windows/Archive.jsx:31
msgid ""
"This canvas got requested during a time of political conflicts on the main "
"Earth canvas. It was a 1024x1024 representation of the political compass "
"with a 5s cooldown and 60s stacking. It got launched on May 11th and "
"remained active for months till it got shut down on November 30th."
msgstr ""
#: src/components/windows/Archive.jsx:32
msgid ""
"We decided to archive it as a timelapse with lossless encoded webm. Taking "
"a screenshot from the timelapse results in a perfect 1:1 representation of "
"how the canvas was at that time."
msgstr ""
#: src/components/windows/Chat.jsx:146
msgid "Channel settings"
msgstr ""
#: src/components/windows/Chat.jsx:161
msgid "Start chatting here"
msgstr ""
#: src/components/windows/Chat.jsx:198
msgid "Chat here"
msgstr ""
#: src/components/windows/Chat.jsx:221
msgid "You must be logged in to chat"
msgstr ""
#: src/components/windows/ForgotPassword.jsx:60
msgid "Sent you a mail with instructions to reset your password."
msgstr ""
#: src/components/windows/ForgotPassword.jsx:71
msgid "Enter your mail address and we will send you a new password:"
msgstr ""
#: src/components/windows/Settings.jsx:133
msgid "Show Grid"
msgstr ""
#: src/components/windows/Settings.jsx:138
msgid "Turn on grid to highlight pixel borders."
msgstr ""
#: src/components/windows/Settings.jsx:141
msgid "Show Pixel Activity"
msgstr ""
#: src/components/windows/Settings.jsx:146
msgid "Show circles where pixels are placed."
msgstr ""
#: src/components/windows/Settings.jsx:149
msgid "Disable Game Sounds"
msgstr ""
#: src/components/windows/Settings.jsx:155
msgid "All sound effects will be disabled."
msgstr ""
#: src/components/windows/Settings.jsx:159
msgid ""
"Your Browser doesn't allow us to use AudioContext to play sounds. Do you "
"have some privacy feature blocking us?"
msgstr ""
#: src/components/windows/Settings.jsx:165
msgid "Enable chat notifications"
msgstr ""
#: src/components/windows/Settings.jsx:169
msgid "Play a sound when new chat messages arrive"
msgstr ""
#: src/components/windows/Settings.jsx:172
msgid "Auto Zoom In"
msgstr ""
#: src/components/windows/Settings.jsx:177
msgid ""
"Zoom in instead of placing a pixel when you tap the canvas and your zoom is "
"small."
msgstr ""
#: src/components/windows/Settings.jsx:180
msgid "Compact Palette"
msgstr ""
#: src/components/windows/Settings.jsx:185
msgid "Display Palette in a compact form that takes less screen space."
msgstr ""
#: src/components/windows/Settings.jsx:188
msgid "Potato Mode"
msgstr ""
#: src/components/windows/Settings.jsx:192
msgid "For when you are playing on a potato."
msgstr ""
#: src/components/Converter.jsx:374
#: src/components/windows/Settings.jsx:195
msgid "Light Grid"
msgstr ""
#: src/components/windows/Settings.jsx:199
msgid "Show Grid in white instead of black."
msgstr ""
#: src/components/windows/Settings.jsx:203
msgid "Historical View"
msgstr ""
#: src/components/windows/Settings.jsx:208
msgid "Check out past versions of the canvas."
msgstr ""
#: src/components/windows/Settings.jsx:213
msgid "Themes"
msgstr ""
#: src/components/windows/Settings.jsx:218
msgid "How pixelplanet should look like."
msgstr ""
#: src/components/windows/Settings.jsx:225
msgid "Select Language"
msgstr ""
#: src/components/windows/UserArea.jsx:27
msgid "Profile"
msgstr ""
#: src/components/windows/UserArea.jsx:30
msgid "Ranking"
msgstr ""
#: src/components/windows/UserArea.jsx:33
msgid "Converter"
msgstr ""
#: src/components/windows/UserArea.jsx:39
msgid "Admintools"
msgstr ""
#: src/components/windows/UserArea.jsx:39
msgid "Modtools"
msgstr ""
#: src/components/windows/UserArea.jsx:40
msgid "Loading..."
msgstr ""
#: src/components/windows/UserArea.jsx:47
msgid "Consider joining us on Guilded:"
msgstr ""
#: src/utils/validation.js:18 #: src/utils/validation.js:18
msgid "Email can't be empty." msgid "Email can't be empty."
msgstr "" msgstr ""
@ -832,6 +828,13 @@ msgstr ""
msgid "Password must be shorter than 60 characters." msgid "Password must be shorter than 60 characters."
msgstr "" msgstr ""
#: src/components/ChangeMail.jsx:91
#: src/components/ChangeName.jsx:68
#: src/components/ChangePassword.jsx:110
#: src/components/LanguageSelect.jsx:73
msgid "Save"
msgstr ""
#: src/components/CanvasItem.jsx:30 #: src/components/CanvasItem.jsx:30
msgid "Online Users" msgid "Online Users"
msgstr "" msgstr ""
@ -877,30 +880,6 @@ msgstr ""
msgid "Dimensions" msgid "Dimensions"
msgstr "" msgstr ""
#: src/components/LogInArea.jsx:21
msgid "Login to access more features and stats."
msgstr ""
#: src/components/LogInArea.jsx:23
msgid "Login with Name or Mail:"
msgstr ""
#: src/components/LogInArea.jsx:30
msgid "I forgot my Password."
msgstr ""
#: src/components/LogInArea.jsx:31
msgid "or login with:"
msgstr ""
#: src/components/LogInArea.jsx:72
msgid "or register here:"
msgstr ""
#: src/components/LogInArea.jsx:79
msgid "Register"
msgstr ""
#: src/components/UserAreaContent.jsx:63 #: src/components/UserAreaContent.jsx:63
msgid "Todays Placed Pixels" msgid "Todays Placed Pixels"
msgstr "" msgstr ""
@ -958,81 +937,81 @@ msgstr ""
msgid "Ranking updates every 5 min. Daily rankings get reset at midnight UTC." msgid "Ranking updates every 5 min. Daily rankings get reset at midnight UTC."
msgstr "" msgstr ""
#: src/components/Admintools.jsx:184 #: src/components/Modtools.jsx:184
msgid "Build image on canvas." msgid "Build image on canvas."
msgstr "" msgstr ""
#: src/components/Admintools.jsx:187 #: src/components/Modtools.jsx:187
msgid "Build image and set it to protected." msgid "Build image and set it to protected."
msgstr "" msgstr ""
#: src/components/Admintools.jsx:190 #: src/components/Modtools.jsx:190
msgid "Build image, but reset cooldown to unset-pixel cd." msgid "Build image, but reset cooldown to unset-pixel cd."
msgstr "" msgstr ""
#: src/components/Admintools.jsx:253 #: src/components/Modtools.jsx:253
msgid "Image Upload" msgid "Image Upload"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:254 #: src/components/Modtools.jsx:254
msgid "Upload images to canvas" msgid "Upload images to canvas"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:256 #: src/components/Modtools.jsx:256
msgid "File" msgid "File"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:276 #: src/components/Modtools.jsx:276
msgid "Coordinates in X_Y format:" msgid "Coordinates in X_Y format:"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:316 #: src/components/Modtools.jsx:316
msgid "Pixel Protection" msgid "Pixel Protection"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:318 #: src/components/Modtools.jsx:318
msgid "" msgid ""
"Set protection of areas (if you need finer grained control, " "Set protection of areas (if you need finer grained control, "
"use protect with image upload and alpha layers)" "use protect with image upload and alpha layers)"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:398 #: src/components/Modtools.jsx:398
msgid "Rollback to Date" msgid "Rollback to Date"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:400 #: src/components/Modtools.jsx:400
msgid "Rollback an area of the canvas to a set date (00:00 UTC)" msgid "Rollback an area of the canvas to a set date (00:00 UTC)"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:475 #: src/components/Modtools.jsx:475
msgid "IP Actions" msgid "IP Actions"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:477 #: src/components/Modtools.jsx:477
msgid "Do stuff with IPs (one IP per line)" msgid "Do stuff with IPs (one IP per line)"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:516 #: src/components/Modtools.jsx:516
msgid "Manage Moderators" msgid "Manage Moderators"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:518 #: src/components/Modtools.jsx:518
msgid "Remove Moderator" msgid "Remove Moderator"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:550 #: src/components/Modtools.jsx:550
msgid "There are no mods" msgid "There are no mods"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:555 #: src/components/Modtools.jsx:555
msgid "Assign new Mod" msgid "Assign new Mod"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:558 #: src/components/Modtools.jsx:558
msgid "Enter UserName of new Mod" msgid "Enter UserName of new Mod"
msgstr "" msgstr ""
#: src/components/Admintools.jsx:567 #: src/components/Modtools.jsx:567
msgid "User Name" msgid "User Name"
msgstr "" msgstr ""
@ -1114,33 +1093,28 @@ msgstr ""
msgid "Download Template" msgid "Download Template"
msgstr "" msgstr ""
#: src/components/ChangeMail.jsx:91 #: src/components/LogInArea.jsx:21
#: src/components/ChangeName.jsx:68 msgid "Login to access more features and stats."
#: src/components/ChangePassword.jsx:110
#: src/components/LanguageSelect.jsx:73
msgid "Save"
msgstr "" msgstr ""
#: src/components/LogInForm.jsx:76 #: src/components/LogInArea.jsx:23
msgid "Name or Email" msgid "Login with Name or Mail:"
msgstr "" msgstr ""
#: src/components/LogInForm.jsx:87 #: src/components/LogInArea.jsx:30
msgid "LogIn" msgid "I forgot my Password."
msgstr "" msgstr ""
#: src/components/UserMessages.jsx:28 #: src/components/LogInArea.jsx:31
msgid "" msgid "or login with:"
"Please verify your mail address \n"
"or your account could get deleted after a few days."
msgstr "" msgstr ""
#: src/components/UserMessages.jsx:49 #: src/components/LogInArea.jsx:72
msgid "A new verification mail is getting sent to you." msgid "or register here:"
msgstr "" msgstr ""
#: src/components/UserMessages.jsx:53 #: src/components/LogInArea.jsx:79
msgid "Click here to request a new verification mail." msgid "Register"
msgstr "" msgstr ""
#: src/components/ChangePassword.jsx:22 #: src/components/ChangePassword.jsx:22
@ -1163,10 +1137,6 @@ msgstr ""
msgid "Confirm New Password" msgid "Confirm New Password"
msgstr "" msgstr ""
#: src/components/ChangeName.jsx:64
msgid "New Username"
msgstr ""
#: src/components/ChangeMail.jsx:59 #: src/components/ChangeMail.jsx:59
msgid "" msgid ""
"Changed Mail successfully. We sent you a verification mail, " "Changed Mail successfully. We sent you a verification mail, "
@ -1177,6 +1147,10 @@ msgstr ""
msgid "New Mail" msgid "New Mail"
msgstr "" msgstr ""
#: src/components/DeleteAccount.jsx:66
msgid "Yes, Delete My Account!"
msgstr ""
#: src/components/SocialSettings.jsx:38 #: src/components/SocialSettings.jsx:38
msgid "Block all Private Messages" msgid "Block all Private Messages"
msgstr "" msgstr ""
@ -1189,8 +1163,30 @@ msgstr ""
msgid "You have no users blocked" msgid "You have no users blocked"
msgstr "" msgstr ""
#: src/components/DeleteAccount.jsx:66 #: src/components/UserMessages.jsx:28
msgid "Yes, Delete My Account!" msgid ""
"Please verify your mail address \n"
"or your account could get deleted after a few days."
msgstr ""
#: src/components/UserMessages.jsx:49
msgid "A new verification mail is getting sent to you."
msgstr ""
#: src/components/UserMessages.jsx:53
msgid "Click here to request a new verification mail."
msgstr ""
#: src/components/ChangeName.jsx:64
msgid "New Username"
msgstr ""
#: src/components/LogInForm.jsx:76
msgid "Name or Email"
msgstr ""
#: src/components/LogInForm.jsx:87
msgid "LogIn"
msgstr "" msgstr ""
#: src/components/windows/Help.jsx:14 #: src/components/windows/Help.jsx:14
@ -1205,6 +1201,11 @@ msgctxt "keybinds"
msgid "X" msgid "X"
msgstr "" msgstr ""
#: src/components/windows/Settings.jsx:150
msgctxt "keybinds"
msgid "M"
msgstr ""
#: src/components/windows/Help.jsx:16 #: src/components/windows/Help.jsx:16
#: src/components/windows/Settings.jsx:205 #: src/components/windows/Settings.jsx:205
msgctxt "keybinds" msgctxt "keybinds"
@ -1254,9 +1255,4 @@ msgstr ""
#: src/components/windows/Help.jsx:31 #: src/components/windows/Help.jsx:31
msgctxt "keybinds" msgctxt "keybinds"
msgid "C" msgid "C"
msgstr ""
#: src/components/windows/Settings.jsx:150
msgctxt "keybinds"
msgid "M"
msgstr "" msgstr ""

1
package-lock.json generated
View File

@ -10,7 +10,6 @@
"dependencies": { "dependencies": {
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
"bluebird": "^3.5.0", "bluebird": "^3.5.0",
"body-parser": "^1.19.1",
"bufferutil": "^4.0.6", "bufferutil": "^4.0.6",
"compression": "^1.7.3", "compression": "^1.7.3",
"connect-redis": "^6.0.0", "connect-redis": "^6.0.0",

View File

@ -25,7 +25,6 @@
"dependencies": { "dependencies": {
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
"bluebird": "^3.5.0", "bluebird": "^3.5.0",
"body-parser": "^1.19.1",
"bufferutil": "^4.0.6", "bufferutil": "^4.0.6",
"compression": "^1.7.3", "compression": "^1.7.3",
"connect-redis": "^6.0.0", "connect-redis": "^6.0.0",

View File

@ -177,10 +177,10 @@ export async function requestLeaveChan(channelId) {
return t`Unknown Error`; return t`Unknown Error`;
} }
export async function requestSolveCaptcha(text) { export async function requestSolveCaptcha(text, captchaid) {
const res = await makeAPIPOSTRequest( const res = await makeAPIPOSTRequest(
'api/captcha', 'api/captcha',
{ text }, { text, id: captchaid },
); );
if (!res.errors && !res.success) { if (!res.errors && !res.success) {
return { return {
@ -193,7 +193,7 @@ export async function requestSolveCaptcha(text) {
export async function requestHistoricalTimes(day, canvasId) { export async function requestHistoricalTimes(day, canvasId) {
try { try {
const date = dateToString(day); const date = dateToString(day);
const url = `api/history?day=${date}&id=${canvasId}`; const url = `history?day=${date}&id=${canvasId}`;
const response = await fetchWithTimeout(url); const response = await fetchWithTimeout(url);
if (response.status !== 200) { if (response.status !== 200) {
return []; return [];
@ -271,3 +271,15 @@ export function requestDeleteAccount(password) {
{ password }, { password },
); );
} }
export function requestRankings() {
return makeAPIGETRequest(
'ranking',
);
}
export function requestMe() {
return makeAPIGETRequest(
'api/me',
);
}

View File

@ -5,6 +5,8 @@ import {
requestBlock, requestBlock,
requestBlockDm, requestBlockDm,
requestLeaveChan, requestLeaveChan,
requestRankings,
requestMe,
} from './fetch'; } from './fetch';
export function sweetAlert( export function sweetAlert(
@ -446,10 +448,8 @@ export function remFromMessages(
export function fetchStats() { export function fetchStats() {
return async (dispatch) => { return async (dispatch) => {
const response = await fetch('api/ranking', { credentials: 'include' }); const rankings = await requestRankings();
if (response.ok) { if (!rankings.errors) {
const rankings = await response.json();
dispatch(receiveStats(rankings)); dispatch(receiveStats(rankings));
} }
}; };
@ -457,12 +457,8 @@ export function fetchStats() {
export function fetchMe() { export function fetchMe() {
return async (dispatch) => { return async (dispatch) => {
const response = await fetch('api/me', { const me = await requestMe();
credentials: 'include', if (!me.errors) {
});
if (response.ok) {
const me = await response.json();
dispatch(receiveMe(me)); dispatch(receiveMe(me));
} }
}; };

View File

@ -113,7 +113,6 @@ export type Action =
dailyTotalPixels: number, dailyTotalPixels: number,
ranking: number, ranking: number,
dailyRanking: number, dailyRanking: number,
minecraftname: string,
blockDm: boolean, blockDm: boolean,
canvases: Object, canvases: Object,
channels: Object, channels: Object,
@ -129,7 +128,6 @@ export type Action =
dailyTotalPixels: number, dailyTotalPixels: number,
ranking: number, ranking: number,
dailyRanking: number, dailyRanking: number,
minecraftname: string,
blockDm: boolean, blockDm: boolean,
canvases: Object, canvases: Object,
channels: Object, channels: Object,

View File

@ -8,10 +8,12 @@ import path from 'path';
import fs from 'fs'; import fs from 'fs';
import process from 'process'; import process from 'process';
import http from 'http'; import http from 'http';
import url from 'url';
import ppfunCaptcha from 'ppfun-captcha'; import ppfunCaptcha from 'ppfun-captcha';
import { getIPFromRequest } from './utils/ip'; import { getIPFromRequest } from './utils/ip';
import { setCaptchaSolution } from './utils/captcha'; import { setCaptchaSolution } from './utils/captcha';
import { getRandomString } from './core/utils';
const PORT = process.env.PORT || 8080; const PORT = process.env.PORT || 8080;
const HOST = process.env.HOST || 'localhost'; const HOST = process.env.HOST || 'localhost';
@ -23,30 +25,51 @@ const font = fs.readdirSync(path.resolve(__dirname, 'captchaFonts'))
)); ));
const server = http.createServer((req, res) => { const server = http.createServer((req, res) => {
const captcha = ppfunCaptcha.create({ console.log(req.url);
width: 500,
height: 300, req.on('error', (err) => {
fontSize: 180, console.error(err);
stroke: 'black',
fill: 'none',
nodeDeviation: 2.5,
connectionPathDeviation: 10.0,
style: 'stroke-width: 4;',
background: '#EFEFEF',
font,
}); });
const ip = getIPFromRequest(req); const urlObject = url.parse(req.url, true);
setCaptchaSolution(captcha.text, ip); if (req.method === 'GET' && urlObject.pathname.endsWith('.svg')) {
console.log(`Serving ${captcha.text} to ${ip}`); const captcha = ppfunCaptcha.create({
width: 500,
height: 300,
fontSize: 180,
stroke: 'black',
fill: 'none',
nodeDeviation: 2.5,
connectionPathDeviation: 10.0,
style: 'stroke-width: 4;',
background: '#EFEFEF',
font,
});
res.writeHead(200, { const ip = getIPFromRequest(req);
'Content-Type': 'image/svg+xml', const captchaid = getRandomString();
'Cache-Control': 'no-cache',
}); setCaptchaSolution(captcha.text, ip, captchaid);
res.write(captcha.data); console.log(`Serving ${captcha.text} to ${ip} / ${captchaid}`);
res.end();
res.writeHead(200, {
'Content-Type': 'image/svg+xml',
'Cache-Control': 'no-cache',
'Captcha-Id': captchaid,
});
res.write(captcha.data);
res.end();
} else {
res.writeHead(404, {
'Content-Type': 'text/html',
'Cache-Control': 'no-cache',
});
res.end(
// eslint-disable-next-line max-len
'<html><body><h1>Captchaserver: 404 Not Found</h1>Captchas are accessible via *.svp paths</body></html>',
);
}
}); });
server.listen(PORT, HOST, () => { server.listen(PORT, HOST, () => {

View File

@ -1,4 +1,6 @@
/* @flow */ /*
* Entrypoint for main client script
*/
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import fetch from 'isomorphic-fetch'; // TODO put in the beggining with webpack! import fetch from 'isomorphic-fetch'; // TODO put in the beggining with webpack!
@ -117,6 +119,8 @@ function init() {
ProtocolClient.connect(); ProtocolClient.connect();
store.dispatch(fetchStats()); store.dispatch(fetchStats());
// TODO: We don't have to do this this often
// the client might not even look at it
setInterval(() => { store.dispatch(fetchStats()); }, 300000); setInterval(() => { store.dispatch(fetchStats()); }, 300000);
} }
init(); init();
@ -156,7 +160,7 @@ document.addEventListener('DOMContentLoaded', () => {
// on captcha received // on captcha received
window.onCaptcha = async function onCaptcha(token: string) { window.onCaptcha = async function onCaptcha(token) {
const body = JSON.stringify({ const body = JSON.stringify({
token, token,
}); });

View File

@ -53,7 +53,7 @@ const Alert = () => {
<p className="modaltext"> <p className="modaltext">
{alertMessage} {alertMessage}
</p> </p>
<p> <div>
{(alertType === 'captcha') {(alertType === 'captcha')
? <Captcha close={close} /> ? <Captcha close={close} />
: ( : (
@ -64,7 +64,7 @@ const Alert = () => {
{alertBtn} {alertBtn}
</button> </button>
)} )}
</p> </div>
</div> </div>
</div> </div>
) )

View File

@ -8,29 +8,47 @@
/* eslint-disable jsx-a11y/no-autofocus */ /* eslint-disable jsx-a11y/no-autofocus */
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { t } from 'ttag'; import { t } from 'ttag';
import { IoReloadCircleSharp } from 'react-icons/io5'; import { IoReloadCircleSharp } from 'react-icons/io5';
import { requestSolveCaptcha } from '../actions/fetch'; import { requestSolveCaptcha } from '../actions/fetch';
function getUrl() { async function getUrlAndId() {
return `${window.ssv.captchaurl}/captcha.svg?${new Date().getTime()}`; const url = window.ssv.captchaurl;
const resp = await fetch(url, {
cache: 'no-cache',
});
if (resp.ok) {
const captchaid = resp.headers.get('captcha-id');
const svgBlob = await resp.blob();
return [URL.createObjectURL(svgBlob), captchaid];
}
return null;
} }
const Captcha = ({ callback, close }) => { const Captcha = ({ callback, close }) => {
const [captchaUrl, setCaptchaUrl] = useState(getUrl()); const [captchaData, setCaptchaData] = useState({});
const [text, setText] = useState(''); const [text, setText] = useState('');
const [errors, setErrors] = useState([]); const [errors, setErrors] = useState([]);
const [imgLoaded, setImgLoaded] = useState(false); const [imgLoaded, setImgLoaded] = useState(false);
useEffect(async () => {
const [svgUrl, captchaid] = await getUrlAndId();
setCaptchaData({ url: svgUrl, id: captchaid });
}, []);
return ( return (
<form <form
onSubmit={async (e) => { onSubmit={async (e) => {
e.preventDefault(); e.preventDefault();
const { errors: resErrors } = await requestSolveCaptcha(text); const { errors: resErrors } = await requestSolveCaptcha(
text,
captchaData.id,
);
if (resErrors) { if (resErrors) {
setCaptchaUrl(getUrl()); const [svgUrl, captchaid] = await getUrlAndId();
setCaptchaData({ url: svgUrl, id: captchaid });
setText(''); setText('');
setErrors(resErrors); setErrors(resErrors);
} else { } else {
@ -58,21 +76,23 @@ const Captcha = ({ callback, close }) => {
position: 'relative', position: 'relative',
}} }}
> >
<img {(captchaData.url) && (
style={{ <img
width: '100%', style={{
position: 'absolute', width: '100%',
top: '50%', position: 'absolute',
left: '50%', top: '50%',
opacity: (imgLoaded) ? 1 : 0, left: '50%',
transform: 'translate(-50%,-50%)', opacity: (imgLoaded) ? 1 : 0,
transition: '100ms', transform: 'translate(-50%,-50%)',
}} transition: '100ms',
src={captchaUrl} }}
alt="CAPTCHA" src={captchaData.url}
onLoad={() => { setImgLoaded(true); }} alt="CAPTCHA"
onError={() => setErrors([t`Could not load captcha`])} onLoad={() => { setImgLoaded(true); }}
/> onError={() => setErrors([t`Could not load captcha`])}
/>
)}
</div> </div>
<p className="modaltext"> <p className="modaltext">
{t`Can't read? Reload:`}&nbsp; {t`Can't read? Reload:`}&nbsp;
@ -82,9 +102,10 @@ const Captcha = ({ callback, close }) => {
title={t`Reload`} title={t`Reload`}
className="modallink" className="modallink"
style={{ fontSize: 28 }} style={{ fontSize: 28 }}
onClick={() => { onClick={async () => {
setImgLoaded(false); setImgLoaded(false);
setCaptchaUrl(getUrl()); const [svgUrl, captchaid] = await getUrlAndId();
setCaptchaData({ url: svgUrl, id: captchaid });
}} }}
> >
<IoReloadCircleSharp /> <IoReloadCircleSharp />

View File

@ -1,5 +1,5 @@
/* /*
* Admintools * Modtools
* @flow * @flow
*/ */
@ -31,7 +31,7 @@ async function submitImageAction(
data.append('image', file); data.append('image', file);
data.append('canvasid', canvas); data.append('canvasid', canvas);
data.append('coords', coords); data.append('coords', coords);
const resp = await fetch('./admintools', { const resp = await fetch('./api/modtools', {
credentials: 'include', credentials: 'include',
method: 'POST', method: 'POST',
body: data, body: data,
@ -51,7 +51,7 @@ async function submitProtAction(
data.append('canvasid', canvas); data.append('canvasid', canvas);
data.append('ulcoor', tlcoords); data.append('ulcoor', tlcoords);
data.append('brcoor', brcoords); data.append('brcoor', brcoords);
const resp = await fetch('./admintools', { const resp = await fetch('./api/modtools', {
credentials: 'include', credentials: 'include',
method: 'POST', method: 'POST',
body: data, body: data,
@ -72,7 +72,7 @@ async function submitRollback(
data.append('canvasid', canvas); data.append('canvasid', canvas);
data.append('ulcoor', tlcoords); data.append('ulcoor', tlcoords);
data.append('brcoor', brcoords); data.append('brcoor', brcoords);
const resp = await fetch('./admintools', { const resp = await fetch('./api/modtools', {
credentials: 'include', credentials: 'include',
method: 'POST', method: 'POST',
body: data, body: data,
@ -88,7 +88,7 @@ async function submitIPAction(
const iplist = document.getElementById('iparea').value; const iplist = document.getElementById('iparea').value;
data.append('ip', iplist); data.append('ip', iplist);
data.append('ipaction', action); data.append('ipaction', action);
const resp = await fetch('./admintools', { const resp = await fetch('./api/modtools', {
credentials: 'include', credentials: 'include',
method: 'POST', method: 'POST',
body: data, body: data,
@ -101,7 +101,7 @@ async function getModList(
) { ) {
const data = new FormData(); const data = new FormData();
data.append('modlist', true); data.append('modlist', true);
const resp = await fetch('./admintools', { const resp = await fetch('./api/modtools', {
credentials: 'include', credentials: 'include',
method: 'POST', method: 'POST',
body: data, body: data,
@ -119,7 +119,7 @@ async function submitRemMod(
) { ) {
const data = new FormData(); const data = new FormData();
data.append('remmod', userId); data.append('remmod', userId);
const resp = await fetch('./admintools', { const resp = await fetch('./api/modtools', {
credentials: 'include', credentials: 'include',
method: 'POST', method: 'POST',
body: data, body: data,
@ -133,7 +133,7 @@ async function submitMakeMod(
) { ) {
const data = new FormData(); const data = new FormData();
data.append('makemod', userName); data.append('makemod', userName);
const resp = await fetch('./admintools', { const resp = await fetch('./api/modtools', {
credentials: 'include', credentials: 'include',
method: 'POST', method: 'POST',
body: data, body: data,
@ -146,7 +146,7 @@ async function submitMakeMod(
} }
function Admintools() { function Modtools() {
const maxDate = getToday(); const maxDate = getToday();
const [selectedCanvas, selectCanvas] = useState(0); const [selectedCanvas, selectCanvas] = useState(0);
@ -603,4 +603,4 @@ function Admintools() {
); );
} }
export default React.memo(Admintools); export default React.memo(Modtools);

View File

@ -15,7 +15,7 @@ import Rankings from '../Rankings';
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
const Converter = React.lazy(() => import(/* webpackChunkName: "converter" */ '../Converter')); const Converter = React.lazy(() => import(/* webpackChunkName: "converter" */ '../Converter'));
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
const Admintools = React.lazy(() => import(/* webpackChunkName: "admintools" */ '../Admintools')); const Modtools = React.lazy(() => import(/* webpackChunkName: "modtools" */ '../Modtools'));
const UserArea = ({ windowId }) => { const UserArea = ({ windowId }) => {
const name = useSelector((state) => state.user.name); const name = useSelector((state) => state.user.name);
@ -36,9 +36,9 @@ const UserArea = ({ windowId }) => {
</Suspense> </Suspense>
</div> </div>
{userlvl && ( {userlvl && (
<div label={(userlvl === 1) ? t`Admintools` : t`Modtools`}> <div label={(userlvl === 1) ? t`Modtools` : t`Modtools`}>
<Suspense fallback={<div>{t`Loading...`}</div>}> <Suspense fallback={<div>{t`Loading...`}</div>}>
<Admintools /> <Modtools />
</Suspense> </Suspense>
</div> </div>
)} )}

View File

@ -10,7 +10,7 @@ import sharp from 'sharp';
import Sequelize from 'sequelize'; import Sequelize from 'sequelize';
import redis from '../data/redis'; import redis from '../data/redis';
import { admintoolsLogger } from './logger'; import { modtoolsLogger } from './logger';
import { getIPv6Subnet } from '../utils/ip'; import { getIPv6Subnet } from '../utils/ip';
import { Blacklist, Whitelist, RegUser } from '../data/models'; import { Blacklist, Whitelist, RegUser } from '../data/models';
// eslint-disable-next-line import/no-unresolved // eslint-disable-next-line import/no-unresolved
@ -46,7 +46,7 @@ export async function executeIPAction(action: string, ips: string): string {
const ipKey = getIPv6Subnet(ip); const ipKey = getIPv6Subnet(ip);
const key = `isprox:${ipKey}`; const key = `isprox:${ipKey}`;
admintoolsLogger.info(`ADMINTOOLS: ${action} ${ip}`); modtoolsLogger.info(`ADMINTOOLS: ${action} ${ip}`);
switch (action) { switch (action) {
case 'ban': case 'ban':
await Blacklist.findOrCreate({ await Blacklist.findOrCreate({
@ -150,7 +150,7 @@ export async function executeImageAction(
); );
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
admintoolsLogger.info(`ADMINTOOLS: Loaded image wth ${pxlCount} pixels to ${x}/${y}`); modtoolsLogger.info(`ADMINTOOLS: Loaded image wth ${pxlCount} pixels to ${x}/${y}`);
return [ return [
200, 200,
`Successfully loaded image wth ${pxlCount} pixels to ${x}/${y}`, `Successfully loaded image wth ${pxlCount} pixels to ${x}/${y}`,
@ -238,7 +238,7 @@ export async function executeProtAction(
height, height,
protect, protect,
); );
admintoolsLogger.info( modtoolsLogger.info(
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
`ADMINTOOLS: Set protect to ${protect} for ${pxlCount} pixels at ${x} / ${y} with dimension ${width}x${height}`, `ADMINTOOLS: Set protect to ${protect} for ${pxlCount} pixels at ${x} / ${y} with dimension ${width}x${height}`,
); );
@ -333,7 +333,7 @@ export async function executeRollback(
height, height,
date, date,
); );
admintoolsLogger.info( modtoolsLogger.info(
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
`ADMINTOOLS: Rollback to ${date} for ${pxlCount} pixels at ${x} / ${y} with dimension ${width}x${height}`, `ADMINTOOLS: Rollback to ${date} for ${pxlCount} pixels at ${x} / ${y} with dimension ${width}x${height}`,
); );

View File

@ -48,7 +48,7 @@ export const LOG_MYSQL = parseInt(process.env.LOG_MYSQL, 10) || false;
export const HOURLY_EVENT = parseInt(process.env.HOURLY_EVENT, 10) || false; export const HOURLY_EVENT = parseInt(process.env.HOURLY_EVENT, 10) || false;
// Accounts // Accounts
export const APISOCKET_KEY = process.env.APISOCKET_KEY || 'changethis'; export const APISOCKET_KEY = process.env.APISOCKET_KEY || null;
// Comma seperated list of user ids of Admins // Comma seperated list of user ids of Admins
export const ADMIN_IDS = (process.env.ADMIN_IDS) export const ADMIN_IDS = (process.env.ADMIN_IDS)
? process.env.ADMIN_IDS.split(',').map((z) => parseInt(z, 10)) : []; ? process.env.ADMIN_IDS.split(',').map((z) => parseInt(z, 10)) : [];

View File

@ -48,12 +48,12 @@ export const proxyLogger = createLogger({
], ],
}); });
export const admintoolsLogger = createLogger({ export const modtoolsLogger = createLogger({
format: format.printf(({ message }) => message), format: format.printf(({ message }) => message),
transports: [ transports: [
new DailyRotateFile({ new DailyRotateFile({
level: 'info', level: 'info',
filename: './log/admintools-%DATE%.log', filename: './log/modtools-%DATE%.log',
maxSize: '20m', maxSize: '20m',
maxFiles: '14d', maxFiles: '14d',
colorize: false, colorize: false,

View File

@ -14,21 +14,17 @@ export default async function getMe(user, lang = 'default') {
const userdata = user.getUserData(); const userdata = user.getUserData();
// sanitize data // sanitize data
const { const {
name, mailVerified, minecraftname, mcVerified, name, mailVerified,
} = userdata; } = userdata;
if (!name) userdata.name = null; if (!name) userdata.name = null;
const messages = []; const messages = [];
if (name && !mailVerified) { if (name && !mailVerified) {
messages.push('not_verified'); messages.push('not_verified');
} }
if (minecraftname && !mcVerified) {
messages.push('not_mc_verified');
}
if (messages.length > 0) { if (messages.length > 0) {
userdata.messages = messages; userdata.messages = messages;
} }
delete userdata.mailVerified; delete userdata.mailVerified;
delete userdata.mcVerified;
userdata.canvases = getLocalicedCanvases(lang); userdata.canvases = getLocalicedCanvases(lang);
userdata.channels = { userdata.channels = {

View File

@ -26,6 +26,14 @@ export function getRandomInt(min, max) {
return min + (Math.floor(Math.random() * range)); return min + (Math.floor(Math.random() * range));
} }
/*
* generates random string with a-z,0-9
* 11 chars length
*/
export function getRandomString() {
return Math.random().toString(36).substring(2, 15);
}
export function distMax([x1, y1], [x2, y2]) { export function distMax([x1, y1], [x2, y2]) {
return Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2)); return Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2));
} }

View File

@ -88,16 +88,6 @@ const RegUser = Model.define('User', {
allowNull: true, allowNull: true,
}, },
minecraftid: {
type: DataType.CHAR(36),
allowNull: true,
},
minecraftname: {
type: DataType.CHAR(16),
allowNull: true,
},
// when mail verification got requested, // when mail verification got requested,
// used for purging unverified accounts // used for purging unverified accounts
verificationReqAt: { verificationReqAt: {
@ -125,10 +115,6 @@ const RegUser = Model.define('User', {
return this.verified & 0x01; return this.verified & 0x01;
}, },
mcVerified(): boolean {
return this.verified & 0x02;
},
blockDm(): boolean { blockDm(): boolean {
return this.blocks & 0x01; return this.blocks & 0x01;
}, },
@ -144,11 +130,6 @@ const RegUser = Model.define('User', {
this.setDataValue('verified', val); this.setDataValue('verified', val);
}, },
mcVerified(num: boolean) {
const val = (num) ? (this.verified | 0x02) : (this.verified & ~0x02);
this.setDataValue('verified', val);
},
blockDm(num: boolean) { blockDm(num: boolean) {
const val = (num) ? (this.blocks | 0x01) : (this.blocks & ~0x01); const val = (num) ? (this.blocks | 0x01) : (this.blocks & ~0x01);
this.setDataValue('blocks', val); this.setDataValue('blocks', val);

View File

@ -223,8 +223,6 @@ class User {
return { return {
name: null, name: null,
mailVerified: false, mailVerified: false,
mcVerified: false,
minecraftname: null,
blockDm: false, blockDm: false,
totalPixels: 0, totalPixels: 0,
dailyTotalPixels: 0, dailyTotalPixels: 0,
@ -242,8 +240,6 @@ class User {
return { return {
name: regUser.name, name: regUser.name,
mailVerified: regUser.mailVerified, mailVerified: regUser.mailVerified,
mcVerified: regUser.mcVerified,
minecraftname: regUser.minecraftname,
blockDm: regUser.blockDm, blockDm: regUser.blockDm,
totalPixels: regUser.totalPixels, totalPixels: regUser.totalPixels,
dailyTotalPixels: regUser.dailyTotalPixels, dailyTotalPixels: regUser.dailyTotalPixels,

View File

@ -0,0 +1,10 @@
# AdminAPI
API which is used for connecting with external applications and bridges to fetch informations.
Modtools are in ./api
Connections to here just possible with header:
```
Authorization: "Bearer APISOCKETKEY"
```

View File

@ -0,0 +1,143 @@
import express from 'express';
import logger from '../../core/logger';
import RegUser from '../../data/models/RegUser';
import { getIPFromRequest } from '../../utils/ip';
import { compareToHash } from '../../utils/hash';
import { APISOCKET_KEY } from '../../core/config';
const router = express.Router();
/*
* Need APISOCKETKEY to access
*/
router.use(async (req, res, next) => {
const { headers } = req;
if (!headers.authorization
|| !APISOCKET_KEY
|| headers.authorization !== `Bearer ${APISOCKET_KEY}`) {
const ip = getIPFromRequest(req);
logger.warn(`API adminapi request from ${ip} rejected`);
res.status(401);
res.json({
success: false,
errors: ['No or invalid authorization header'],
});
return;
}
next();
});
router.use(express.json());
/*
* check login credentials
* useful for 3rd party login
*/
router.post('/checklogin', async (req, res) => {
const errors = [];
const { password } = req.body;
if (!password) {
errors.push('No password given');
}
const query = {
attributes: [
'id',
'name',
'email',
'password',
],
};
let userString;
if (req.body.name) {
query.where = { name: req.body.name };
userString = req.body.name;
} else if (req.body.email) {
query.where = { email: req.body.email };
userString = req.body.email;
} else if (req.body.id) {
query.where = { id: req.body.id };
userString = String(req.body.id);
} else {
errors.push('No name or email given');
}
if (errors.length) {
res.status(400);
res.json({
success: false,
errors,
});
return;
}
const reguser = await RegUser.findOne(query);
if (!reguser) {
res.json({
success: false,
errors: [`User ${userString} does not exist`],
});
return;
}
if (!compareToHash(password, reguser.password)) {
logger.info(
`ADMINAPI: User ${reguser.name} / ${reguser.id} entered wronng password`,
);
res.json({
success: false,
errors: [`Password wrong for user ${userString}`],
});
return;
}
logger.info(`ADMINAPI: User ${reguser.name} / ${reguser.id} got loged in`);
res.json({
success: true,
userdata: {
id: reguser.id,
name: reguser.name,
email: reguser.email,
},
});
});
/*
* get user data
*/
router.post('/userdata', async (req, res) => {
const { id } = req.body;
if (!id) {
res.status(400);
res.json({
success: false,
errors: ['No id given'],
});
return;
}
const reguser = await RegUser.findOne({
where: {
id,
},
});
if (!reguser) {
res.json({
success: false,
errors: ['No such user'],
});
return;
}
res.json({
success: true,
userdata: {
id: reguser.id,
name: reguser.name,
email: reguser.email,
},
});
});
export default router;

3
src/routes/api/README.md Normal file
View File

@ -0,0 +1,3 @@
# API
api for accounts, requests that do not require login data or accounts should not be here

View File

@ -16,14 +16,19 @@ export default async (req: Request, res: Response) => {
const { t } = req.ttag; const { t } = req.ttag;
try { try {
const { text } = req.body; const { text, id } = req.body;
if (!text) { if (!text) {
res.status(400) res.status(400)
.json({ errors: [t`No captcha text given`] }); .json({ errors: [t`No captcha text given`] });
return; return;
} }
if (!id) {
res.status(400)
.json({ errors: [t`No captcha id given`] });
return;
}
const ret = await checkCaptchaSolution(text, ip); const ret = await checkCaptchaSolution(text, ip, id);
switch (ret) { switch (ret) {
case 0: case 0:

View File

@ -1,13 +1,7 @@
/**
* @flow
*/
import express from 'express'; import express from 'express';
import bodyParser from 'body-parser';
import session from '../../core/session'; import session from '../../core/session';
import passport from '../../core/passport'; import passport from '../../core/passport';
import { expressTTag } from '../../core/ttag';
import logger from '../../core/logger'; import logger from '../../core/logger';
import User from '../../data/models/User'; import User from '../../data/models/User';
import { getIPFromRequest } from '../../utils/ip'; import { getIPFromRequest } from '../../utils/ip';
@ -15,23 +9,17 @@ import { getIPFromRequest } from '../../utils/ip';
import me from './me'; import me from './me';
import captcha from './captcha'; import captcha from './captcha';
import auth from './auth'; import auth from './auth';
import ranking from './ranking';
import history from './history';
import chatHistory from './chathistory'; import chatHistory from './chathistory';
import startDm from './startdm'; import startDm from './startdm';
import leaveChan from './leavechan'; import leaveChan from './leavechan';
import block from './block'; import block from './block';
import blockdm from './blockdm'; import blockdm from './blockdm';
import modtools from './modtools';
const router = express.Router(); const router = express.Router();
// this route doesn't need passport router.use(express.json());
router.get('/ranking', ranking);
router.get('/history', history);
router.use(bodyParser.json());
router.use((err, req, res, next) => { router.use((err, req, res, next) => {
if (err) { if (err) {
@ -43,11 +31,6 @@ router.use((err, req, res, next) => {
} }
}); });
/*
* make localisations available
*/
router.use(expressTTag);
// captcah doesn't need a user // captcah doesn't need a user
router.post('/captcha', captcha); router.post('/captcha', captcha);
@ -72,6 +55,12 @@ router.use(session);
router.use(passport.initialize()); router.use(passport.initialize());
router.use(passport.session()); router.use(passport.session());
/*
* modtools
* (does not json bodies, but urlencoded)
*/
router.use('/modtools', modtools);
/* /*
* create dummy user with just ip if not * create dummy user with just ip if not
* logged in * logged in

View File

@ -2,15 +2,12 @@
* *
* starts a DM session * starts a DM session
* *
* @flow
*/ */
import type { Request, Response } from 'express';
import logger from '../../core/logger'; import logger from '../../core/logger';
import socketEvents from '../../socket/SocketEvents'; import socketEvents from '../../socket/SocketEvents';
async function leaveChan(req: Request, res: Response) { async function leaveChan(req, res) {
const channelId = parseInt(req.body.channelId, 10); const channelId = parseInt(req.body.channelId, 10);
const { user } = req; const { user } = req;

View File

@ -1,6 +1,6 @@
/** /**
* basic admin api * basic mod api
* is used by ../components/Admintools * is used by ../components/Modtools
* *
* @flow * @flow
* *
@ -8,13 +8,10 @@
import express from 'express'; import express from 'express';
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import bodyParser from 'body-parser';
import multer from 'multer'; import multer from 'multer';
import { getIPFromRequest } from '../utils/ip'; import { getIPFromRequest } from '../../utils/ip';
import session from '../core/session'; import { modtoolsLogger } from '../../core/logger';
import passport from '../core/passport';
import { admintoolsLogger } from '../core/logger';
import { import {
executeIPAction, executeIPAction,
executeImageAction, executeImageAction,
@ -23,7 +20,7 @@ import {
getModList, getModList,
removeMod, removeMod,
makeMod, makeMod,
} from '../core/adminfunctions'; } from '../../core/adminfunctions';
const router = express.Router(); const router = express.Router();
@ -32,7 +29,7 @@ const router = express.Router();
* multer middleware for getting POST parameters * multer middleware for getting POST parameters
* into req.file (if file) and req.body for text * into req.file (if file) and req.body for text
*/ */
router.use(bodyParser.urlencoded({ extended: true })); router.use(express.urlencoded({ extended: true }));
const upload = multer({ const upload = multer({
limits: { limits: {
fileSize: 5 * 1024 * 1024, fileSize: 5 * 1024 * 1024,
@ -41,18 +38,16 @@ const upload = multer({
/* /*
* make sure User is logged in and mod or admin * make sure User is logged in and mod or mod
*/ */
router.use(session);
router.use(passport.initialize());
router.use(passport.session());
router.use(async (req, res, next) => { router.use(async (req, res, next) => {
const ip = getIPFromRequest(req); const ip = getIPFromRequest(req);
if (!req.user) { if (!req.user) {
admintoolsLogger.info( modtoolsLogger.info(
`ADMINTOOLS: ${ip} tried to access admintools without login`, `MODTOOLS: ${ip} tried to access modtools without login`,
); );
res.status(403).send('You are not logged in'); const { t } = req.ttag;
res.status(403).send(t`You are not logged in`);
return; return;
} }
/* /*
@ -60,14 +55,15 @@ router.use(async (req, res, next) => {
* 2 = Mod * 2 = Mod
*/ */
if (!req.user.userlvl) { if (!req.user.userlvl) {
admintoolsLogger.info( modtoolsLogger.info(
`ADMINTOOLS: ${ip} / ${req.user.id} tried to access admintools`, `MODTOOLS: ${ip} / ${req.user.id} tried to access modtools`,
); );
res.status(403).send('You are not allowed to access this page'); const { t } = req.ttag;
res.status(403).send(t`You are not allowed to access this page`);
return; return;
} }
admintoolsLogger.info( modtoolsLogger.info(
`ADMINTOOLS: ${req.user.id} / ${req.user.regUser.name} is using admintools`, `MODTOOLS: ${req.user.id} / ${req.user.regUser.name} is using modtools`,
); );
next(); next();
@ -128,7 +124,8 @@ router.post('/', upload.single('image'), async (req, res, next) => {
*/ */
router.use(async (req, res, next) => { router.use(async (req, res, next) => {
if (req.user.userlvl !== 1) { if (req.user.userlvl !== 1) {
res.status(403).send('Just admins can do that'); const { t } = req.ttag;
res.status(403).send(t`Just admins can do that`);
return; return;
} }
next(); next();

View File

@ -6,7 +6,7 @@
import fs from 'fs'; import fs from 'fs';
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import { BACKUP_DIR } from '../../core/config'; import { BACKUP_DIR } from '../core/config';
async function history(req: Request, res: Response) { async function history(req: Request, res: Response) {
const { day, id } = req.query; const { day, id } = req.query;

View File

@ -3,16 +3,133 @@
* @flow * @flow
*/ */
import api from './api'; import express from 'express';
import etag from 'etag';
import path from 'path';
import ranking from './ranking';
import history from './history';
import tiles from './tiles'; import tiles from './tiles';
import chunks from './chunks'; import chunks from './chunks';
import admintools from './admintools';
import resetPassword from './reset_password'; import resetPassword from './reset_password';
import api from './api';
import adminapi from './adminapi';
export { import assets from './assets.json'; // eslint-disable-line import/no-unresolved
api, import { expressTTag } from '../core/ttag';
tiles, import generateGlobePage from '../ssr-components/Globe';
import generateMainPage from '../ssr-components/Main';
import { MONTH } from '../core/constants';
import { GUILDED_INVITE } from '../core/config';
const router = express.Router();
/*
* ranking of pixels placed
* daily and total
*/
router.get('/ranking', ranking);
/*
* give: date per query
* returns: array of HHMM backups available
*/
router.get('/history', history);
/*
* zoomed tiles
*/
router.use('/tiles', tiles);
/*
* adminapi
*/
router.use('/adminapi', adminapi);
//
// public folder
// (this should be served with nginx or other webserver)
// -----------------------------------------------------------------------------
router.use(express.static(path.join(__dirname, 'public'), {
maxAge: 3 * MONTH,
extensions: ['html'],
}));
/*
* Redirect to guilded
*/
router.use('/guilded', (req, res) => {
res.redirect(GUILDED_INVITE);
});
/*
* Serving Chunks
*/
router.get(
'/chunks/:c([0-9]+)/:x([0-9]+)/:y([0-9]+)(/)?:z([0-9]+)?.bmp',
chunks, chunks,
admintools, );
resetPassword,
}; /*
* Following with translations
* ---------------------------------------------------------------------------
*/
router.use(expressTTag);
/*
* API calls
*/
router.use('/api', api);
/*
* Password Reset Link
*/
router.use('/reset_password', resetPassword);
//
// 3D Globe (react generated)
// -----------------------------------------------------------------------------
const globeEtag = etag(
assets.globe.js.join('_'),
{ weak: true },
);
router.get('/globe', async (req, res) => {
res.set({
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
'Content-Type': 'text/html; charset=utf-8',
ETag: globeEtag,
});
if (req.headers['if-none-match'] === globeEtag) {
res.status(304).end();
return;
}
res.status(200).send(generateGlobePage(req.lang));
});
//
// Main Page (react generated)
// -----------------------------------------------------------------------------
const indexEtag = etag(
assets.client.js.join('_'),
{ weak: true },
);
router.get('/', async (req, res) => {
res.set({
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
'Content-Type': 'text/html; charset=utf-8',
ETag: indexEtag,
});
if (req.headers['if-none-match'] === indexEtag) {
res.status(304).end();
return;
}
res.status(200).send(generateMainPage(req.lang));
});
export default router;

View File

@ -5,7 +5,7 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import rankings from '../../core/ranking'; import rankings from '../core/ranking';
export default async (req: Request, res: Response) => { export default async (req: Request, res: Response) => {

View File

@ -5,7 +5,6 @@
*/ */
import express from 'express'; import express from 'express';
import bodyParser from 'body-parser';
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
@ -21,7 +20,7 @@ const router = express.Router();
/* /*
* decode form data to req.body * decode form data to req.body
*/ */
router.use(bodyParser.urlencoded({ extended: true })); router.use(express.urlencoded({ extended: true }));
/* /*

View File

@ -1,43 +1,31 @@
/* @flow */ /*
* Entrypoint for main server script
*/
import url from 'url'; import url from 'url';
import path from 'path';
import compression from 'compression'; import compression from 'compression';
import express from 'express'; import express from 'express';
import http from 'http'; import http from 'http';
import etag from 'etag';
// import baseCss from './components/base.tcss'; // import baseCss from './components/base.tcss';
import forceGC from './core/forceGC'; import forceGC from './core/forceGC';
import assets from './assets.json'; // eslint-disable-line import/no-unresolved
import logger from './core/logger'; import logger from './core/logger';
import rankings from './core/ranking'; import rankings from './core/ranking';
import models from './data/models'; import models from './data/models';
import routes from './routes';
import chatProvider from './core/ChatProvider'; import chatProvider from './core/ChatProvider';
import { expressTTag } from './core/ttag';
import SocketServer from './socket/SocketServer'; import SocketServer from './socket/SocketServer';
import APISocketServer from './socket/APISocketServer'; import APISocketServer from './socket/APISocketServer';
import {
api,
tiles,
chunks,
admintools,
resetPassword,
} from './routes';
import generateGlobePage from './ssr-components/Globe';
import generateMainPage from './ssr-components/Main';
import { SECOND, MONTH } from './core/constants'; import { PORT, HOST } from './core/config';
import { PORT, HOST, GUILDED_INVITE } from './core/config'; import { SECOND } from './core/constants';
import { startAllCanvasLoops } from './core/tileserver'; import { startAllCanvasLoops } from './core/tileserver';
startAllCanvasLoops(); startAllCanvasLoops();
const app = express(); const app = express();
app.disable('x-powered-by'); app.disable('x-powered-by');
@ -70,19 +58,6 @@ function wsupgrade(request, socket, head) {
} }
server.on('upgrade', wsupgrade); server.on('upgrade', wsupgrade);
//
// API
// -----------------------------------------------------------------------------
app.use('/api', api);
//
// Serving Zoomed Tiless
// -----------------------------------------------------------------------------
app.use('/tiles', tiles);
/* /*
* use gzip compression for following calls * use gzip compression for following calls
/* level from -1 (default, 6) to 0 (no) from 1 (fastest) to 9 (best) /* level from -1 (default, 6) to 0 (no) from 1 (fastest) to 9 (best)
@ -98,93 +73,7 @@ app.use(compression({
}, },
})); }));
app.use(routes);
//
// public folder
// (this should be served with nginx or other webserver)
// -----------------------------------------------------------------------------
app.use(express.static(path.join(__dirname, 'public'), {
maxAge: 3 * MONTH,
extensions: ['html'],
}));
//
// Redirecct to guilded
// -----------------------------------------------------------------------------
app.use('/guilded', (req, res) => {
res.redirect(GUILDED_INVITE);
});
//
// Serving Chunks
// -----------------------------------------------------------------------------
app.get('/chunks/:c([0-9]+)/:x([0-9]+)/:y([0-9]+)(/)?:z([0-9]+)?.bmp', chunks);
//
// Admintools
// -----------------------------------------------------------------------------
app.use('/admintools', admintools);
/*
* decide which language to use
*/
app.use(expressTTag);
//
// Password Reset Link
// -----------------------------------------------------------------------------
app.use('/reset_password', resetPassword);
//
// 3D Globe (react generated)
// -----------------------------------------------------------------------------
const globeEtag = etag(
assets.globe.js.join('_'),
{ weak: true },
);
app.get('/globe', async (req, res) => {
res.set({
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
'Content-Type': 'text/html; charset=utf-8',
ETag: globeEtag,
});
if (req.headers['if-none-match'] === globeEtag) {
res.status(304).end();
return;
}
res.status(200).send(generateGlobePage(req.lang));
});
//
// Main Page (react generated)
// -----------------------------------------------------------------------------
const indexEtag = etag(
assets.client.js.join('_'),
{ weak: true },
);
app.get('/', async (req, res) => {
res.set({
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
'Content-Type': 'text/html; charset=utf-8',
ETag: indexEtag,
});
if (req.headers['if-none-match'] === indexEtag) {
res.status(304).end();
return;
}
res.status(200).send(generateMainPage(req.lang));
});
// //
// ip config // ip config

View File

@ -26,6 +26,7 @@ async function verifyClient(info, done) {
const ip = getIPFromRequest(req); const ip = getIPFromRequest(req);
if (!headers.authorization if (!headers.authorization
|| !APISOCKET_KEY
|| headers.authorization !== `Bearer ${APISOCKET_KEY}`) { || headers.authorization !== `Bearer ${APISOCKET_KEY}`) {
logger.warn(`API ws request from ${ip} authenticated`); logger.warn(`API ws request from ${ip} authenticated`);
return done(false); return done(false);

View File

@ -71,13 +71,17 @@ function evaluateResult(captchaText, userText) {
* *
* @param text Solution of captcha * @param text Solution of captcha
* @param ip * @param ip
* @param ttl time to be valid in seconds * @param captchaid
*/ */
export function setCaptchaSolution( export function setCaptchaSolution(
text, text,
ip, ip,
captchaid = null,
) { ) {
const key = `capt:${ip}`; let key = `capt:${ip}`;
if (captchaid) {
key += `:${captchaid}`;
}
return redis.setAsync(key, text, 'EX', CAPTCHA_TIMEOUT); return redis.setAsync(key, text, 'EX', CAPTCHA_TIMEOUT);
} }
@ -93,9 +97,13 @@ export function setCaptchaSolution(
export async function checkCaptchaSolution( export async function checkCaptchaSolution(
text, text,
ip, ip,
captchaid = null,
) { ) {
const ipn = getIPv6Subnet(ip); const ipn = getIPv6Subnet(ip);
const key = `capt:${ip}`; let key = `capt:${ip}`;
if (captchaid) {
key += `:${captchaid}`;
}
const solution = await redis.getAsync(key); const solution = await redis.getAsync(key);
if (solution) { if (solution) {
if (evaluateResult(solution.toString('utf8'), text)) { if (evaluateResult(solution.toString('utf8'), text)) {
@ -109,7 +117,7 @@ export async function checkCaptchaSolution(
); );
return 2; return 2;
} }
logger.info(`CAPTCHA ${ip} timed out`); logger.info(`CAPTCHA ${ip}:${captchaid} timed out`);
return 1; return 1;
} }

4
utils/synapse/deploy.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
scp ./testmodule.py root@pixelplanet:/etc/matrix-synapse/testmodule.py
ssh root@pixelplanet 'systemctl restart matrix-synapse'

141
utils/synapse/testmodule.py Normal file
View File

@ -0,0 +1,141 @@
#
# Python3 Module for matrix-synapse
#
from typing import Awaitable, Callable, Optional, Tuple
import logging
import synapse
from synapse import module_api
logger = logging.getLogger(__name__)
class MyAuthProvider:
def __init__(self, config: dict, api: module_api):
self.api = api
if 'ppfunurl' not in config:
raise Exception('Pixelplanet ppfunurl not configured')
self.ppfunurl = config["ppfunurl"]
if 'apisocketkey' not in config:
raise Exception('Pixelplanet apisocketkey not configured')
self.apisocketkey = config["apisocketkey"]
self.credentials = {
"bob": "building",
"@scoop:matrix.org": "digging",
}
api.register_password_auth_provider_callbacks(
check_3pid_auth = self.check_3pid_pass,
auth_checkers = {
("m.login.password", ("password",)): self.check_pass,
},
)
async def check_credentials(
self,
query,
) -> Optional[
Tuple[
int,
str,
Optional[str]
]
]:
try:
resp = await self.api.http_client.post_json_get_json(
self.ppfunurl + '/adminapi/checklogin',
query,
[[ "authorization", ['Bearer ' + self.apisocketkey] ]],
)
if not resp["success"]:
raise Exception(resp["errors"][0])
userdata = resp['userdata']
return (userdata['id'], userdata['name'], userdata['email'])
except Exception as e:
logger.warning('Could not login via ppfun: %s', e)
return None
async def login(
self,
ppfun_id,
ppfun_name,
ppfun_email,
) -> Optional[
Tuple[
str,
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
]
]:
localpart = f'pp_{ppfun_id}'
user_id = self.api.get_qualified_user_id(localpart)
logger.info('check if user %s exists', user_id)
does_exist = await self.api.check_user_exists(user_id)
if does_exist is None:
logger.info('User %s does not exist yet, registering new user', user_id)
emails = None
if ppfun_email:
emails = [ppfun_email]
try:
user_id = await self.api.register_user(localpart, ppfun_name, emails)
except Exception as e:
logger.warning('Could not create user %s, because %s', user_id, e)
return None
logger.info('User %s logged in via ppfun %s', user_id, ppfun_name)
return user_id, None
async def check_3pid_pass(
self,
medium: str,
address: str,
password: str,
) -> Optional[
Tuple[
str,
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
]
]:
if medium != "email" or not address:
return None
ppfun_userdata = await self.check_credentials({
"email": address,
"password": password
})
if ppfun_userdata is not None:
logger.info('User %s logging in with ppfun credentials', ppfun_userdata)
ret = await self.login(*ppfun_userdata)
return ret
return None
async def check_pass(
self,
username: str,
login_type: str,
login_dict: "synapse.module_api.JsonDict",
) -> Optional[
Tuple[
str,
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
]
]:
if login_type != "m.login.password":
return None
query = {
"password": login_dict.get('password')
}
if username.startswith('@pp_'):
query['id'] = username[4: username.index(':')]
elif username.startswith('pp_') and username.endswith(f':{self.api._server_name}'):
query['id'] = username[3: username.index(':')]
else:
query['name'] = username
ppfun_userdata = await self.check_credentials(query)
if ppfun_userdata is not None:
logger.info('User %s logging in with ppfun credentials', ppfun_userdata)
ret = await self.login(*ppfun_userdata)
return ret
return None