mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
chore: drop old client support (#14369)
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
50
packages/common/infra/src/utils/redirect-allowlist.ts
Normal file
50
packages/common/infra/src/utils/redirect-allowlist.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user