chore: drop old client support (#14369)

This commit is contained in:
DarkSky
2026-02-05 02:49:33 +08:00
committed by GitHub
parent de29e8300a
commit 403f16b404
103 changed files with 3293 additions and 997 deletions
@@ -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({