diff --git a/API.md b/API.md index 90e37ab..5af7d1d 100644 --- a/API.md +++ b/API.md @@ -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: diff --git a/i18n/ssr-de.po b/i18n/ssr-de.po index 4c2a329..0aeb55c 100644 --- a/i18n/ssr-de.po +++ b/i18n/ssr-de.po @@ -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." diff --git a/i18n/template-ssr.pot b/i18n/template-ssr.pot index 8dd4829..0536e5f 100644 --- a/i18n/template-ssr.pot +++ b/i18n/template-ssr.pot @@ -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 "" diff --git a/i18n/template.pot b/i18n/template.pot index 6a62348..09f0ca2 100644 --- a/i18n/template.pot +++ b/i18n/template.pot @@ -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 "" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8d4e471..1e69522 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 50a402a..7ea88b5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/actions/fetch.js b/src/actions/fetch.js index 04c9462..7a291cb 100644 --- a/src/actions/fetch.js +++ b/src/actions/fetch.js @@ -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', + ); +} diff --git a/src/actions/index.js b/src/actions/index.js index acdb97f..0809682 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -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)); } }; diff --git a/src/actions/types.js b/src/actions/types.js index 3c6ee86..20330cf 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -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, diff --git a/src/captchaserver.js b/src/captchaserver.js index 9823411..e905e64 100644 --- a/src/captchaserver.js +++ b/src/captchaserver.js @@ -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 + '

Captchaserver: 404 Not Found

Captchas are accessible via *.svp paths', + ); + } }); server.listen(PORT, HOST, () => { diff --git a/src/client.js b/src/client.js index 57aec50..400e365 100644 --- a/src/client.js +++ b/src/client.js @@ -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, }); diff --git a/src/components/Alert.jsx b/src/components/Alert.jsx index 00b4efe..ccf4959 100644 --- a/src/components/Alert.jsx +++ b/src/components/Alert.jsx @@ -53,7 +53,7 @@ const Alert = () => {

{alertMessage}

-

+

{(alertType === 'captcha') ? : ( @@ -64,7 +64,7 @@ const Alert = () => { {alertBtn} )} -

+
) diff --git a/src/components/Captcha.jsx b/src/components/Captcha.jsx index 7835512..178928c 100644 --- a/src/components/Captcha.jsx +++ b/src/components/Captcha.jsx @@ -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 (
{ 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', }} > - CAPTCHA { setImgLoaded(true); }} - onError={() => setErrors([t`Could not load captcha`])} - /> + {(captchaData.url) && ( + CAPTCHA { setImgLoaded(true); }} + onError={() => setErrors([t`Could not load captcha`])} + /> + )}

{t`Can't read? Reload:`}  @@ -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 }); }} > diff --git a/src/components/Admintools.jsx b/src/components/Modtools.jsx similarity index 97% rename from src/components/Admintools.jsx rename to src/components/Modtools.jsx index 0d76138..d005a81 100644 --- a/src/components/Admintools.jsx +++ b/src/components/Modtools.jsx @@ -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); diff --git a/src/components/windows/UserArea.jsx b/src/components/windows/UserArea.jsx index 52e11d6..0bf4126 100644 --- a/src/components/windows/UserArea.jsx +++ b/src/components/windows/UserArea.jsx @@ -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 }) => { {userlvl && ( -

+
{t`Loading...`}
}> - +
)} diff --git a/src/core/adminfunctions.js b/src/core/adminfunctions.js index 6c4ff73..1c9856c 100644 --- a/src/core/adminfunctions.js +++ b/src/core/adminfunctions.js @@ -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}`, ); diff --git a/src/core/config.js b/src/core/config.js index ed9cfd5..c785a92 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -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)) : []; diff --git a/src/core/logger.js b/src/core/logger.js index 823cd44..6c7f169 100644 --- a/src/core/logger.js +++ b/src/core/logger.js @@ -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, diff --git a/src/core/me.js b/src/core/me.js index 9446756..e1fc6b2 100644 --- a/src/core/me.js +++ b/src/core/me.js @@ -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 = { diff --git a/src/core/utils.js b/src/core/utils.js index 7338d0b..18de6cc 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -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)); } diff --git a/src/data/models/RegUser.js b/src/data/models/RegUser.js index 977b5da..0f06d6d 100644 --- a/src/data/models/RegUser.js +++ b/src/data/models/RegUser.js @@ -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); diff --git a/src/data/models/User.js b/src/data/models/User.js index 04447e2..1d6440a 100644 --- a/src/data/models/User.js +++ b/src/data/models/User.js @@ -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, diff --git a/src/routes/adminapi/README.md b/src/routes/adminapi/README.md new file mode 100644 index 0000000..16f5849 --- /dev/null +++ b/src/routes/adminapi/README.md @@ -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" +``` diff --git a/src/routes/adminapi/index.js b/src/routes/adminapi/index.js new file mode 100644 index 0000000..cb8aca1 --- /dev/null +++ b/src/routes/adminapi/index.js @@ -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; diff --git a/src/routes/api/README.md b/src/routes/api/README.md new file mode 100644 index 0000000..5247c17 --- /dev/null +++ b/src/routes/api/README.md @@ -0,0 +1,3 @@ +# API + +api for accounts, requests that do not require login data or accounts should not be here diff --git a/src/routes/api/captcha.js b/src/routes/api/captcha.js index dfecc54..fb7d1bd 100644 --- a/src/routes/api/captcha.js +++ b/src/routes/api/captcha.js @@ -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: diff --git a/src/routes/api/index.js b/src/routes/api/index.js index 0063dbd..571e2d2 100644 --- a/src/routes/api/index.js +++ b/src/routes/api/index.js @@ -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 diff --git a/src/routes/api/leavechan.js b/src/routes/api/leavechan.js index b0b1aac..9e67e0d 100644 --- a/src/routes/api/leavechan.js +++ b/src/routes/api/leavechan.js @@ -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; diff --git a/src/routes/admintools.js b/src/routes/api/modtools.js similarity index 74% rename from src/routes/admintools.js rename to src/routes/api/modtools.js index f4c7578..1558a82 100644 --- a/src/routes/admintools.js +++ b/src/routes/api/modtools.js @@ -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(); diff --git a/src/routes/api/history.js b/src/routes/history.js similarity index 94% rename from src/routes/api/history.js rename to src/routes/history.js index 0ff2801..1b0a510 100644 --- a/src/routes/api/history.js +++ b/src/routes/history.js @@ -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; diff --git a/src/routes/index.js b/src/routes/index.js index 603f733..404c0de 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -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; diff --git a/src/routes/api/ranking.js b/src/routes/ranking.js similarity index 80% rename from src/routes/api/ranking.js rename to src/routes/ranking.js index c110132..ebc29c5 100644 --- a/src/routes/api/ranking.js +++ b/src/routes/ranking.js @@ -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) => { diff --git a/src/routes/reset_password.js b/src/routes/reset_password.js index 48dc2be..94d2905 100644 --- a/src/routes/reset_password.js +++ b/src/routes/reset_password.js @@ -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 })); /* diff --git a/src/server.js b/src/server.js index cb52820..64d738f 100644 --- a/src/server.js +++ b/src/server.js @@ -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 diff --git a/src/socket/APISocketServer.js b/src/socket/APISocketServer.js index 67670e1..71431be 100644 --- a/src/socket/APISocketServer.js +++ b/src/socket/APISocketServer.js @@ -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); diff --git a/src/utils/captcha.js b/src/utils/captcha.js index 79b33e9..2da30ad 100644 --- a/src/utils/captcha.js +++ b/src/utils/captcha.js @@ -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; } diff --git a/utils/synapse/deploy.sh b/utils/synapse/deploy.sh new file mode 100755 index 0000000..652a1c0 --- /dev/null +++ b/utils/synapse/deploy.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +scp ./testmodule.py root@pixelplanet:/etc/matrix-synapse/testmodule.py +ssh root@pixelplanet 'systemctl restart matrix-synapse' diff --git a/utils/synapse/testmodule.py b/utils/synapse/testmodule.py new file mode 100644 index 0000000..3d0191e --- /dev/null +++ b/utils/synapse/testmodule.py @@ -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