diff --git a/packages/frontend/core/src/components/affine/auth/oauth.tsx b/packages/frontend/core/src/components/affine/auth/oauth.tsx index 494d4ed575..926e199c94 100644 --- a/packages/frontend/core/src/components/affine/auth/oauth.tsx +++ b/packages/frontend/core/src/components/affine/auth/oauth.tsx @@ -1,9 +1,11 @@ import { Skeleton } from '@affine/component'; import { Button } from '@affine/component/ui/button'; +import { popupWindow } from '@affine/core/utils'; +import { appInfo } from '@affine/electron-api'; import { OAuthProviderType } from '@affine/graphql'; import { GithubIcon, GoogleDuotoneIcon } from '@blocksuite/icons/rc'; import { useLiveData, useService } from '@toeverything/infra'; -import type { ReactElement } from 'react'; +import { type ReactElement, useCallback } from 'react'; import { ServerConfigService } from '../../../modules/cloud'; @@ -46,25 +48,29 @@ export function OAuth() { function OAuthProvider({ provider }: { provider: OAuthProviderType }) { const { icon } = OAuthProviderMap[provider]; + const onClick = useCallback(() => { + let oauthUrl = + (environment.isElectron ? runtimeConfig.serverUrlPrefix : '') + + `/oauth/login?provider=${provider}`; + + if (environment.isElectron) { + oauthUrl += `&client=${appInfo?.schema}`; + } + + popupWindow(oauthUrl); + }, [provider]); + return ( - - - + Continue with {provider} + ); } diff --git a/packages/frontend/core/src/modules/cloud/services/auth.ts b/packages/frontend/core/src/modules/cloud/services/auth.ts index 9380d79208..95d651d92a 100644 --- a/packages/frontend/core/src/modules/cloud/services/auth.ts +++ b/packages/frontend/core/src/modules/cloud/services/auth.ts @@ -112,6 +112,7 @@ export class AuthService extends Service { async oauthPreflight( provider: OAuthProviderType, + client: string, /** @deprecated*/ redirectUrl?: string ) { const res = await this.fetchService.fetch('/api/oauth/preflight', { @@ -131,7 +132,7 @@ export class AuthService extends Service { 'state', JSON.stringify({ state: oauthUrl.searchParams.get('state'), - client: environment.isElectron ? appInfo?.schema : 'web', + client, }) ); url = oauthUrl.toString(); diff --git a/packages/frontend/core/src/pages/auth/common.ts b/packages/frontend/core/src/pages/auth/common.ts new file mode 100644 index 0000000000..bfde600e95 --- /dev/null +++ b/packages/frontend/core/src/pages/auth/common.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; + +export const supportedClient = z.enum([ + 'web', + 'affine', + 'affine-canary', + 'affine-beta', + ...(environment.isDebug ? ['affine-dev'] : []), +]); diff --git a/packages/frontend/core/src/pages/auth/magic-link.tsx b/packages/frontend/core/src/pages/auth/magic-link.tsx index 71ff26fc06..42ae697442 100644 --- a/packages/frontend/core/src/pages/auth/magic-link.tsx +++ b/packages/frontend/core/src/pages/auth/magic-link.tsx @@ -9,6 +9,7 @@ import { } from 'react-router-dom'; import { AuthService } from '../../modules/cloud'; +import { supportedClient } from './common'; interface LoaderData { token: string; @@ -38,6 +39,11 @@ export const loader: LoaderFunction = ({ request }) => { return payload; } + const clientCheckResult = supportedClient.safeParse(client); + if (!clientCheckResult.success) { + return redirect('/sign-in?error=Invalid callback parameters'); + } + const authParams = new URLSearchParams(); authParams.set('method', 'magic-link'); authParams.set('payload', JSON.stringify(payload)); diff --git a/packages/frontend/core/src/pages/auth/oauth-callback.tsx b/packages/frontend/core/src/pages/auth/oauth-callback.tsx index ffdc8b7844..d72a2ff2b2 100644 --- a/packages/frontend/core/src/pages/auth/oauth-callback.tsx +++ b/packages/frontend/core/src/pages/auth/oauth-callback.tsx @@ -9,6 +9,7 @@ import { } from 'react-router-dom'; import { AuthService } from '../../modules/cloud'; +import { supportedClient } from './common'; interface LoaderData { state: string; @@ -38,6 +39,11 @@ export const loader: LoaderFunction = async ({ request }) => { return payload; } + const clientCheckResult = supportedClient.safeParse(client); + if (!clientCheckResult.success) { + return redirect('/sign-in?error=Invalid oauth callback parameters'); + } + const authParams = new URLSearchParams(); authParams.set('method', 'oauth'); authParams.set('payload', JSON.stringify(payload)); diff --git a/packages/frontend/core/src/pages/auth/oauth-login.tsx b/packages/frontend/core/src/pages/auth/oauth-login.tsx index 11810c6cce..ed4ff242d0 100644 --- a/packages/frontend/core/src/pages/auth/oauth-login.tsx +++ b/packages/frontend/core/src/pages/auth/oauth-login.tsx @@ -11,32 +11,48 @@ import { } from 'react-router-dom'; import { z } from 'zod'; +import { supportedClient } from './common'; + const supportedProvider = z.nativeEnum(OAuthProviderType); +const oauthParameters = z.object({ + provider: supportedProvider, + client: supportedClient, + redirectUri: z.string().optional().nullable(), +}); + interface LoaderData { provider: OAuthProviderType; - redirectUri: string; + client: string; + redirectUri?: string; } export const loader: LoaderFunction = async ({ request }) => { const url = new URL(request.url); const searchParams = url.searchParams; const provider = searchParams.get('provider'); + const client = searchParams.get('client') ?? 'web'; const redirectUri = searchParams.get('redirect_uri'); // sign out first await fetch('/api/auth/sign-out'); - const maybeProvider = supportedProvider.safeParse(provider); - if (maybeProvider.success) { + const paramsParseResult = oauthParameters.safeParse({ + provider, + client, + redirectUri, + }); + + if (paramsParseResult.success) { return { provider, + client, redirectUri, }; } return redirect( - `/sign-in?error=${encodeURIComponent(`Invalid oauth provider ${provider}`)}` + `/sign-in?error=${encodeURIComponent(`Invalid oauth parameters`)}` ); }; @@ -48,7 +64,7 @@ export const Component = () => { useEffect(() => { auth - .oauthPreflight(data.provider, data.redirectUri) + .oauthPreflight(data.provider, data.client, data.redirectUri) .then(url => { // this is the url of oauth provider auth page, can't navigate with react-router location.href = url;