diff --git a/apps/admin/config/use-global.tsx b/apps/admin/config/use-global.tsx index 8560bf5..efdf37a 100644 --- a/apps/admin/config/use-global.tsx +++ b/apps/admin/config/use-global.tsx @@ -74,6 +74,7 @@ export const useGlobalStore = create((set) => ({ subscribe_domain: '', pan_domain: false, }, + oauth_methods: [], }, user: undefined, setCommon: (common) => diff --git a/apps/admin/services/common/typings.d.ts b/apps/admin/services/common/typings.d.ts index 732367e..751d088 100644 --- a/apps/admin/services/common/typings.d.ts +++ b/apps/admin/services/common/typings.d.ts @@ -161,6 +161,7 @@ declare namespace API { invite: InviteConfig; currency: CurrencyConfig; subscribe: SubscribeConfig; + oauth_methods: string[]; }; type GetStatResponse = { @@ -227,6 +228,7 @@ declare namespace API { type OAthLoginRequest = { /** google, facebook, apple, telegram, github etc. */ method: string; + redirect: string; }; type OAuthLoginResponse = { diff --git a/apps/user/app/auth/page.tsx b/apps/user/app/auth/page.tsx index ac26c8d..9d57b47 100644 --- a/apps/user/app/auth/page.tsx +++ b/apps/user/app/auth/page.tsx @@ -3,8 +3,11 @@ import LanguageSwitch from '@/components/language-switch'; import ThemeSwitch from '@/components/theme-switch'; import useGlobalStore from '@/config/use-global'; +import { oAuthLogin } from '@/services/common/oauth'; import { DotLottieReact } from '@lottiefiles/dotlottie-react'; +import { Button } from '@workspace/ui/components/button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs'; +import { Icon } from '@workspace/ui/custom-components/icon'; import LoginLottie from '@workspace/ui/lotties/login.json'; import { useTranslations } from 'next-intl'; import Image from 'next/legacy/image'; @@ -12,10 +15,18 @@ import Link from 'next/link'; import EmailAuthForm from './email/auth-form'; import PhoneAuthForm from './phone/auth-form'; +const icons = { + apple: 'uil:apple', + google: 'logos:google-icon', + facebook: 'logos:facebook', + github: 'uil:github', + telegram: 'logos:telegram', +}; + export default function Page() { const t = useTranslations('auth'); const { common } = useGlobalStore(); - const { site, auth } = common; + const { site, auth, oauth_methods } = common; const AUTH_COMPONENT_MAP = { email: , @@ -23,15 +34,21 @@ export default function Page() { } as const; type AuthMethod = keyof typeof AUTH_COMPONENT_MAP; - const enabledAuthMethods = Object.entries(auth).reduce((acc, [key, value]) => { - const enabledKey = `${key}_enabled` as const; - if (typeof value === 'object' && value !== null && enabledKey in value) { - const enabled = (value as Record)[enabledKey]; - if (enabled) acc.push(key as AuthMethod); - } - return acc; - }, []); + const enabledAuthMethods = (Object.keys(AUTH_COMPONENT_MAP) as AuthMethod[]).filter((key) => { + const value = auth[key]; + const enabledKey = `${key}_enabled` as const; + + if (typeof value !== 'object' || value === null) { + return false; + } + + if (!(enabledKey in value)) { + return false; + } + const isEnabled = (value as unknown as Record)[enabledKey]; + return isEnabled; + }); return (
@@ -57,7 +74,7 @@ export default function Page() {
-
+

{t('verifyAccount')}

{t('verifyAccountDesc')} @@ -81,6 +98,38 @@ export default function Page() { )}
+
+ {oauth_methods?.length > 0 && ( + <> +
+ + Or continue with + +
+
+ {oauth_methods?.map((method: any) => { + return ( + + ); + })} +
+ + )} +
diff --git a/apps/user/app/oauth/[platform]/certification.tsx b/apps/user/app/oauth/[platform]/certification.tsx new file mode 100644 index 0000000..3d21751 --- /dev/null +++ b/apps/user/app/oauth/[platform]/certification.tsx @@ -0,0 +1,51 @@ +'use client'; + +import { + appleLoginCallback, + facebookLoginCallback, + googleLoginCallback, + telegramLoginCallback, +} from '@/services/common/oauth'; +import { getRedirectUrl, setAuthorization } from '@/utils/common'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { useEffect } from 'react'; + +interface CertificationProps { + platform: string; + children: React.ReactNode; +} + +export default function Certification({ platform, children }: CertificationProps) { + const searchParams = useSearchParams(); + const router = useRouter(); + + async function LoginCallback() { + const body = Object.fromEntries(searchParams.entries()) as any; + switch (platform) { + case 'apple': + return appleLoginCallback(body); + case 'facebook': + return facebookLoginCallback(body); + case 'google': + return googleLoginCallback(body); + case 'telegram': + return telegramLoginCallback(body); + default: + break; + } + } + useEffect(() => { + LoginCallback() + .then((res) => { + const token = res?.data?.data?.token; + setAuthorization(token); + router.replace(getRedirectUrl()); + router.refresh(); + }) + .catch((error) => { + router.replace('/auth'); + }); + }, [platform, searchParams.values()]); + + return children; +} diff --git a/apps/user/app/oauth/[platform]/page.tsx b/apps/user/app/oauth/[platform]/page.tsx new file mode 100644 index 0000000..f2c4528 --- /dev/null +++ b/apps/user/app/oauth/[platform]/page.tsx @@ -0,0 +1,53 @@ +import HyperText from '@workspace/ui/components/hyper-text'; +import { OrbitingCircles } from '@workspace/ui/components/orbiting-circles'; +import { Icon } from '@workspace/ui/custom-components/icon'; +import { getTranslations } from 'next-intl/server'; +import Certification from './certification'; + +export async function generateStaticParams() { + return { + paths: [ + { params: { platform: 'telegram' } }, + { params: { platform: 'apple' } }, + { params: { platform: 'facebook' } }, + { params: { platform: 'google' } }, + { params: { platform: 'github' } }, + ], + fallback: false, + }; +} + +export default async function Page({ + params: { platform }, +}: { + params: { + platform: string; + }; +}) { + const t = await getTranslations('auth'); + return ( + +
+
+ {platform} + {t('authenticating')} +
+ + + + + + + + + + + + + + + +
+
+ ); +} diff --git a/apps/user/config/use-global.tsx b/apps/user/config/use-global.tsx index 1148dad..c3c2181 100644 --- a/apps/user/config/use-global.tsx +++ b/apps/user/config/use-global.tsx @@ -64,6 +64,7 @@ export const useGlobalStore = create((set, get) => ({ subscribe_domain: '', pan_domain: false, }, + oauth_methods: [], }, user: undefined, setCommon: (common) => diff --git a/apps/user/locales/cs-CZ/auth.json b/apps/user/locales/cs-CZ/auth.json index ef2bf0a..875eae9 100644 --- a/apps/user/locales/cs-CZ/auth.json +++ b/apps/user/locales/cs-CZ/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Ověřování...", "check": { "checking": "Ověřování...", "continue": "Pokračovat", diff --git a/apps/user/locales/de-DE/auth.json b/apps/user/locales/de-DE/auth.json index b05be60..e3ed919 100644 --- a/apps/user/locales/de-DE/auth.json +++ b/apps/user/locales/de-DE/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Authentifizierung...", "check": { "checking": "Überprüfung läuft...", "continue": "Fortfahren", diff --git a/apps/user/locales/en-US/auth.json b/apps/user/locales/en-US/auth.json index a6356d6..ad60711 100644 --- a/apps/user/locales/en-US/auth.json +++ b/apps/user/locales/en-US/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Authenticating...", "check": { "checking": "Checking...", "continue": "Continue", diff --git a/apps/user/locales/es-ES/auth.json b/apps/user/locales/es-ES/auth.json index 70abeb5..bd97a3d 100644 --- a/apps/user/locales/es-ES/auth.json +++ b/apps/user/locales/es-ES/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Autenticando...", "check": { "checking": "Verificando...", "continue": "Continuar", diff --git a/apps/user/locales/es-MX/auth.json b/apps/user/locales/es-MX/auth.json index 2a4649e..5aa780b 100644 --- a/apps/user/locales/es-MX/auth.json +++ b/apps/user/locales/es-MX/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Autenticando...", "check": { "checking": "Verificando...", "continue": "Continuar", diff --git a/apps/user/locales/fa-IR/auth.json b/apps/user/locales/fa-IR/auth.json index 190963f..355dd50 100644 --- a/apps/user/locales/fa-IR/auth.json +++ b/apps/user/locales/fa-IR/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "در حال احراز هویت...", "check": { "checking": "در حال بررسی...", "continue": "ادامه", diff --git a/apps/user/locales/fi-FI/auth.json b/apps/user/locales/fi-FI/auth.json index bbf21f3..8484ef6 100644 --- a/apps/user/locales/fi-FI/auth.json +++ b/apps/user/locales/fi-FI/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Tunnistautuminen...", "check": { "checking": "Tarkistetaan...", "continue": "Jatka", diff --git a/apps/user/locales/fr-FR/auth.json b/apps/user/locales/fr-FR/auth.json index 0f83b4f..6597c47 100644 --- a/apps/user/locales/fr-FR/auth.json +++ b/apps/user/locales/fr-FR/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Authentification en cours...", "check": { "checking": "Vérification en cours...", "continue": "Continuer", diff --git a/apps/user/locales/hi-IN/auth.json b/apps/user/locales/hi-IN/auth.json index 0535f76..be04eed 100644 --- a/apps/user/locales/hi-IN/auth.json +++ b/apps/user/locales/hi-IN/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "प्रमाणित किया जा रहा है...", "check": { "checking": "सत्यापन हो रहा है...", "continue": "जारी रखें", diff --git a/apps/user/locales/hu-HU/auth.json b/apps/user/locales/hu-HU/auth.json index 16ef874..59f3c92 100644 --- a/apps/user/locales/hu-HU/auth.json +++ b/apps/user/locales/hu-HU/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Hitelesítés folyamatban...", "check": { "checking": "Ellenőrzés folyamatban...", "continue": "Folytatás", diff --git a/apps/user/locales/ja-JP/auth.json b/apps/user/locales/ja-JP/auth.json index bdadb73..1d72f93 100644 --- a/apps/user/locales/ja-JP/auth.json +++ b/apps/user/locales/ja-JP/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "認証中...", "check": { "checking": "検証中...", "continue": "続ける", diff --git a/apps/user/locales/ko-KR/auth.json b/apps/user/locales/ko-KR/auth.json index 6ccede3..b05c2d2 100644 --- a/apps/user/locales/ko-KR/auth.json +++ b/apps/user/locales/ko-KR/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "인증 중...", "check": { "checking": "확인 중...", "continue": "계속", diff --git a/apps/user/locales/no-NO/auth.json b/apps/user/locales/no-NO/auth.json index 0318ea9..5327821 100644 --- a/apps/user/locales/no-NO/auth.json +++ b/apps/user/locales/no-NO/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Autentiserer...", "check": { "checking": "Verifiserer...", "continue": "Fortsett", diff --git a/apps/user/locales/pl-PL/auth.json b/apps/user/locales/pl-PL/auth.json index 510bba6..f508b47 100644 --- a/apps/user/locales/pl-PL/auth.json +++ b/apps/user/locales/pl-PL/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Uwierzytelnianie...", "check": { "checking": "Sprawdzanie...", "continue": "Kontynuuj", diff --git a/apps/user/locales/pt-BR/auth.json b/apps/user/locales/pt-BR/auth.json index 4a0f7f5..88c332a 100644 --- a/apps/user/locales/pt-BR/auth.json +++ b/apps/user/locales/pt-BR/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Autenticando...", "check": { "checking": "Verificando...", "continue": "Continuar", diff --git a/apps/user/locales/ro-RO/auth.json b/apps/user/locales/ro-RO/auth.json index eae7b22..37b3a8c 100644 --- a/apps/user/locales/ro-RO/auth.json +++ b/apps/user/locales/ro-RO/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Autentificare...", "check": { "checking": "Se verifică...", "continue": "Continuă", diff --git a/apps/user/locales/ru-RU/auth.json b/apps/user/locales/ru-RU/auth.json index ff1f5d6..41e4fe1 100644 --- a/apps/user/locales/ru-RU/auth.json +++ b/apps/user/locales/ru-RU/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Аутентификация...", "check": { "checking": "Проверка...", "continue": "Продолжить", diff --git a/apps/user/locales/th-TH/auth.json b/apps/user/locales/th-TH/auth.json index d221d87..c1ce7eb 100644 --- a/apps/user/locales/th-TH/auth.json +++ b/apps/user/locales/th-TH/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "กำลังตรวจสอบสิทธิ์...", "check": { "checking": "กำลังตรวจสอบ...", "continue": "ดำเนินการต่อ", diff --git a/apps/user/locales/tr-TR/auth.json b/apps/user/locales/tr-TR/auth.json index 8ba87dd..dfb95d4 100644 --- a/apps/user/locales/tr-TR/auth.json +++ b/apps/user/locales/tr-TR/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Kimlik doğrulanıyor...", "check": { "checking": "Doğrulanıyor...", "continue": "Devam et", diff --git a/apps/user/locales/uk-UA/auth.json b/apps/user/locales/uk-UA/auth.json index aa8a0c5..4900684 100644 --- a/apps/user/locales/uk-UA/auth.json +++ b/apps/user/locales/uk-UA/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Аутентифікація...", "check": { "checking": "Перевірка...", "continue": "Продовжити", diff --git a/apps/user/locales/vi-VN/auth.json b/apps/user/locales/vi-VN/auth.json index 5df9b95..03909db 100644 --- a/apps/user/locales/vi-VN/auth.json +++ b/apps/user/locales/vi-VN/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "Đang xác thực...", "check": { "checking": "Đang kiểm tra...", "continue": "Tiếp tục", diff --git a/apps/user/locales/zh-CN/auth.json b/apps/user/locales/zh-CN/auth.json index 655d412..01a5f3e 100644 --- a/apps/user/locales/zh-CN/auth.json +++ b/apps/user/locales/zh-CN/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "正在验证...", "check": { "checking": "正在验证...", "continue": "继续", diff --git a/apps/user/locales/zh-HK/auth.json b/apps/user/locales/zh-HK/auth.json index 7d594b8..7a9f4d4 100644 --- a/apps/user/locales/zh-HK/auth.json +++ b/apps/user/locales/zh-HK/auth.json @@ -1,4 +1,5 @@ { + "authenticating": "正在驗證...", "check": { "checking": "正在驗證...", "continue": "繼續", diff --git a/apps/user/services/common/index.ts b/apps/user/services/common/index.ts index 310d348..61ba129 100644 --- a/apps/user/services/common/index.ts +++ b/apps/user/services/common/index.ts @@ -1,10 +1,12 @@ // @ts-ignore -/* eslint-disable */ + // API 更新时间: // API 唯一标识: import * as auth from './auth'; import * as common from './common'; +import * as oauth from './oauth'; export default { auth, + oauth, common, }; diff --git a/apps/user/services/common/oauth.ts b/apps/user/services/common/oauth.ts new file mode 100644 index 0000000..5e42f5d --- /dev/null +++ b/apps/user/services/common/oauth.ts @@ -0,0 +1,80 @@ +// @ts-ignore +/* eslint-disable */ +import request from '@/utils/request'; + +/** Apple Login Callback POST /v1/auth/oauth/callback/apple */ +export async function appleLoginCallback( + body: { + code: string; + id_token: string; + state: string; + }, + options?: { [key: string]: any }, +) { + const formData = new FormData(); + + Object.keys(body).forEach((ele) => { + const item = (body as any)[ele]; + + if (item !== undefined && item !== null) { + if (typeof item === 'object' && !(item instanceof File)) { + if (item instanceof Array) { + item.forEach((f) => formData.append(ele, f || '')); + } else { + formData.append(ele, JSON.stringify(item)); + } + } else { + formData.append(ele, item); + } + } + }); + + return request('/v1/auth/oauth/callback/apple', { + method: 'POST', + data: formData, + ...(options || {}), + }); +} + +/** Facebook Login Callback GET /v1/auth/oauth/callback/facebook */ +export async function facebookLoginCallback(options?: { [key: string]: any }) { + return request('/v1/auth/oauth/callback/facebook', { + method: 'GET', + ...(options || {}), + }); +} + +/** Google Login Callback GET /v1/auth/oauth/callback/google */ +export async function googleLoginCallback( + // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) + params: API.GoogleLoginCallbackParams, + options?: { [key: string]: any }, +) { + return request('/v1/auth/oauth/callback/google', { + method: 'GET', + params: { + ...params, + }, + ...(options || {}), + }); +} + +/** Telegram Login Callback GET /v1/auth/oauth/callback/telegram */ +export async function telegramLoginCallback(options?: { [key: string]: any }) { + return request('/v1/auth/oauth/callback/telegram', { + method: 'GET', + ...(options || {}), + }); +} + +/** OAuth login POST /v1/auth/oauth/login */ +export async function oAuthLogin(body: API.OAthLoginRequest, options?: { [key: string]: any }) { + return request('/v1/auth/oauth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} diff --git a/apps/user/services/common/typings.d.ts b/apps/user/services/common/typings.d.ts index 04164fb..751d088 100644 --- a/apps/user/services/common/typings.d.ts +++ b/apps/user/services/common/typings.d.ts @@ -10,6 +10,12 @@ declare namespace API { updated_at: number; }; + type AppleLoginCallbackRequest = { + code: string; + id_token: string; + state: string; + }; + type Application = { id: number; icon: string; @@ -19,6 +25,9 @@ declare namespace API { }; type ApplicationConfig = { + app_id: number; + encryption_key: string; + encryption_method: string; domains: string[]; startup_picture: string; startup_picture_skip_time: number; @@ -152,6 +161,7 @@ declare namespace API { invite: InviteConfig; currency: CurrencyConfig; subscribe: SubscribeConfig; + oauth_methods: string[]; }; type GetStatResponse = { @@ -169,6 +179,16 @@ declare namespace API { tos_content: string; }; + type GoogleLoginCallbackParams = { + code: string; + state: string; + }; + + type GoogleLoginCallbackRequest = { + code: string; + state: string; + }; + type Hysteria2 = { port: number; hop_ports: string; @@ -205,6 +225,26 @@ declare namespace API { last_at: number; }; + type OAthLoginRequest = { + /** google, facebook, apple, telegram, github etc. */ + method: string; + redirect: string; + }; + + type OAuthLoginResponse = { + redirect: string; + }; + + type OAuthMethod = { + id: number; + platform: string; + config: Record; + redirect: string; + enabled: boolean; + created_at: number; + updated_at: number; + }; + type OnlineUser = { uid: number; ip: string; diff --git a/apps/user/services/user/typings.d.ts b/apps/user/services/user/typings.d.ts index 6076cb4..554dbb9 100644 --- a/apps/user/services/user/typings.d.ts +++ b/apps/user/services/user/typings.d.ts @@ -19,6 +19,9 @@ declare namespace API { }; type ApplicationConfig = { + app_id: number; + encryption_key: string; + encryption_method: string; domains: string[]; startup_picture: string; startup_picture_skip_time: number; @@ -226,6 +229,16 @@ declare namespace API { last_at: number; }; + type OAuthMethod = { + id: number; + platform: string; + config: Record; + redirect: string; + enabled: boolean; + created_at: number; + updated_at: number; + }; + type OnlineUser = { uid: number; ip: string; diff --git a/apps/user/utils/common.ts b/apps/user/utils/common.ts index 734a4b0..2f36119 100644 --- a/apps/user/utils/common.ts +++ b/apps/user/utils/common.ts @@ -45,7 +45,7 @@ export function Logout() { if (!isBrowser()) return; cookies.remove('Authorization'); const pathname = location.pathname; - if (!['', '/', '/auth', '/tos'].includes(pathname)) { + if (!['', '/', '/auth', '/tos', '/oauth'].includes(pathname)) { setRedirectUrl(location.pathname); location.href = `/auth`; }