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
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:
`https://[old.]pixelplanet.fun/mcws`
`https://[dev.]pixelplanet.fun/mcws`
Connection just possible with header:

View File

@ -64,30 +64,6 @@ msgstr "Dein Land ist temporär stummgeschaltet"
msgid "Stop flooding."
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
msgid "You sent an empty password or invalid data :("
msgstr "Du hast ein ungültiges Passwort oder Daten gesendet :("
@ -120,6 +96,30 @@ msgstr ""
"Dieser Passwort Wiederherstellungslink ist falsch oder abgelaufen, bitte "
"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
#, javascript-format
msgid ""
@ -243,6 +243,38 @@ msgstr "PixelPlanet.Fun Passwort Wiederherstellung"
msgid "Reset your password here"
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
msgid "Email can't be empty."
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."
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
msgid "E-Mail already in use."
msgstr "E-Mail wird bereits verwendet."

View File

@ -53,6 +53,44 @@ msgstr ""
msgid "Stop flooding."
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
msgid "Double click on globe to go back."
msgstr ""
@ -69,44 +107,6 @@ msgstr ""
msgid "A 3D globe of our whole map"
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
#, javascript-format
msgid ""
@ -218,26 +218,6 @@ msgstr ""
msgid "Reset your password here"
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
msgid "Email can't be empty."
msgstr ""
@ -286,6 +266,38 @@ msgstr ""
msgid "Password must be shorter than 60 characters."
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
msgid "E-Mail already in use."
msgstr ""
@ -302,6 +314,10 @@ msgstr ""
msgid "Failed to establish session after register :("
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_passwd.js:37
#: src/routes/api/auth/delete_account.js:38
@ -314,23 +330,6 @@ msgstr ""
msgid "Incorrect password!"
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:32
msgid "Mail verification"
@ -346,6 +345,19 @@ msgid ""
"request a new one."
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
msgid "Earth"
msgstr ""

View File

@ -45,22 +45,6 @@ msgstr ""
msgid "Hide Hidden Canvases"
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
msgid "Error :("
msgstr ""
@ -170,6 +154,22 @@ msgstr ""
msgid "Can't render 3D canvas, do you have WebGL2 disabled?"
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/CoordinatesBox.jsx:32
msgid "Copy to Clipboard"
@ -187,8 +187,8 @@ msgstr ""
msgid "Pixels placed"
msgstr ""
#: src/components/Admintools.jsx:224
#: src/components/ModalRoot.jsx:69
#: src/components/Modtools.jsx:224
#: src/components/Window.jsx:138
#: src/components/contextmenus/ChannelContextMenu.jsx:67
msgid "Close"
@ -198,27 +198,6 @@ msgstr ""
msgid "Restore"
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
msgid "You made too many requests"
msgstr ""
@ -248,6 +227,27 @@ msgstr ""
msgid "Server answered with gibberish :("
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
msgid "Loading"
msgstr ""
@ -256,6 +256,25 @@ msgstr ""
msgid "Select Date above"
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
msgid "Clone"
msgstr ""
@ -272,29 +291,6 @@ msgstr ""
msgid "Resize"
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
msgid "Close Palette"
msgstr ""
@ -303,6 +299,10 @@ msgstr ""
msgid "Open Palette"
msgstr ""
#: src/components/buttons/GlobeButton.jsx:35
msgid "Globe View"
msgstr ""
#: src/components/contextmenus/ChannelContextMenu.jsx:55
msgid "Mute"
msgstr ""
@ -384,6 +384,225 @@ msgstr ""
msgid "Send"
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
msgid "your IP"
msgstr ""
@ -561,229 +780,6 @@ msgstr ""
msgid "Credit for the Palette of the Top10 canvas goes to ${ vinikLink }."
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
msgid "Email can't be empty."
msgstr ""
@ -832,6 +828,13 @@ msgstr ""
msgid "Password must be shorter than 60 characters."
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
msgid "Online Users"
msgstr ""
@ -877,30 +880,6 @@ msgstr ""
msgid "Dimensions"
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
msgid "Todays Placed Pixels"
msgstr ""
@ -958,81 +937,81 @@ msgstr ""
msgid "Ranking updates every 5 min. Daily rankings get reset at midnight UTC."
msgstr ""
#: src/components/Admintools.jsx:184
#: src/components/Modtools.jsx:184
msgid "Build image on canvas."
msgstr ""
#: src/components/Admintools.jsx:187
#: src/components/Modtools.jsx:187
msgid "Build image and set it to protected."
msgstr ""
#: src/components/Admintools.jsx:190
#: src/components/Modtools.jsx:190
msgid "Build image, but reset cooldown to unset-pixel cd."
msgstr ""
#: src/components/Admintools.jsx:253
#: src/components/Modtools.jsx:253
msgid "Image Upload"
msgstr ""
#: src/components/Admintools.jsx:254
#: src/components/Modtools.jsx:254
msgid "Upload images to canvas"
msgstr ""
#: src/components/Admintools.jsx:256
#: src/components/Modtools.jsx:256
msgid "File"
msgstr ""
#: src/components/Admintools.jsx:276
#: src/components/Modtools.jsx:276
msgid "Coordinates in X_Y format:"
msgstr ""
#: src/components/Admintools.jsx:316
#: src/components/Modtools.jsx:316
msgid "Pixel Protection"
msgstr ""
#: src/components/Admintools.jsx:318
#: src/components/Modtools.jsx:318
msgid ""
"Set protection of areas (if you need finer grained control, "
"use protect with image upload and alpha layers)"
msgstr ""
#: src/components/Admintools.jsx:398
#: src/components/Modtools.jsx:398
msgid "Rollback to Date"
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)"
msgstr ""
#: src/components/Admintools.jsx:475
#: src/components/Modtools.jsx:475
msgid "IP Actions"
msgstr ""
#: src/components/Admintools.jsx:477
#: src/components/Modtools.jsx:477
msgid "Do stuff with IPs (one IP per line)"
msgstr ""
#: src/components/Admintools.jsx:516
#: src/components/Modtools.jsx:516
msgid "Manage Moderators"
msgstr ""
#: src/components/Admintools.jsx:518
#: src/components/Modtools.jsx:518
msgid "Remove Moderator"
msgstr ""
#: src/components/Admintools.jsx:550
#: src/components/Modtools.jsx:550
msgid "There are no mods"
msgstr ""
#: src/components/Admintools.jsx:555
#: src/components/Modtools.jsx:555
msgid "Assign new Mod"
msgstr ""
#: src/components/Admintools.jsx:558
#: src/components/Modtools.jsx:558
msgid "Enter UserName of new Mod"
msgstr ""
#: src/components/Admintools.jsx:567
#: src/components/Modtools.jsx:567
msgid "User Name"
msgstr ""
@ -1114,33 +1093,28 @@ msgstr ""
msgid "Download Template"
msgstr ""
#: src/components/ChangeMail.jsx:91
#: src/components/ChangeName.jsx:68
#: src/components/ChangePassword.jsx:110
#: src/components/LanguageSelect.jsx:73
msgid "Save"
#: src/components/LogInArea.jsx:21
msgid "Login to access more features and stats."
msgstr ""
#: src/components/LogInForm.jsx:76
msgid "Name or Email"
#: src/components/LogInArea.jsx:23
msgid "Login with Name or Mail:"
msgstr ""
#: src/components/LogInForm.jsx:87
msgid "LogIn"
#: src/components/LogInArea.jsx:30
msgid "I forgot my Password."
msgstr ""
#: src/components/UserMessages.jsx:28
msgid ""
"Please verify your mail address \n"
"or your account could get deleted after a few days."
#: src/components/LogInArea.jsx:31
msgid "or login with:"
msgstr ""
#: src/components/UserMessages.jsx:49
msgid "A new verification mail is getting sent to you."
#: src/components/LogInArea.jsx:72
msgid "or register here:"
msgstr ""
#: src/components/UserMessages.jsx:53
msgid "Click here to request a new verification mail."
#: src/components/LogInArea.jsx:79
msgid "Register"
msgstr ""
#: src/components/ChangePassword.jsx:22
@ -1163,10 +1137,6 @@ msgstr ""
msgid "Confirm New Password"
msgstr ""
#: src/components/ChangeName.jsx:64
msgid "New Username"
msgstr ""
#: src/components/ChangeMail.jsx:59
msgid ""
"Changed Mail successfully. We sent you a verification mail, "
@ -1177,6 +1147,10 @@ msgstr ""
msgid "New Mail"
msgstr ""
#: src/components/DeleteAccount.jsx:66
msgid "Yes, Delete My Account!"
msgstr ""
#: src/components/SocialSettings.jsx:38
msgid "Block all Private Messages"
msgstr ""
@ -1189,8 +1163,30 @@ msgstr ""
msgid "You have no users blocked"
msgstr ""
#: src/components/DeleteAccount.jsx:66
msgid "Yes, Delete My Account!"
#: src/components/UserMessages.jsx:28
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 ""
#: src/components/windows/Help.jsx:14
@ -1205,6 +1201,11 @@ msgctxt "keybinds"
msgid "X"
msgstr ""
#: src/components/windows/Settings.jsx:150
msgctxt "keybinds"
msgid "M"
msgstr ""
#: src/components/windows/Help.jsx:16
#: src/components/windows/Settings.jsx:205
msgctxt "keybinds"
@ -1254,9 +1255,4 @@ msgstr ""
#: src/components/windows/Help.jsx:31
msgctxt "keybinds"
msgid "C"
msgstr ""
#: src/components/windows/Settings.jsx:150
msgctxt "keybinds"
msgid "M"
msgstr ""

1
package-lock.json generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,10 +8,12 @@ import path from 'path';
import fs from 'fs';
import process from 'process';
import http from 'http';
import url from 'url';
import ppfunCaptcha from 'ppfun-captcha';
import { getIPFromRequest } from './utils/ip';
import { setCaptchaSolution } from './utils/captcha';
import { getRandomString } from './core/utils';
const PORT = process.env.PORT || 8080;
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 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,
console.log(req.url);
req.on('error', (err) => {
console.error(err);
});
const ip = getIPFromRequest(req);
const urlObject = url.parse(req.url, true);
setCaptchaSolution(captcha.text, ip);
console.log(`Serving ${captcha.text} to ${ip}`);
if (req.method === 'GET' && urlObject.pathname.endsWith('.svg')) {
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, {
'Content-Type': 'image/svg+xml',
'Cache-Control': 'no-cache',
});
res.write(captcha.data);
res.end();
const ip = getIPFromRequest(req);
const captchaid = getRandomString();
setCaptchaSolution(captcha.text, ip, captchaid);
console.log(`Serving ${captcha.text} to ${ip} / ${captchaid}`);
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, () => {

View File

@ -1,4 +1,6 @@
/* @flow */
/*
* Entrypoint for main client script
*/
// eslint-disable-next-line no-unused-vars
import fetch from 'isomorphic-fetch'; // TODO put in the beggining with webpack!
@ -117,6 +119,8 @@ function init() {
ProtocolClient.connect();
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);
}
init();
@ -156,7 +160,7 @@ document.addEventListener('DOMContentLoaded', () => {
// on captcha received
window.onCaptcha = async function onCaptcha(token: string) {
window.onCaptcha = async function onCaptcha(token) {
const body = JSON.stringify({
token,
});

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Admintools
* Modtools
* @flow
*/
@ -31,7 +31,7 @@ async function submitImageAction(
data.append('image', file);
data.append('canvasid', canvas);
data.append('coords', coords);
const resp = await fetch('./admintools', {
const resp = await fetch('./api/modtools', {
credentials: 'include',
method: 'POST',
body: data,
@ -51,7 +51,7 @@ async function submitProtAction(
data.append('canvasid', canvas);
data.append('ulcoor', tlcoords);
data.append('brcoor', brcoords);
const resp = await fetch('./admintools', {
const resp = await fetch('./api/modtools', {
credentials: 'include',
method: 'POST',
body: data,
@ -72,7 +72,7 @@ async function submitRollback(
data.append('canvasid', canvas);
data.append('ulcoor', tlcoords);
data.append('brcoor', brcoords);
const resp = await fetch('./admintools', {
const resp = await fetch('./api/modtools', {
credentials: 'include',
method: 'POST',
body: data,
@ -88,7 +88,7 @@ async function submitIPAction(
const iplist = document.getElementById('iparea').value;
data.append('ip', iplist);
data.append('ipaction', action);
const resp = await fetch('./admintools', {
const resp = await fetch('./api/modtools', {
credentials: 'include',
method: 'POST',
body: data,
@ -101,7 +101,7 @@ async function getModList(
) {
const data = new FormData();
data.append('modlist', true);
const resp = await fetch('./admintools', {
const resp = await fetch('./api/modtools', {
credentials: 'include',
method: 'POST',
body: data,
@ -119,7 +119,7 @@ async function submitRemMod(
) {
const data = new FormData();
data.append('remmod', userId);
const resp = await fetch('./admintools', {
const resp = await fetch('./api/modtools', {
credentials: 'include',
method: 'POST',
body: data,
@ -133,7 +133,7 @@ async function submitMakeMod(
) {
const data = new FormData();
data.append('makemod', userName);
const resp = await fetch('./admintools', {
const resp = await fetch('./api/modtools', {
credentials: 'include',
method: 'POST',
body: data,
@ -146,7 +146,7 @@ async function submitMakeMod(
}
function Admintools() {
function Modtools() {
const maxDate = getToday();
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
const Converter = React.lazy(() => import(/* webpackChunkName: "converter" */ '../Converter'));
// 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 name = useSelector((state) => state.user.name);
@ -36,9 +36,9 @@ const UserArea = ({ windowId }) => {
</Suspense>
</div>
{userlvl && (
<div label={(userlvl === 1) ? t`Admintools` : t`Modtools`}>
<div label={(userlvl === 1) ? t`Modtools` : t`Modtools`}>
<Suspense fallback={<div>{t`Loading...`}</div>}>
<Admintools />
<Modtools />
</Suspense>
</div>
)}

View File

@ -10,7 +10,7 @@ import sharp from 'sharp';
import Sequelize from 'sequelize';
import redis from '../data/redis';
import { admintoolsLogger } from './logger';
import { modtoolsLogger } from './logger';
import { getIPv6Subnet } from '../utils/ip';
import { Blacklist, Whitelist, RegUser } from '../data/models';
// 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 key = `isprox:${ipKey}`;
admintoolsLogger.info(`ADMINTOOLS: ${action} ${ip}`);
modtoolsLogger.info(`ADMINTOOLS: ${action} ${ip}`);
switch (action) {
case 'ban':
await Blacklist.findOrCreate({
@ -150,7 +150,7 @@ export async function executeImageAction(
);
// 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 [
200,
`Successfully loaded image wth ${pxlCount} pixels to ${x}/${y}`,
@ -238,7 +238,7 @@ export async function executeProtAction(
height,
protect,
);
admintoolsLogger.info(
modtoolsLogger.info(
// eslint-disable-next-line max-len
`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,
date,
);
admintoolsLogger.info(
modtoolsLogger.info(
// eslint-disable-next-line max-len
`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;
// 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
export const ADMIN_IDS = (process.env.ADMIN_IDS)
? 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),
transports: [
new DailyRotateFile({
level: 'info',
filename: './log/admintools-%DATE%.log',
filename: './log/modtools-%DATE%.log',
maxSize: '20m',
maxFiles: '14d',
colorize: false,

View File

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

View File

@ -26,6 +26,14 @@ export function getRandomInt(min, max) {
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]) {
return Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2));
}

View File

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

View File

@ -223,8 +223,6 @@ class User {
return {
name: null,
mailVerified: false,
mcVerified: false,
minecraftname: null,
blockDm: false,
totalPixels: 0,
dailyTotalPixels: 0,
@ -242,8 +240,6 @@ class User {
return {
name: regUser.name,
mailVerified: regUser.mailVerified,
mcVerified: regUser.mcVerified,
minecraftname: regUser.minecraftname,
blockDm: regUser.blockDm,
totalPixels: regUser.totalPixels,
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;
try {
const { text } = req.body;
const { text, id } = req.body;
if (!text) {
res.status(400)
.json({ errors: [t`No captcha text given`] });
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) {
case 0:

View File

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

View File

@ -2,15 +2,12 @@
*
* starts a DM session
*
* @flow
*/
import type { Request, Response } from 'express';
import logger from '../../core/logger';
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 { user } = req;

View File

@ -1,6 +1,6 @@
/**
* basic admin api
* is used by ../components/Admintools
* basic mod api
* is used by ../components/Modtools
*
* @flow
*
@ -8,13 +8,10 @@
import express from 'express';
import type { Request, Response } from 'express';
import bodyParser from 'body-parser';
import multer from 'multer';
import { getIPFromRequest } from '../utils/ip';
import session from '../core/session';
import passport from '../core/passport';
import { admintoolsLogger } from '../core/logger';
import { getIPFromRequest } from '../../utils/ip';
import { modtoolsLogger } from '../../core/logger';
import {
executeIPAction,
executeImageAction,
@ -23,7 +20,7 @@ import {
getModList,
removeMod,
makeMod,
} from '../core/adminfunctions';
} from '../../core/adminfunctions';
const router = express.Router();
@ -32,7 +29,7 @@ const router = express.Router();
* multer middleware for getting POST parameters
* 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({
limits: {
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) => {
const ip = getIPFromRequest(req);
if (!req.user) {
admintoolsLogger.info(
`ADMINTOOLS: ${ip} tried to access admintools without login`,
modtoolsLogger.info(
`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;
}
/*
@ -60,14 +55,15 @@ router.use(async (req, res, next) => {
* 2 = Mod
*/
if (!req.user.userlvl) {
admintoolsLogger.info(
`ADMINTOOLS: ${ip} / ${req.user.id} tried to access admintools`,
modtoolsLogger.info(
`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;
}
admintoolsLogger.info(
`ADMINTOOLS: ${req.user.id} / ${req.user.regUser.name} is using admintools`,
modtoolsLogger.info(
`MODTOOLS: ${req.user.id} / ${req.user.regUser.name} is using modtools`,
);
next();
@ -128,7 +124,8 @@ router.post('/', upload.single('image'), async (req, res, next) => {
*/
router.use(async (req, res, next) => {
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;
}
next();

View File

@ -6,7 +6,7 @@
import fs from 'fs';
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) {
const { day, id } = req.query;

View File

@ -3,16 +3,133 @@
* @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 chunks from './chunks';
import admintools from './admintools';
import resetPassword from './reset_password';
import api from './api';
import adminapi from './adminapi';
export {
api,
tiles,
import assets from './assets.json'; // eslint-disable-line import/no-unresolved
import { expressTTag } from '../core/ttag';
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,
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 rankings from '../../core/ranking';
import rankings from '../core/ranking';
export default async (req: Request, res: Response) => {

View File

@ -5,7 +5,6 @@
*/
import express from 'express';
import bodyParser from 'body-parser';
import type { Request, Response } from 'express';
@ -21,7 +20,7 @@ const router = express.Router();
/*
* 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 path from 'path';
import compression from 'compression';
import express from 'express';
import http from 'http';
import etag from 'etag';
// import baseCss from './components/base.tcss';
import forceGC from './core/forceGC';
import assets from './assets.json'; // eslint-disable-line import/no-unresolved
import logger from './core/logger';
import rankings from './core/ranking';
import models from './data/models';
import routes from './routes';
import chatProvider from './core/ChatProvider';
import { expressTTag } from './core/ttag';
import SocketServer from './socket/SocketServer';
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, GUILDED_INVITE } from './core/config';
import { PORT, HOST } from './core/config';
import { SECOND } from './core/constants';
import { startAllCanvasLoops } from './core/tileserver';
startAllCanvasLoops();
const app = express();
app.disable('x-powered-by');
@ -70,19 +58,6 @@ function wsupgrade(request, socket, head) {
}
server.on('upgrade', wsupgrade);
//
// API
// -----------------------------------------------------------------------------
app.use('/api', api);
//
// Serving Zoomed Tiless
// -----------------------------------------------------------------------------
app.use('/tiles', tiles);
/*
* use gzip compression for following calls
/* level from -1 (default, 6) to 0 (no) from 1 (fastest) to 9 (best)
@ -98,93 +73,7 @@ app.use(compression({
},
}));
//
// 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));
});
app.use(routes);
//
// ip config

View File

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

View File

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