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

View File

@@ -0,0 +1,66 @@
import { describe, expect, test } from 'vitest';
import { isAllowedRedirectTarget } from '../redirect-allowlist';
describe('redirect allowlist', () => {
test('allows same hostname', () => {
expect(
isAllowedRedirectTarget('https://self.example.com/path', {
currentHostname: 'self.example.com',
})
).toBe(true);
});
test('allows trusted domains and subdomains', () => {
expect(
isAllowedRedirectTarget('https://github.com/toeverything/AFFiNE', {
currentHostname: 'self.example.com',
})
).toBe(true);
expect(
isAllowedRedirectTarget('https://sub.github.com/foo', {
currentHostname: 'self.example.com',
})
).toBe(true);
});
test('blocks look-alike domains', () => {
expect(
isAllowedRedirectTarget('https://evilgithub.com', {
currentHostname: 'self.example.com',
})
).toBe(false);
});
test('blocks disallowed protocols', () => {
expect(
isAllowedRedirectTarget('javascript:alert(1)', {
currentHostname: 'self.example.com',
})
).toBe(false);
});
test('handles port and trailing dot', () => {
expect(
isAllowedRedirectTarget('https://github.com:8443', {
currentHostname: 'self.example.com',
})
).toBe(true);
expect(
isAllowedRedirectTarget('https://affine.pro./', {
currentHostname: 'self.example.com',
})
).toBe(true);
});
test('blocks punycode homograph', () => {
// "а" is Cyrillic small a (U+0430), different from Latin "a"
expect(
isAllowedRedirectTarget('https://аffine.pro', {
currentHostname: 'self.example.com',
})
).toBe(false);
});
});

View File

@@ -4,6 +4,7 @@ export * from './exhaustmap-with-trailing';
export * from './fractional-indexing';
export * from './merge-updates';
export * from './object-pool';
export * from './redirect-allowlist';
export * from './stable-hash';
export * from './throw-if-aborted';
export * from './yjs-observable';

View File

@@ -0,0 +1,50 @@
export const TRUSTED_REDIRECT_DOMAINS = [
'google.com',
'stripe.com',
'github.com',
'twitter.com',
'discord.gg',
'youtube.com',
't.me',
'reddit.com',
'affine.pro',
].map(d => d.toLowerCase());
export const ALLOWED_REDIRECT_PROTOCOLS = new Set(['http:', 'https:']);
function normalizeHostname(hostname: string) {
return hostname.toLowerCase().replace(/\.$/, '');
}
function hostnameMatchesDomain(hostname: string, domain: string) {
return hostname === domain || hostname.endsWith(`.${domain}`);
}
export function isAllowedRedirectTarget(
redirectUri: string,
options: {
currentHostname: string;
}
) {
const currentHostname = normalizeHostname(options.currentHostname);
try {
const target = new URL(redirectUri);
if (!ALLOWED_REDIRECT_PROTOCOLS.has(target.protocol)) {
return false;
}
const hostname = normalizeHostname(target.hostname);
if (hostname === currentHostname) {
return true;
}
return TRUSTED_REDIRECT_DOMAINS.some(domain =>
hostnameMatchesDomain(hostname, domain)
);
} catch {
return false;
}
}