diff --git a/comty.js b/comty.js index 0dd24fee..fb4d6665 160000 --- a/comty.js +++ b/comty.js @@ -1 +1 @@ -Subproject commit 0dd24fee4a231dbb41d5b14ff92b67d8f14cb5a2 +Subproject commit fb4d666576a937eaad4ea69f9e5a13778f06b3f5 diff --git a/packages/app/package.json b/packages/app/package.json index fbde7af4..a2a085b5 100755 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,90 +1,91 @@ { - "name": "@comty/app", - "version": "1.44.0@alpha", - "license": "ComtyLicense", - "main": "electron/main", - "type": "module", - "author": "RageStudio", - "description": "A prototype of a social network.", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "release": "node ./scripts/release.js", - "postinstall": "./scripts/postinstall.sh", - "eslint": "eslint" - }, - "dependencies": { - "@ant-design/icons": "^5.4.0", - "@dnd-kit/core": "^6.0.8", - "@dnd-kit/modifiers": "^9.0.0", - "@dnd-kit/sortable": "^7.0.2", - "@dnd-kit/utilities": "^3.2.2", - "@emotion/react": "^11.13.0", - "@emotion/styled": "^11.13.0", - "@ffmpeg/ffmpeg": "^0.12.10", - "@ffmpeg/util": "^0.12.1", - "@mui/material": "^5.11.9", - "@ragestudio/cordova-nfc": "^1.2.0", - "@ragestudio/vessel": "^0.23.1", - "@sentry/browser": "^7.64.0", - "@tauri-apps/api": "^1.5.4", - "@tsmx/human-readable": "^1.0.7", - "antd": "^5.20.6", - "axios": "^1.7.7", - "bear-react-carousel": "^4.0.10-alpha.0", - "classnames": "2.3.1", - "comty.js": "^0.68.0", - "d3": "^7.9.0", - "dashjs": "^5.0.3", - "dompurify": "^3.0.0", - "fast-average-color": "^9.2.0", - "fuse.js": "6.5.3", - "hls.js": "^1.5.17", - "howler": "2.2.3", - "i18next": "21.6.6", - "js-cookie": "3.0.1", - "jsmediatags": "^3.9.7", - "lottie-react": "^2.4.0", - "luxon": "^3.0.4", - "mime": "^3.0.0", - "moment": "2.29.4", - "motion": "^12.4.2", - "music-metadata": "^11.2.1", - "plyr": "^3.7.8", - "prop-types": "^15.8.1", - "qs": "^6.14.0", - "react": "18.3.1", - "react-beautiful-dnd": "^13.1.1", - "react-color": "2.19.3", - "react-countup": "^6.4.1", - "react-dom": "18.3.1", - "react-fast-marquee": "^1.3.5", - "react-i18next": "11.15.3", - "react-icons": "^5.4.0", - "react-lazy-load-image-component": "^1.5.4", - "react-markdown": "^8.0.3", - "react-modal-image": "^2.6.0", - "react-player": "^2.16.0", - "react-rnd": "^10.4.14", - "react-transition-group": "^4.4.5", - "react-useanimations": "^2.10.0", - "remark-gfm": "^3.0.1", - "rxjs": "^7.5.5", - "store": "^2.0.12", - "swapy": "^1.0.5", - "ua-parser-js": "^1.0.36", - "vaul": "^1.1.2", - "vite": "^6.2.6" - }, - "devDependencies": { - "@eslint/js": "^9.26.0", - "@octokit/rest": "^21.1.1", - "7zip-min": "1.4.3", - "dotenv": "16.0.3", - "eslint": "^9.26.0", - "eslint-plugin-react": "^7.37.5", - "form-data": "^4.0.0", - "globals": "^16.1.0" - } + "name": "@comty/app", + "version": "1.44.1@alpha", + "license": "ComtyLicense", + "main": "electron/main", + "type": "module", + "author": "RageStudio", + "description": "A prototype of a social network.", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "release": "node ./scripts/release.js", + "postinstall": "./scripts/postinstall.sh", + "eslint": "eslint" + }, + "dependencies": { + "@ant-design/icons": "^5.4.0", + "@dnd-kit/core": "^6.0.8", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^7.0.2", + "@dnd-kit/utilities": "^3.2.2", + "@emotion/react": "^11.13.0", + "@emotion/styled": "^11.13.0", + "@ffmpeg/ffmpeg": "^0.12.10", + "@ffmpeg/util": "^0.12.1", + "@mui/material": "^5.11.9", + "@ragestudio/cordova-nfc": "^1.2.0", + "@ragestudio/vessel": "^0.23.1", + "@sentry/browser": "^7.64.0", + "@tauri-apps/api": "^1.5.4", + "@tsmx/human-readable": "^1.0.7", + "antd": "^5.20.6", + "axios": "^1.7.7", + "bear-react-carousel": "^4.0.10-alpha.0", + "classnames": "2.3.1", + "comty.js": "^0.68.1", + "d3": "^7.9.0", + "dashjs": "^5.0.3", + "dompurify": "^3.0.0", + "fast-average-color": "^9.2.0", + "fuse.js": "6.5.3", + "hls.js": "^1.5.17", + "howler": "2.2.3", + "i18next": "21.6.6", + "js-cookie": "3.0.1", + "jsmediatags": "^3.9.7", + "lottie-react": "^2.4.0", + "luxon": "^3.0.4", + "mime": "^3.0.0", + "moment": "2.29.4", + "motion": "^12.4.2", + "music-metadata": "^11.2.1", + "plyr": "^3.7.8", + "prop-types": "^15.8.1", + "qs": "^6.14.0", + "react": "18.3.1", + "react-beautiful-dnd": "^13.1.1", + "react-color": "2.19.3", + "react-countup": "^6.4.1", + "react-dom": "18.3.1", + "react-fast-marquee": "^1.3.5", + "react-i18next": "11.15.3", + "react-icons": "^5.4.0", + "react-lazy-load-image-component": "^1.5.4", + "react-markdown": "^8.0.3", + "react-modal-image": "^2.6.0", + "react-player": "^2.16.0", + "react-rnd": "^10.4.14", + "react-transition-group": "^4.4.5", + "react-turnstile": "^1.1.4", + "react-useanimations": "^2.10.0", + "remark-gfm": "^3.0.1", + "rxjs": "^7.5.5", + "store": "^2.0.12", + "swapy": "^1.0.5", + "ua-parser-js": "^1.0.36", + "vaul": "^1.1.2", + "vite": "^6.2.6" + }, + "devDependencies": { + "@eslint/js": "^9.26.0", + "@octokit/rest": "^21.1.1", + "7zip-min": "1.4.3", + "dotenv": "16.0.3", + "eslint": "^9.26.0", + "eslint-plugin-react": "^7.37.5", + "form-data": "^4.0.0", + "globals": "^16.1.0" + } } diff --git a/packages/app/src/components/Player/SeekBar/index.less b/packages/app/src/components/Player/SeekBar/index.less index 458e5317..c71b04f6 100755 --- a/packages/app/src/components/Player/SeekBar/index.less +++ b/packages/app/src/components/Player/SeekBar/index.less @@ -1,3 +1,11 @@ +&.mobile { + .player-seek_bar { + .slider-container { + height: 10px; + } + } +} + .player-seek_bar { z-index: 330; diff --git a/packages/app/src/contexts/WithPlayerContext/index.jsx b/packages/app/src/contexts/WithPlayerContext/index.jsx index 1fbe4c9a..ee453f62 100755 --- a/packages/app/src/contexts/WithPlayerContext/index.jsx +++ b/packages/app/src/contexts/WithPlayerContext/index.jsx @@ -40,9 +40,7 @@ export const usePlayerStateContext = (updater) => { app.cores.player.eventBus().on("player.state.update", handleStateChange) return () => { - app.cores.player - .eventBus() - .off("player.state.update", handleStateChange) + app.cores.player.eventBus().off("player.state.update", handleStateChange) } }, []) @@ -56,9 +54,7 @@ export class WithPlayerContext extends React.Component { events = { "player.state.update": async (state) => { - if (state !== this.state) { - this.setState(state) - } + this.setState(state) }, } diff --git a/packages/app/src/layout.jsx b/packages/app/src/layout.jsx index e52fbd5f..1a935dad 100755 --- a/packages/app/src/layout.jsx +++ b/packages/app/src/layout.jsx @@ -23,9 +23,7 @@ export default class Layout extends React.PureComponent { const transitionLayer = document.getElementById("transitionLayer") if (!transitionLayer) { - console.warn( - "transitionLayer not found, no animation will be played", - ) + console.warn("transitionLayer not found, no animation will be played") return false } @@ -42,9 +40,7 @@ export default class Layout extends React.PureComponent { const transitionLayer = document.getElementById("transitionLayer") if (!transitionLayer) { - console.warn( - "transitionLayer not found, no animation will be played", - ) + console.warn("transitionLayer not found, no animation will be played") return false } @@ -114,10 +110,7 @@ export default class Layout extends React.PureComponent { ) }, toggleMobileStyle: (to) => { - return this.layoutInterface.toggleRootContainerClassname( - "mobile", - to, - ) + return this.layoutInterface.toggleRootContainerClassname("mobile", to) }, toggleReducedAnimations: (to) => { return this.layoutInterface.toggleRootContainerClassname( @@ -150,17 +143,14 @@ export default class Layout extends React.PureComponent { ) }, toggleRootContainerClassname: (classname, to) => { - const root = document.documentElement + const root = document.documentElement if (!root) { console.error("root not found") return false } - to = - typeof to === "boolean" - ? to - : !root.classList.contains(classname) + to = typeof to === "boolean" ? to : !root.classList.contains(classname) if (root.classList.contains(classname) === to) { // ignore @@ -208,10 +198,9 @@ export default class Layout extends React.PureComponent { if (this.state.renderError) { if (this.props.staticRenders?.RenderError) { - return React.createElement( - this.props.staticRenders?.RenderError, - { error: this.state.renderError }, - ) + return React.createElement(this.props.staticRenders?.RenderError, { + error: this.state.renderError, + }) } return JSON.stringify(this.state.renderError) diff --git a/packages/app/src/layouts/components/@mobile/bottomBar/index.jsx b/packages/app/src/layouts/components/@mobile/bottomBar/index.jsx index 84e4be0d..ed45c4c7 100755 --- a/packages/app/src/layouts/components/@mobile/bottomBar/index.jsx +++ b/packages/app/src/layouts/components/@mobile/bottomBar/index.jsx @@ -159,7 +159,10 @@ const AccountButton = React.forwardRef((props, ref) => { >
{user ? ( - + ) : ( createIconRender("FiLogin") )} @@ -168,6 +171,8 @@ const AccountButton = React.forwardRef((props, ref) => { ) }) +AccountButton.displayName = "AccountButton" + export class BottomBar extends React.Component { static contextType = Context @@ -366,9 +371,7 @@ export class BottomBar extends React.Component { } const heightValue = Number( - app.cores.style - .getDefaultVar("bottom-bar-height") - .replace("px", ""), + app.cores.style.getDefaultVar("bottom-bar-height").replace("px", ""), ) return ( @@ -409,10 +412,7 @@ export class BottomBar extends React.Component {
@@ -441,9 +441,7 @@ export class BottomBar extends React.Component { }) }} > -
- {createIconRender("FiHome")} -
+
{createIconRender("FiHome")}
-
- {createIconRender("FiSearch")} -
+
{createIconRender("FiSearch")}
diff --git a/packages/app/src/pages/auth/forms/register/index.jsx b/packages/app/src/pages/auth/forms/register/index.jsx index f4a42f14..d1f6afeb 100755 --- a/packages/app/src/pages/auth/forms/register/index.jsx +++ b/packages/app/src/pages/auth/forms/register/index.jsx @@ -12,8 +12,9 @@ import UsernameStep from "./steps/username" import PasswordStep from "./steps/password" import EmailStep from "./steps/email" import TOSStep from "./steps/tos" +import CaptchaStep from "./steps/captcha" -const steps = [UsernameStep, PasswordStep, EmailStep, TOSStep] +const steps = [UsernameStep, PasswordStep, EmailStep, TOSStep, CaptchaStep] const RegisterForm = (props) => { const [finishing, setFinishing] = React.useState(false) @@ -35,6 +36,7 @@ const RegisterForm = (props) => { password: stepsValues.password, email: stepsValues.email, tos: stepsValues.tos, + captcha: stepsValues.captcha, }).catch((err) => { setFinishSuccess(false) setFinishing(false) @@ -107,8 +109,8 @@ const RegisterForm = (props) => { <>

👋 Hi! Nice to meet you

- Tell us some basic information to get started - creating your account. + Tell us some basic information to get started creating your + account.

)} @@ -116,8 +118,7 @@ const RegisterForm = (props) => { {!finishSuccess && !finishing && step > 0 && ( <>

- {currentStepData?.icon && - createIconRender(currentStepData.icon)} + {currentStepData?.icon && createIconRender(currentStepData.icon)} {currentStepData?.title}

@@ -150,10 +151,7 @@ const RegisterForm = (props) => {

Welcome abord!

-

- One last step, we need you to login with your new - account. -

+

One last step, we need you to login with your new account.

{ )} {finishError && ( - + )} {!finishSuccess && !finishing && (
{step === 0 && ( - props.setActiveKey("selector")} - > + props.setActiveKey("selector")}> Cancel )} {step > 0 && ( - prevStep()}> - Back - + prevStep()}>Back )} { + return ( + { + props.updateValue(token) + }} + /> + ) +} + +export default { + key: "captcha", + title: "Step 4", + icon: "FiLock", + description: + "We need you to prove that you are a human. Please enter the captcha below.", + required: true, + content: CaptchaStepComponent, +} diff --git a/packages/app/src/pages/auth/forms/register/steps/email/index.jsx b/packages/app/src/pages/auth/forms/register/steps/email/index.jsx index 1b8940a5..04a7474e 100755 --- a/packages/app/src/pages/auth/forms/register/steps/email/index.jsx +++ b/packages/app/src/pages/auth/forms/register/steps/email/index.jsx @@ -4,87 +4,103 @@ import * as antd from "antd" import AuthModel from "@models/auth" const EmailStepComponent = (props) => { - const [email, setEmail] = React.useState(props.currentValue ?? "") + const [email, setEmail] = React.useState(props.currentValue ?? "") - const [loading, setLoading] = React.useState(false) - const [validFormat, setValidFormat] = React.useState(null) - const [emailAvailable, setEmailAvailable] = React.useState(null) + const [loading, setLoading] = React.useState(false) + const [validFormat, setValidFormat] = React.useState(null) + const [emailAvailable, setEmailAvailable] = React.useState(null) - const isValid = () => { - return email.length > 0 && validFormat && emailAvailable - } + const isValid = () => { + return email.length > 0 && validFormat && emailAvailable + } - const checkIfIsEmail = (email) => { - return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) - } + const checkIfIsEmail = (email) => { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) + } - const submit = () => { - if (!isValid()) return + const submit = () => { + if (!isValid()) { + return false + } - props.onPressEnter() - } + props.onPressEnter() + } - const handleUpdate = (e) => { - setEmail(e.target.value) - } + const handleUpdate = (e) => { + setEmail(e.target.value) + } - React.useEffect(() => { - if (email.length === 0) { - setEmailAvailable(null) - setValidFormat(null) + React.useEffect(() => { + if (email.length === 0) { + setEmailAvailable(null) + setValidFormat(null) - return - } + return + } - props.updateValue(null) + props.updateValue(null) - setLoading(true) + setLoading(true) - setValidFormat(checkIfIsEmail(email)) + const isEmailValid = checkIfIsEmail(email) + setValidFormat(isEmailValid) - // check if email is available - const timer = setTimeout(async () => { - if (!validFormat) return + // check if email is available + const timer = setTimeout(async () => { + if (!isEmailValid) { + return false + } - const request = await AuthModel.availability({ email }).catch((error) => { - antd.message.error(`Cannot check email availability: ${error.message}`) + const request = await AuthModel.availability({ email }).catch((error) => { + antd.message.error(`Cannot check email availability: ${error.message}`) - return false - }) + return false + }) - if (request) { - setEmailAvailable(!request.exist) + if (request) { + setEmailAvailable(!request.exist) - if (request.exist) { - antd.message.error("Email is already in use") - props.updateValue(null) - } else { - props.updateValue(email) - } - } + if (request.exist) { + antd.message.error("Email is already in use") + props.updateValue(null) + } else { + props.updateValue(email) + } + } - setLoading(false) - }, 1000) + setLoading(false) + }, 1000) - return () => clearTimeout(timer) - }, [email]) + return () => clearTimeout(timer) + }, [email]) - return
- -
+ return ( +
+ +
+ ) } export default { - key: "email", - title: "Step 3", - icon: "FiMail", - description: "Enter a email for the account, it can be used to access to your account. \n Will not be shared with anyone else and not be used for marketing purposes.", - required: true, - content: EmailStepComponent, -} \ No newline at end of file + key: "email", + title: "Step 3", + icon: "FiMail", + description: + "Enter a email for the account, it can be used to access to your account. \n Will not be shared with anyone else and not be used for marketing purposes.", + required: true, + content: EmailStepComponent, +} diff --git a/packages/app/src/pages/auth/forms/register/steps/tos/index.jsx b/packages/app/src/pages/auth/forms/register/steps/tos/index.jsx index b361e493..0c14a226 100755 --- a/packages/app/src/pages/auth/forms/register/steps/tos/index.jsx +++ b/packages/app/src/pages/auth/forms/register/steps/tos/index.jsx @@ -1,80 +1,81 @@ -import React from "react" import * as antd from "antd" import MarkdownReader from "@components/MarkdownReader" import config from "@config" const FrameStyle = { - "width": "60vw", - "max-width": "60vw", - "height": "90vh", - "max-height": "90vh", - "overflow": "overlay", - "justify-content": "flex-start", + width: "60vw", + "max-width": "60vw", + height: "90vh", + "max-height": "90vh", + overflow: "overlay", + "justify-content": "flex-start", } const LegalDocumentsDecorators = { - "terms": "Terms of Service", - "privacy": "Privacy Policy", + terms: "Terms of Service", + privacy: "Privacy Policy", } function composeConfirmationCheckboxLabel(documents) { - let labels = [ - "I have read and accept" - ] + let labels = ["I have read and accept"] - documents.forEach(([key, value], index) => { - const isLast = index === documents.length - 1 + documents.forEach(([key, value], index) => { + const isLast = index === documents.length - 1 - labels.push(`the ${LegalDocumentsDecorators[key] ?? `document (${key})`} ${!isLast ? "and" : ""}`) - }) + labels.push( + `the ${LegalDocumentsDecorators[key] ?? `document (${key})`} ${!isLast ? "and" : ""}`, + ) + }) - return labels.join(" ") + return labels.join(" ") } const TermsOfServiceStepComponent = (props) => { - const legalDocuments = Object.entries(config.legal) + const legalDocuments = Object.entries(config.legal) - return
- { - Object.entries(config.legal).map(([key, value]) => { - if (!value) { - return null - } + return ( +
+ {Object.entries(config.legal).map(([key, value]) => { + if (!value) { + return null + } - return { - app.layout.modal.open(key, MarkdownReader, { - includeCloseButton: true, - frameContentStyle: FrameStyle, - props: { - url: value - } - }) - }} - > - Read {LegalDocumentsDecorators[key] ?? `document (${key})`} - - }) - } + return ( + { + app.layout.modal.open(key, MarkdownReader, { + includeCloseButton: true, + frameContentStyle: FrameStyle, + props: { + url: value, + }, + }) + }} + > + Read {LegalDocumentsDecorators[key] ?? `document (${key})`} + + ) + })} - { - props.updateValue(event.target.checked) - }} - > - {composeConfirmationCheckboxLabel(legalDocuments)} - -
+ { + props.updateValue(event.target.checked) + }} + > + {composeConfirmationCheckboxLabel(legalDocuments)} + +
+ ) } export default { - key: "tos", - title: "Step 3", - icon: "FileDone", - description: "Take your time to read these legal documents.", - required: true, - content: TermsOfServiceStepComponent, + key: "tos", + title: "Step 3", + icon: "FileDone", + description: "Take your time to read these legal documents.", + required: true, + content: TermsOfServiceStepComponent, } diff --git a/packages/app/src/pages/auth/forms/selector/index.jsx b/packages/app/src/pages/auth/forms/selector/index.jsx index ebc27c96..e9cb927e 100755 --- a/packages/app/src/pages/auth/forms/selector/index.jsx +++ b/packages/app/src/pages/auth/forms/selector/index.jsx @@ -6,9 +6,14 @@ import { Icons } from "@components/Icons" const MainSelector = (props) => { return ( <> -
- -
+ {!app.isMobile && ( +
+ +
+ )}
{app.userData && ( diff --git a/packages/app/src/pages/auth/index.mobile.jsx b/packages/app/src/pages/auth/index.mobile.jsx index 3969df7e..bbf0f509 100755 --- a/packages/app/src/pages/auth/index.mobile.jsx +++ b/packages/app/src/pages/auth/index.mobile.jsx @@ -1,32 +1,55 @@ import React from "react" +import config from "@config" import useRandomFeaturedWallpaperUrl from "@hooks/useRandomFeaturedWallpaperUrl" +import useUrlQueryActiveKey from "@hooks/useUrlQueryActiveKey" + +import RegisterForm from "./forms/register" +import MainSelector from "./forms/selector" +import RecoveryForm from "./forms/recovery" + +const keyToComponents = { + selector: MainSelector, + register: RegisterForm, + recovery: RecoveryForm, +} import "./index.mobile.less" -export default (props) => { +const AuthPage = (props) => { const randomWallpaperURL = useRandomFeaturedWallpaperUrl() - - React.useEffect(() => { - if (app.userData) { - app.navigation.goMain() - } else { - app.auth.login() - } - }, []) + const [activeKey, setActiveKey] = useUrlQueryActiveKey({ + defaultKey: "selector", + }) return ( -
+
- {/*

- {wallpaperData?.author ? wallpaperData.author : null} -

*/} + /> + +
+
+ +
+ +
+ {React.createElement( + keyToComponents[activeKey] ?? keyToComponents["selector"], + { + setActiveKey: setActiveKey, + }, + )} +
) } + +export default AuthPage diff --git a/packages/app/src/pages/auth/index.mobile.less b/packages/app/src/pages/auth/index.mobile.less index b05a5d18..ea485231 100755 --- a/packages/app/src/pages/auth/index.mobile.less +++ b/packages/app/src/pages/auth/index.mobile.less @@ -1,22 +1,89 @@ -.loginPage { - position: relative; +.login-page { + position: relative; - width: 100%; - height: 100vh; - height: 100dvh; + display: flex; + flex-direction: column; - .wallpaper { - position: absolute; + align-items: center; + justify-content: center; - top: 0; - left: 0; + width: 100%; + height: 100vh; + height: 100dvh; - width: 100%; - height: 100vh; - height: 100dvh; + .wallpaper { + position: fixed; - background-position: center; - background-size: cover; - background-repeat: no-repeat; - } -} \ No newline at end of file + top: 0; + left: 0; + + z-index: -1; + + width: 100%; + height: 100vh; + height: 100dvh; + + background-position: center; + background-size: cover; + background-repeat: no-repeat; + } + + .login-page-card { + position: relative; + + display: flex; + flex-direction: column; + + width: 95%; + padding: 15px; + + background-color: var(--background-color-primary); + + border-radius: 24px; + + gap: 30px; + + &__header { + display: flex; + flex-direction: row; + + align-items: center; + + gap: 10px; + + &__logo { + width: 40px; + height: 40px; + } + } + + &__content { + display: flex; + flex-direction: column; + + .ant-btn { + height: fit-content; + padding: 7px 15px; + font-size: 0.8rem; + } + + .actions { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + gap: 15px; + } + + .register_form { + position: unset; + + .register_form_actions { + top: 0; + bottom: unset; + } + } + } + } +} diff --git a/packages/app/src/pages/lyrics/components/text/index.jsx b/packages/app/src/pages/lyrics/components/text/index.jsx index 3fe85416..a2b3e3fd 100644 --- a/packages/app/src/pages/lyrics/components/text/index.jsx +++ b/packages/app/src/pages/lyrics/components/text/index.jsx @@ -104,6 +104,8 @@ const LyricsText = React.forwardRef((props, textRef) => { React.useEffect(() => { setVisible(false) setCurrentLineIndex(0) + // set scroll top to 0 + textRef.current.scrollTop = 0 }, [playerState.track_manifest]) React.useEffect(() => { diff --git a/packages/server/services/auth/auth.service.js b/packages/server/services/auth/auth.service.js index 1cb3f8aa..b0e008f8 100644 --- a/packages/server/services/auth/auth.service.js +++ b/packages/server/services/auth/auth.service.js @@ -35,7 +35,6 @@ export default class API extends Server { onExit() { this.queuesManager.cleanUp() - console.log("Jijija") } } diff --git a/packages/server/services/auth/classes/account/methods/create.js b/packages/server/services/auth/classes/account/methods/create.js index d64c280a..63478c75 100644 --- a/packages/server/services/auth/classes/account/methods/create.js +++ b/packages/server/services/auth/classes/account/methods/create.js @@ -1,60 +1,84 @@ import bcrypt from "bcrypt" import { User } from "@db_models" -import requiredFields from "@shared-utils/requiredFields" - import Account from "@classes/account" +import requiredFields from "@shared-utils/requiredFields" +import verifyTurnstileToken from "@utils/verifyTurnstileToken" + export default async (payload) => { - requiredFields(["username", "password", "email"], payload) + requiredFields(["username", "password", "email"], payload) - let { username, password, email, public_name, roles, avatar, accept_tos } = payload + let { + username, + password, + email, + public_name, + roles, + avatar, + accept_tos, + captcha, + } = payload - if (ToBoolean(accept_tos) !== true) { - throw new OperationError(400, "You must accept the terms of service in order to create an account.") - } + if (ToBoolean(accept_tos) !== true) { + throw new OperationError( + 400, + "You must accept the terms of service in order to create an account.", + ) + } - await Account.usernameMeetPolicy(username) + if (!captcha) { + throw new OperationError(400, "Captcha token is required") + } - // check if username is already taken - const existentUser = await User - .findOne({ username: username }) + const turnstileResponse = await verifyTurnstileToken(captcha) - if (existentUser) { - throw new OperationError(400, "User already exists") - } + if (turnstileResponse.success !== true) { + throw new OperationError(400, "Invalid captcha token") + } - // check if the email is already in use - const existentEmail = await User - .findOne({ email: email }) - .select("+email") + await Account.usernameMeetPolicy(username) - if (existentEmail) { - throw new OperationError(400, "Email already in use") - } + // check if username is already taken + const existentUser = await User.findOne({ username: username }) - await Account.passwordMeetPolicy(password) + if (existentUser) { + throw new OperationError(400, "User already exists") + } - // hash the password - const hash = bcrypt.hashSync(password, parseInt(process.env.BCRYPT_ROUNDS ?? 3)) + // check if the email is already in use + const existentEmail = await User.findOne({ email: email }).select("+email") - let user = new User({ - username: username, - password: hash, - email: email, - public_name: public_name, - avatar: avatar ?? `https://api.dicebear.com/7.x/thumbs/svg?seed=${username}`, - roles: roles, - created_at: new Date().getTime(), - accept_tos: accept_tos, - activated: false, - }) + if (existentEmail) { + throw new OperationError(400, "Email already in use") + } - await user.save() + await Account.passwordMeetPolicy(password) - await Account.sendActivationCode(user._id.toString()) + // hash the password + const hash = bcrypt.hashSync( + password, + parseInt(process.env.BCRYPT_ROUNDS ?? 3), + ) - return { - activation_required: true, - user: user, - } -} \ No newline at end of file + let user = new User({ + username: username, + password: hash, + email: email, + public_name: public_name, + avatar: + avatar ?? `https://api.dicebear.com/7.x/thumbs/svg?seed=${username}`, + roles: roles, + created_at: new Date().getTime(), + accept_tos: accept_tos, + activated: false, + }) + + await user.save() + + await Account.sendActivationCode(user._id.toString()) + + return { + activation_required: true, + user: user, + } +} diff --git a/packages/server/services/auth/utils/verifyTurnstileToken.js b/packages/server/services/auth/utils/verifyTurnstileToken.js new file mode 100644 index 00000000..693c52c1 --- /dev/null +++ b/packages/server/services/auth/utils/verifyTurnstileToken.js @@ -0,0 +1,26 @@ +import axios from "axios" + +export default async (token) => { + const secret = process.env.TURNSTILE_SECRET + + if (!secret) { + throw new Error("Turnstile secret is not set") + } + + let response = await axios({ + url: "https://challenges.cloudflare.com/turnstile/v0/siteverify", + method: "POST", + headers: { + "Content-Type": "application/json", + }, + data: { + secret: secret, + response: token, + }, + }).catch((err) => { + console.error(err.response.data) + throw new Error("Turnstile verification failed") + }) + + return response.data +}