mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-09 05:05:52 +08:00
chore: drop old client support (#14369)
This commit is contained in:
@@ -14,6 +14,23 @@ import { z } from 'zod';
|
||||
import { supportedClient } from './common';
|
||||
|
||||
const supportedProvider = z.nativeEnum(OAuthProviderType);
|
||||
const CSRF_COOKIE_NAME = 'affine_csrf_token';
|
||||
|
||||
function getCookieValue(name: string) {
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cookies = document.cookie ? document.cookie.split('; ') : [];
|
||||
for (const cookie of cookies) {
|
||||
const idx = cookie.indexOf('=');
|
||||
const key = idx === -1 ? cookie : cookie.slice(0, idx);
|
||||
if (key === name) {
|
||||
return idx === -1 ? '' : cookie.slice(idx + 1);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const oauthParameters = z.object({
|
||||
provider: supportedProvider,
|
||||
@@ -36,7 +53,11 @@ export const loader: LoaderFunction = async ({ request }) => {
|
||||
|
||||
// sign out first, web only
|
||||
if (client === 'web') {
|
||||
await fetch('/api/auth/sign-out');
|
||||
const csrfToken = getCookieValue(CSRF_COOKIE_NAME);
|
||||
await fetch('/api/auth/sign-out', {
|
||||
method: 'POST',
|
||||
headers: csrfToken ? { 'x-affine-csrf-token': csrfToken } : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
const paramsParseResult = oauthParameters.safeParse({
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||
import { GraphQLService } from '@affine/core/modules/cloud';
|
||||
import { AuthService } from '@affine/core/modules/cloud';
|
||||
import { OpenInAppPage } from '@affine/core/modules/open-in-app/views/open-in-app-page';
|
||||
import {
|
||||
appSchemaUrl,
|
||||
appSchemes,
|
||||
channelToScheme,
|
||||
} from '@affine/core/utils/channel';
|
||||
import type { GetCurrentUserQuery } from '@affine/graphql';
|
||||
import { getCurrentUserQuery } from '@affine/graphql';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { AppContainer } from '../../components/app-container';
|
||||
@@ -49,38 +47,43 @@ const OpenUrl = () => {
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const OpenOAuthJwt = () => {
|
||||
const [currentUser, setCurrentUser] = useState<
|
||||
GetCurrentUserQuery['currentUser'] | null
|
||||
>(null);
|
||||
const OpenAppSignInRedirect = () => {
|
||||
const authService = useService(AuthService);
|
||||
const [params] = useSearchParams();
|
||||
const graphqlService = useService(GraphQLService);
|
||||
const triggeredRef = useRef(false);
|
||||
const [urlToOpen, setUrlToOpen] = useState<string | null>(null);
|
||||
|
||||
const maybeScheme = appSchemes.safeParse(params.get('scheme'));
|
||||
const scheme = maybeScheme.success
|
||||
? maybeScheme.data
|
||||
: channelToScheme[BUILD_CONFIG.appBuildType];
|
||||
const next = params.get('next') || '';
|
||||
const next = params.get('next') || undefined;
|
||||
|
||||
useEffect(() => {
|
||||
graphqlService
|
||||
.gql({
|
||||
query: getCurrentUserQuery,
|
||||
})
|
||||
.then(res => {
|
||||
setCurrentUser(res?.currentUser || null);
|
||||
if (triggeredRef.current) {
|
||||
return;
|
||||
}
|
||||
triggeredRef.current = true;
|
||||
|
||||
authService
|
||||
.createOpenAppSignInCode()
|
||||
.then(code => {
|
||||
const authParams = new URLSearchParams();
|
||||
authParams.set('method', 'open-app-signin');
|
||||
authParams.set(
|
||||
'payload',
|
||||
JSON.stringify(next ? { code, next } : { code })
|
||||
);
|
||||
authParams.set('server', location.origin);
|
||||
setUrlToOpen(`${scheme}://authentication?${authParams.toString()}`);
|
||||
})
|
||||
.catch(console.error);
|
||||
}, [graphqlService]);
|
||||
}, [authService, next, scheme]);
|
||||
|
||||
if (!currentUser || !currentUser?.token?.sessionToken) {
|
||||
if (!urlToOpen) {
|
||||
return <AppContainer fallback />;
|
||||
}
|
||||
|
||||
const urlToOpen = `${scheme}://signin-redirect?token=${
|
||||
currentUser.token.sessionToken
|
||||
}&next=${next}`;
|
||||
|
||||
return <OpenInAppPage urlToOpen={urlToOpen} />;
|
||||
};
|
||||
|
||||
@@ -91,7 +94,7 @@ export const Component = () => {
|
||||
if (action === 'url') {
|
||||
return <OpenUrl />;
|
||||
} else if (action === 'signin-redirect') {
|
||||
return <OpenOAuthJwt />;
|
||||
return <OpenAppSignInRedirect />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { escapeRegExp } from 'lodash-es';
|
||||
import { isAllowedRedirectTarget } from '@toeverything/infra';
|
||||
import { type LoaderFunction, Navigate, useLoaderData } from 'react-router-dom';
|
||||
|
||||
const trustedDomain = [
|
||||
'google.com',
|
||||
'stripe.com',
|
||||
'github.com',
|
||||
'twitter.com',
|
||||
'discord.gg',
|
||||
'youtube.com',
|
||||
't.me',
|
||||
'reddit.com',
|
||||
'affine.pro',
|
||||
];
|
||||
|
||||
const logger = new DebugLogger('redirect_proxy');
|
||||
const ALLOWED_PROTOCOLS = new Set(['http:', 'https:']);
|
||||
|
||||
/**
|
||||
* /redirect-proxy page
|
||||
@@ -31,26 +18,13 @@ export const loader: LoaderFunction = async ({ request }) => {
|
||||
return { allow: false };
|
||||
}
|
||||
|
||||
try {
|
||||
const target = new URL(redirectUri);
|
||||
|
||||
if (!ALLOWED_PROTOCOLS.has(target.protocol)) {
|
||||
logger.warn('Blocked redirect with disallowed protocol', target.protocol);
|
||||
return { allow: false };
|
||||
}
|
||||
|
||||
if (
|
||||
target.hostname === window.location.hostname ||
|
||||
trustedDomain.some(domain =>
|
||||
new RegExp(`(^|\\.)${escapeRegExp(domain)}$`).test(target.hostname)
|
||||
)
|
||||
) {
|
||||
location.href = redirectUri;
|
||||
return { allow: true };
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Failed to parse redirect uri', e);
|
||||
return { allow: false };
|
||||
if (
|
||||
isAllowedRedirectTarget(redirectUri, {
|
||||
currentHostname: window.location.hostname,
|
||||
})
|
||||
) {
|
||||
location.href = redirectUri;
|
||||
return { allow: true };
|
||||
}
|
||||
|
||||
logger.warn('Blocked redirect to untrusted domain', redirectUri);
|
||||
|
||||
@@ -4,6 +4,24 @@ import { AuthProvider } from '../provider/auth';
|
||||
import { ServerScope } from '../scopes/server';
|
||||
import { FetchService } from '../services/fetch';
|
||||
|
||||
const CSRF_COOKIE_NAME = 'affine_csrf_token';
|
||||
|
||||
function getCookieValue(name: string) {
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cookies = document.cookie ? document.cookie.split('; ') : [];
|
||||
for (const cookie of cookies) {
|
||||
const idx = cookie.indexOf('=');
|
||||
const key = idx === -1 ? cookie : cookie.slice(0, idx);
|
||||
if (key === name) {
|
||||
return idx === -1 ? '' : cookie.slice(idx + 1);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function configureDefaultAuthProvider(framework: Framework) {
|
||||
framework.scope(ServerScope).override(AuthProvider, resolver => {
|
||||
const fetchService = resolver.get(FetchService);
|
||||
@@ -62,7 +80,11 @@ export function configureDefaultAuthProvider(framework: Framework) {
|
||||
});
|
||||
},
|
||||
async signOut() {
|
||||
await fetchService.fetch('/api/auth/sign-out');
|
||||
const csrfToken = getCookieValue(CSRF_COOKIE_NAME);
|
||||
await fetchService.fetch('/api/auth/sign-out', {
|
||||
method: 'POST',
|
||||
headers: csrfToken ? { 'x-affine-csrf-token': csrfToken } : undefined,
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -165,6 +165,32 @@ export class AuthService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
async createOpenAppSignInCode() {
|
||||
const res = await this.fetchService.fetch(
|
||||
'/api/auth/open-app/sign-in-code',
|
||||
{
|
||||
method: 'POST',
|
||||
}
|
||||
);
|
||||
const body = (await res.json()) as { code?: string };
|
||||
|
||||
if (!body.code) {
|
||||
throw new Error('Missing open-app sign-in code');
|
||||
}
|
||||
|
||||
return body.code;
|
||||
}
|
||||
|
||||
async signInOpenAppSignInCode(code: string) {
|
||||
await this.fetchService.fetch('/api/auth/open-app/sign-in', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ code }),
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
|
||||
this.session.revalidate();
|
||||
}
|
||||
|
||||
async signInPassword(credential: {
|
||||
email: string;
|
||||
password: string;
|
||||
|
||||
@@ -146,6 +146,14 @@ export class DesktopApiService extends Service {
|
||||
await authService.signInOauth(code, state, provider);
|
||||
break;
|
||||
}
|
||||
case 'open-app-signin': {
|
||||
const code = (payload as { code?: unknown }).code;
|
||||
if (typeof code !== 'string' || !code) {
|
||||
throw new Error('Invalid open-app sign-in payload');
|
||||
}
|
||||
await authService.signInOpenAppSignInCode(code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})().catch(e => {
|
||||
notify.error({
|
||||
|
||||
Reference in New Issue
Block a user