From 029cf014f1ff0e389c104270394f0d0861d672ee Mon Sep 17 00:00:00 2001 From: Teages Date: Mon, 27 Nov 2023 12:40:07 +0800 Subject: [PATCH] switch to turnstile --- assets/css/tailwind.css | 3 + components/Captcha.vue | 103 +++++++++++++++++-------- components/Comment/Create.vue | 48 ++++++------ config/development-local.json | 2 +- config/development.json | 2 +- config/production.json | 2 +- nuxt.config.ts | 9 ++- package.json | 136 +++++++++++++++++----------------- pages/levels/[id]/index.vue | 2 +- pages/session/link.vue | 91 ++++++++++++----------- pages/session/login.vue | 55 +++++++------- pages/session/reset/index.vue | 58 ++++++++------- pages/session/signup.vue | 70 ++++++++--------- pnpm-lock.yaml | 50 +++++-------- 14 files changed, 341 insertions(+), 290 deletions(-) diff --git a/assets/css/tailwind.css b/assets/css/tailwind.css index 00b64b50e..5d4ba4776 100644 --- a/assets/css/tailwind.css +++ b/assets/css/tailwind.css @@ -26,6 +26,9 @@ html[lang="ko-KR"] { body { @apply bg-black bg-opacity-30 break-words min-w-[380px] } +html { + @apply overflow-y-scroll +} /* Scrollbar */ diff --git a/components/Captcha.vue b/components/Captcha.vue index d10ce5360..1aa8bb7fa 100644 --- a/components/Captcha.vue +++ b/components/Captcha.vue @@ -1,49 +1,88 @@ + \ No newline at end of file diff --git a/components/Comment/Create.vue b/components/Comment/Create.vue index 6be2fcffe..94e44a00d 100644 --- a/components/Comment/Create.vue +++ b/components/Comment/Create.vue @@ -22,30 +22,34 @@ const loading = ref(false) async function sendPost() { loading.value = true - const res = await useServiceFetch(url.value, { - method: 'POST', - body: { - captcha: await props.verify(), - content: post.value, - category: props.category, - key: props.thread, - }, - }) - if (res.data.value && user.value) { - res.data.value.owner = { - id: user.value.id, - uid: user.value.uid, - name: user.value.name, - avatar: { - small: avatarURL(user.value.id), - original: avatarURL(user.value.id), - medium: avatarURL(user.value.id), - large: avatarURL(user.value.id), + try { + const res = await useServiceFetch(url.value, { + method: 'POST', + body: { + captcha: await props.verify(), + content: post.value, + category: props.category, + key: props.thread, }, + }) + if (res.data.value && user.value) { + res.data.value.owner = { + id: user.value.id, + uid: user.value.uid, + name: user.value.name, + avatar: { + small: avatarURL(user.value.id), + original: avatarURL(user.value.id), + medium: avatarURL(user.value.id), + large: avatarURL(user.value.id), + }, + } + props.afterPost(res.data.value) + post.value = '' + successAlert('Comment Added') } - props.afterPost(res.data.value) - post.value = '' - successAlert('Comment Added') + } catch (error) { + handleErrorToast(error as Error) } loading.value = false } diff --git a/config/development-local.json b/config/development-local.json index 61ac09d84..38a8683f6 100644 --- a/config/development-local.json +++ b/config/development-local.json @@ -8,6 +8,6 @@ "imageURL": "https://images.cytoid.io", "webURL": "http://localhost:3000", "cookieDomain": "localhost", - "captchaKey": "6Le6KqsUAAAAANemJwIe18ld-0kKSQn9aqR4-ltM", + "captchaKey": "0x4AAAAAAANth3Rv--EiIENn", "analyticsCode": "UA-127631599-1" } diff --git a/config/development.json b/config/development.json index ea76f74b1..1db4ee32a 100644 --- a/config/development.json +++ b/config/development.json @@ -9,6 +9,6 @@ "imageURL": "https://images.cytoid.io", "webURL": "http://localhost:3000", "cookieDomain": "localhost", - "captchaKey": "6Le6KqsUAAAAANemJwIe18ld-0kKSQn9aqR4-ltM", + "captchaKey": "0x4AAAAAAANth3Rv--EiIENn", "analyticsCode": "UA-127631599-1" } diff --git a/config/production.json b/config/production.json index 2a1787761..e0ada8572 100644 --- a/config/production.json +++ b/config/production.json @@ -8,6 +8,6 @@ "imageURL": "https://images.cytoid.io", "webURL": "https://next.cytoid.io", "cookieDomain": "next.cytoid.io", - "captchaKey": "6Le6KqsUAAAAANemJwIe18ld-0kKSQn9aqR4-ltM", + "captchaKey": "0x4AAAAAAANth3Rv--EiIENn", "analyticsCode": "UA-127631599-1" } diff --git a/nuxt.config.ts b/nuxt.config.ts index 29e648912..17803b726 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -20,7 +20,7 @@ export default defineNuxtConfig({ '@nuxtjs/tailwindcss', 'nuxt-icon', '@nuxtjs/google-fonts', - 'vue-recaptcha/nuxt', + '@nuxtjs/turnstile', '@vite-pwa/nuxt', '@nuxtjs/device', 'nuxt-gtag', @@ -51,12 +51,13 @@ export default defineNuxtConfig({ graphqlURLServer: config.get('graphqlURLServer'), graphqlURLClient: config.get('graphqlURLClient'), servicesUA: process.env.SERVICES_UA ?? '', - recaptcha: { - v2SiteKey: config.get('captchaKey'), - }, }, }, + turnstile: { + siteKey: config.get('captchaKey'), + }, + graphqlCodegen: { config: { schema: 'gql/schema.graphql', diff --git a/package.json b/package.json index 82ecf2db6..4cfe32074 100644 --- a/package.json +++ b/package.json @@ -1,68 +1,68 @@ -{ - "name": "cytoid.io", - "type": "module", - "version": "2.0.0", - "private": true, - "description": "Cytoid is a music game where you can create, share and play your own levels! Powered by community, with many dedicated creators, Cytoid provides a huge variety of musical genres to enjoy and a diverse range of gameplay design.", - "scripts": { - "build": "nuxt build", - "start": "node .output/server/index.mjs", - "dev": "nuxt dev", - "dev:local": "cross-env NODE_APP_INSTANCE=local nuxt dev", - "gql": "npx cross-env NODE_ENV=production node ./cli/codegen-update.js", - "gql:local": "npx cross-env NODE_APP_INSTANCE=local node ./cli/codegen-update.js", - "generate": "nuxt generate", - "preview": "nuxt preview", - "postinstall": "nuxt prepare", - "lint": "eslint .", - "lint:fix": "eslint . --fix" - }, - "dependencies": { - "graphql": "^16.7.1" - }, - "devDependencies": { - "@antfu/eslint-config": "^2.1.0", - "@graphql-codegen/cli": "^5.0.0", - "@graphql-codegen/client-preset": "^4.0.1", - "@nuxt/devtools": "^1.0.3", - "@nuxtjs/device": "^3.1.1", - "@nuxtjs/google-fonts": "^3.0.2", - "@nuxtjs/i18n": "^8.0.0-rc.3", - "@nuxtjs/tailwindcss": "^6.8.0", - "@tailwindcss/typography": "^0.5.9", - "@types/config": "^3.3.0", - "@types/glob": "^8.1.0", - "@types/howler": "^2.2.7", - "@types/lodash": "^4.14.196", - "@types/node": "^20.10.0", - "@types/sanitize-html": "^2.6.2", - "@typescript-eslint/eslint-plugin": "^6.1.0", - "@typescript-eslint/parser": "^6.1.0", - "@urql/core": "^4.1.0", - "@vite-pwa/nuxt": "^0.3.2", - "@vitejs/plugin-legacy": "^5.2.0", - "@vueuse/components": "^10.3.0", - "@vueuse/nuxt": "^10.2.1", - "@wasm-audio-decoders/ogg-vorbis": "^0.1.12", - "apexcharts": "^3.41.0", - "config": "^3.3.9", - "cross-env": "^7.0.3", - "daisyui": "^3.2.1", - "date-fns": "^2.30.0", - "dotenv": "^16.3.1", - "easymde": "^2.18.0", - "eslint": "^8.45.0", - "howler": "^2.2.3", - "marked": "^10.0.0", - "nuxt": "^3.8.0", - "nuxt-gtag": "^1.1.1", - "nuxt-icon": "^0.6.6", - "ogg-opus-decoder": "^1.6.8", - "sanitize-html": "^2.11.0", - "terser": "^5.19.2", - "typescript": "^5.1.6", - "vue-i18n": "9", - "vue-recaptcha": "^3.0.0-alpha.6", - "vue3-apexcharts": "^1.4.4" - } -} +{ + "name": "cytoid.io", + "type": "module", + "version": "2.0.0", + "private": true, + "description": "Cytoid is a music game where you can create, share and play your own levels! Powered by community, with many dedicated creators, Cytoid provides a huge variety of musical genres to enjoy and a diverse range of gameplay design.", + "scripts": { + "build": "nuxt build", + "start": "node .output/server/index.mjs", + "dev": "nuxt dev", + "dev:local": "cross-env NODE_APP_INSTANCE=local nuxt dev", + "gql": "npx cross-env NODE_ENV=production node ./cli/codegen-update.js", + "gql:local": "npx cross-env NODE_APP_INSTANCE=local node ./cli/codegen-update.js", + "generate": "nuxt generate", + "preview": "nuxt preview", + "postinstall": "nuxt prepare", + "lint": "eslint .", + "lint:fix": "eslint . --fix" + }, + "dependencies": { + "graphql": "^16.7.1" + }, + "devDependencies": { + "@antfu/eslint-config": "^2.1.0", + "@graphql-codegen/cli": "^5.0.0", + "@graphql-codegen/client-preset": "^4.0.1", + "@nuxt/devtools": "^1.0.3", + "@nuxtjs/device": "^3.1.1", + "@nuxtjs/google-fonts": "^3.0.2", + "@nuxtjs/i18n": "^8.0.0-rc.3", + "@nuxtjs/tailwindcss": "^6.8.0", + "@nuxtjs/turnstile": "^0.6.3", + "@tailwindcss/typography": "^0.5.9", + "@types/config": "^3.3.0", + "@types/glob": "^8.1.0", + "@types/howler": "^2.2.7", + "@types/lodash": "^4.14.196", + "@types/node": "^20.10.0", + "@types/sanitize-html": "^2.6.2", + "@typescript-eslint/eslint-plugin": "^6.1.0", + "@typescript-eslint/parser": "^6.1.0", + "@urql/core": "^4.1.0", + "@vite-pwa/nuxt": "^0.3.2", + "@vitejs/plugin-legacy": "^5.2.0", + "@vueuse/components": "^10.3.0", + "@vueuse/nuxt": "^10.2.1", + "@wasm-audio-decoders/ogg-vorbis": "^0.1.12", + "apexcharts": "^3.41.0", + "config": "^3.3.9", + "cross-env": "^7.0.3", + "daisyui": "^3.2.1", + "date-fns": "^2.30.0", + "dotenv": "^16.3.1", + "easymde": "^2.18.0", + "eslint": "^8.45.0", + "howler": "^2.2.3", + "marked": "^10.0.0", + "nuxt": "^3.8.0", + "nuxt-gtag": "^1.1.1", + "nuxt-icon": "^0.6.6", + "ogg-opus-decoder": "^1.6.8", + "sanitize-html": "^2.11.0", + "terser": "^5.19.2", + "typescript": "^5.1.6", + "vue-i18n": "9", + "vue3-apexcharts": "^1.4.4" + } +} diff --git a/pages/levels/[id]/index.vue b/pages/levels/[id]/index.vue index db8f003de..adeb8243d 100644 --- a/pages/levels/[id]/index.vue +++ b/pages/levels/[id]/index.vue @@ -132,8 +132,8 @@ async function downloadLevel(verify: () => Promise) { } if (downloadLink.value === '') { - const token = await verify() try { + const token = await verify() const { data: res } = await useServiceFetch(`/levels/${levelId}/resources`, { method: 'POST', body: { diff --git a/pages/session/link.vue b/pages/session/link.vue index 99b73bf69..c63ab480b 100644 --- a/pages/session/link.vue +++ b/pages/session/link.vue @@ -41,56 +41,59 @@ async function linkAccount(verify: () => Promise) { loading.value = true - const captchaToken = await verify() - // try to login - const response = await _loginWithPayload({ - ...linkForm, - remember: false, - captcha: captchaToken, - }) - if (response.error.value) { - const error = response.error.value - const code = error.statusCode - if (code === 401) { - errorAlert(t('general.login_password_error')) - return - } - else if (code === 403) { - warningAlert(t('general.login_inactive_error')) - return - } - else if (code === 404) { - // Account does not exist. - router.replace({ - name: 'session-signup', - query: { - token: token.value, - provider: provider.value, - username: linkForm.username, - origin: origin.value, - }, - }) + try { + const captchaToken = await verify() + // try to login + const response = await _loginWithPayload({ + ...linkForm, + remember: false, + captcha: captchaToken, + }) + if (response.error.value) { + const error = response.error.value + const code = error.statusCode + if (code === 401) { + errorAlert(t('general.login_password_error')) + return + } + else if (code === 403) { + warningAlert(t('general.login_inactive_error')) + return + } + else if (code === 404) { + // Account does not exist. + router.replace({ + name: 'session-signup', + query: { + token: token.value, + provider: provider.value, + username: linkForm.username, + origin: origin.value, + }, + }) + } + else { + handleErrorToast(error) + } } else { - handleErrorToast(error) - } - } - else { - if (user.value) { - // link Account - await useMutation(mutation, { - token: token.value, - }) + if (user.value) { + // link Account + await useMutation(mutation, { + token: token.value, + }) - successAlert(t('general.login_snack_bar', { name: user.value.name || user.value.uid })) - // loginNext() - if (route.query.origin) { - router.replace({ path: decodeURIComponent(route.query.origin.toString()) }) + successAlert(t('general.login_snack_bar', { name: user.value.name || user.value.uid })) + // loginNext() + if (route.query.origin) { + router.replace({ path: decodeURIComponent(route.query.origin.toString()) }) + } + else { router.replace({ name: 'settings-account' }) } } - else { router.replace({ name: 'settings-account' }) } } + } catch (error) { + handleErrorToast(error as Error) } - loading.value = false } diff --git a/pages/session/login.vue b/pages/session/login.vue index 7448925a2..cbf609ff0 100644 --- a/pages/session/login.vue +++ b/pages/session/login.vue @@ -25,36 +25,41 @@ async function loginWithPayload(verify: () => Promise) { loading.value = true - const captchaToken = await verify() - const response = await _loginWithPayload({ - ...loginForm, - username: loginForm.username.toLowerCase(), - captcha: captchaToken, - }) - loading.value = false + try { + const captchaToken = await verify() + const response = await _loginWithPayload({ + ...loginForm, + username: loginForm.username.toLowerCase(), + captcha: captchaToken, + }) - if (response.error.value) { - const error = response.error.value - const code = error.statusCode - if (code === 401) { - errorAlert(t('general.login_password_error')) - } - else if (code === 403) { - warningAlert(t('general.login_inactive_error')) - } - else if (code === 404) { - errorAlert(t('general.login_username_error')) + if (response.error.value) { + const error = response.error.value + const code = error.statusCode + if (code === 401) { + errorAlert(t('general.login_password_error')) + } + else if (code === 403) { + warningAlert(t('general.login_inactive_error')) + } + else if (code === 404) { + errorAlert(t('general.login_username_error')) + } + else { + handleErrorToast(error) + } + return } - else { - handleErrorToast(error) + + if (user.value) { + successAlert(t('general.login_snack_bar', { name: user.value.name || user.value.uid })) + loginNext() } - return + } catch (error) { + handleErrorToast(error as Error) } - if (user.value) { - successAlert(t('general.login_snack_bar', { name: user.value.name || user.value.uid })) - loginNext() - } + loading.value = false } function loginWithProvider(provider: string) { if (process.client) { diff --git a/pages/session/reset/index.vue b/pages/session/reset/index.vue index ca3ee72c2..536ff51e1 100644 --- a/pages/session/reset/index.vue +++ b/pages/session/reset/index.vue @@ -23,20 +23,24 @@ async function sendResetRequest(verify: () => Promise) { loading.value = true - const captchaToken = await verify() - const response = await useMutation(mutation, { - email: email.value, - captcha: captchaToken, - }).catch((e) => { - handleErrorToast(e) - }) - const success = response?.sendResetPasswordEmail ?? false - if (!success) { - errorAlert('The email was never registered / confirmed!') - } - else { - sent.value = email.value - startTimer() + try { + const captchaToken = await verify() + const response = await useMutation(mutation, { + email: email.value, + captcha: captchaToken, + }).catch((e) => { + handleErrorToast(e) + }) + const success = response?.sendResetPasswordEmail ?? false + if (!success) { + errorAlert('The email was never registered / confirmed!') + } + else { + sent.value = email.value + startTimer() + } + } catch (error) { + handleErrorToast(error as Error) } loading.value = false @@ -44,18 +48,20 @@ async function sendResetRequest(verify: () => Promise) { async function resend(verify: () => Promise) { loading.value = true - useTimeoutFn(() => { - loading.value = false - }, 1000) - - const captchaToken = await verify() - await useMutation(mutation, { - email: sent.value, - captcha: captchaToken, - }).catch((e) => { - handleErrorToast(e) - }) - + try { + const captchaToken = await verify() + await Promise.race([ + useTimeoutFn(() => { + throw new Error('Timeout') + }, 10000), + useMutation(mutation, { + email: sent.value, + captcha: captchaToken, + }), + ]) + } catch (error) { + handleErrorToast(error as Error) + } loading.value = false } diff --git a/pages/session/signup.vue b/pages/session/signup.vue index 3ccdc4d5c..5fd809edc 100644 --- a/pages/session/signup.vue +++ b/pages/session/signup.vue @@ -85,46 +85,48 @@ const verified = computed(() => { async function signUp(verify: () => Promise) { loading.value = true - const captchaToken = await verify() - - const response = await useServiceFetch('/session', { - method: 'PUT', - body: { - uid: form.value.username, - email: form.value.email, - password: form.value.password, - captcha: captchaToken, - }, - }) - const { data, error } = response - if (error.value || !data.value?.user) { - if (error.value?.message) { - handleErrorToast(new Error(error.value.message)) - } - else { - handleErrorToast(error.value ?? new Error('Unknown error')) + try { + const captchaToken = await verify() + + const response = await useServiceFetch('/session', { + method: 'PUT', + body: { + uid: form.value.username, + email: form.value.email, + password: form.value.password, + captcha: captchaToken, + }, + }) + const { data, error } = response + if (error.value || !data.value?.user) { + if (error.value?.message) { + handleErrorToast(new Error(error.value.message)) + } + else { + handleErrorToast(error.value ?? new Error('Unknown error')) + } + + loading.value = false + return } - loading.value = false - return - } + const userData = data.value?.user - const userData = data.value?.user + if (route.query.token) { + const token = route.query.token.toString() + // Bind account if register by third party + await useMutation(linkMutation, { + token, + }) + } - if (route.query.token) { - const token = route.query.token.toString() - // Bind account if register by third party - await useMutation(linkMutation, { - token, - }) + user.value = userData + successAlert('Sign up successfully!') + loginNext() + } catch (error) { + handleErrorToast(error as Error) } - - user.value = userData - - successAlert('Sign up successfully!') - loading.value = false - loginNext() } function loginNext() { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f5f9ffbf..f96767be6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,6 +34,9 @@ devDependencies: '@nuxtjs/tailwindcss': specifier: ^6.8.0 version: 6.10.1(rollup@2.79.1) + '@nuxtjs/turnstile': + specifier: ^0.6.3 + version: 0.6.3(rollup@2.79.1) '@tailwindcss/typography': specifier: ^0.5.9 version: 0.5.10(tailwindcss@3.3.5) @@ -133,9 +136,6 @@ devDependencies: vue-i18n: specifier: '9' version: 9.7.1(vue@3.3.9) - vue-recaptcha: - specifier: ^3.0.0-alpha.6 - version: 3.0.0-alpha.6(rollup@2.79.1)(vue@3.3.9) vue3-apexcharts: specifier: ^1.4.4 version: 1.4.4(apexcharts@3.44.0)(vue@3.3.9) @@ -3430,6 +3430,17 @@ packages: - ts-node dev: true + /@nuxtjs/turnstile@0.6.3(rollup@2.79.1): + resolution: {integrity: sha512-POo7J6mlN4apRKQQziyvbBN7IsOtDiDuH0euAWrFv5LUBuBlPcARJMsBhziYWp7hTSGpqS/DHj8/5mrVltQdzg==} + dependencies: + '@nuxt/kit': 3.8.2(rollup@2.79.1) + defu: 6.1.3 + pathe: 1.1.1 + transitivePeerDependencies: + - rollup + - supports-color + dev: true + /@parcel/watcher-android-arm64@2.3.0: resolution: {integrity: sha512-f4o9eA3dgk0XRT3XhB0UWpWpLnKgrh1IwNJKJ7UJek7eTYccQ8LR7XUWFKqw6aEq5KUNlCcGvSzKqSX/vtWVVA==} engines: {node: '>= 10.0.0'} @@ -3517,6 +3528,7 @@ packages: dependencies: is-glob: 4.0.3 micromatch: 4.0.5 + napi-wasm: 1.1.0 dev: true bundledDependencies: - napi-wasm @@ -8950,6 +8962,10 @@ packages: hasBin: true dev: true + /napi-wasm@1.1.0: + resolution: {integrity: sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg==} + dev: true + /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: true @@ -9571,11 +9587,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /p-defer@4.0.0: - resolution: {integrity: sha512-Vb3QRvQ0Y5XnF40ZUWW7JfLogicVh/EnA5gBIvKDJoYpeI82+1E3AlB9yOcKFS0AhHrWVnAQO39fbR0G99IVEQ==} - engines: {node: '>=12'} - dev: true - /p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -12392,29 +12403,6 @@ packages: vue: 3.3.9(typescript@5.3.2) dev: true - /vue-recaptcha@3.0.0-alpha.6(rollup@2.79.1)(vue@3.3.9): - resolution: {integrity: sha512-hwxxAXENLN6GKJhH6s+NJV1f6llSFEQ0jMTxjEunpR6CMbQ5xc7DAHFtwrsDutGnbS8wl9yp30jsYcCToZEhTQ==} - peerDependencies: - '@vue/composition-api': '*' - vue: ^3.0.0 - peerDependenciesMeta: - '@vue/composition-api': - optional: true - dependencies: - '@nuxt/kit': 3.8.2(rollup@2.79.1) - '@nuxt/schema': 3.8.2(rollup@2.79.1) - '@vueuse/shared': 10.6.1(vue@3.3.9) - defu: 6.1.3 - p-defer: 4.0.0 - std-env: 3.5.0 - type-fest: 3.13.1 - vue: 3.3.9(typescript@5.3.2) - vue-demi: 0.14.6(vue@3.3.9) - transitivePeerDependencies: - - rollup - - supports-color - dev: true - /vue-router@4.2.5(vue@3.3.9): resolution: {integrity: sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==} peerDependencies: