mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-23 13:29:02 +08:00
feat: drop outdated session (#14373)
#### PR Dependency Tree * **PR #14373** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added client version tracking and validation to ensure application compatibility across authentication flows and sessions. * Enhanced OAuth authentication with improved version handling during sign-in and refresh operations. * **Bug Fixes** * Improved payment callback URL handling with safer defaults for redirect links. * **Tests** * Expanded test coverage for client version enforcement and session management. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { expect, test } from 'vitest';
|
||||
|
||||
import {
|
||||
buildAuthenticationDeepLink,
|
||||
buildOpenAppUrlRoute,
|
||||
normalizeOpenAppSignInNextParam,
|
||||
} from '../utils';
|
||||
|
||||
test('buildAuthenticationDeepLink', () => {
|
||||
const payload = { code: '1', next: '/workspace/123' };
|
||||
const url = buildAuthenticationDeepLink({
|
||||
scheme: 'affine',
|
||||
method: 'open-app-signin',
|
||||
payload,
|
||||
server: 'https://app.affine.local',
|
||||
});
|
||||
|
||||
const parsed = new URL(url);
|
||||
|
||||
expect(parsed.protocol).toBe('affine:');
|
||||
expect(parsed.hostname).toBe('authentication');
|
||||
expect(parsed.searchParams.get('method')).toBe('open-app-signin');
|
||||
expect(parsed.searchParams.get('payload')).toBe(JSON.stringify(payload));
|
||||
expect(parsed.searchParams.get('server')).toBe('https://app.affine.local');
|
||||
});
|
||||
|
||||
test('buildOpenAppUrlRoute', () => {
|
||||
const urlToOpen = 'affine://authentication?method=oauth&payload=%7B%7D';
|
||||
const route = buildOpenAppUrlRoute(urlToOpen);
|
||||
|
||||
const parsed = new URL(route, 'https://app.affine.local');
|
||||
expect(parsed.pathname).toBe('/open-app/url');
|
||||
expect(parsed.searchParams.get('url')).toBe(urlToOpen);
|
||||
});
|
||||
|
||||
test('normalizeOpenAppSignInNextParam', () => {
|
||||
expect(
|
||||
normalizeOpenAppSignInNextParam(
|
||||
'/workspace/123',
|
||||
'https://app.affine.local'
|
||||
)
|
||||
).toBe('/workspace/123');
|
||||
|
||||
expect(
|
||||
normalizeOpenAppSignInNextParam(
|
||||
'https://app.affine.local/workspace/123?foo=1#bar',
|
||||
'https://app.affine.local'
|
||||
)
|
||||
).toBe('/workspace/123?foo=1#bar');
|
||||
|
||||
expect(
|
||||
normalizeOpenAppSignInNextParam(
|
||||
'https://evil.example/workspace/123',
|
||||
'https://app.affine.local'
|
||||
)
|
||||
).toBeUndefined();
|
||||
|
||||
expect(
|
||||
normalizeOpenAppSignInNextParam(
|
||||
'//evil.example/workspace/123',
|
||||
'https://app.affine.local'
|
||||
)
|
||||
).toBeUndefined();
|
||||
|
||||
expect(
|
||||
normalizeOpenAppSignInNextParam(
|
||||
'/redirect-proxy?redirect_uri=https://evil.example',
|
||||
'https://app.affine.local'
|
||||
)
|
||||
).toBeUndefined();
|
||||
});
|
||||
@@ -1,8 +1,84 @@
|
||||
import { channelToScheme } from '@affine/core/utils';
|
||||
import { channelToScheme } from '@affine/core/utils/channel';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
|
||||
const logger = new DebugLogger('open-in-app');
|
||||
|
||||
export type AuthenticationMethod = 'magic-link' | 'oauth' | 'open-app-signin';
|
||||
|
||||
export function buildAuthenticationDeepLink(options: {
|
||||
scheme: string;
|
||||
method: AuthenticationMethod;
|
||||
payload: unknown;
|
||||
server?: string;
|
||||
}) {
|
||||
const params = new URLSearchParams();
|
||||
params.set('method', options.method);
|
||||
params.set('payload', JSON.stringify(options.payload));
|
||||
if (options.server) {
|
||||
params.set('server', options.server);
|
||||
}
|
||||
|
||||
return `${options.scheme}://authentication?${params.toString()}`;
|
||||
}
|
||||
|
||||
export function buildOpenAppUrlRoute(urlToOpen: string) {
|
||||
const params = new URLSearchParams();
|
||||
params.set('url', urlToOpen);
|
||||
return `/open-app/url?${params.toString()}`;
|
||||
}
|
||||
|
||||
function isAllowedOpenAppSignInNext(next: string) {
|
||||
if (next === '/') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (next.startsWith('/workspace')) {
|
||||
const boundary = next.charAt('/workspace'.length);
|
||||
return (
|
||||
boundary === '' ||
|
||||
boundary === '/' ||
|
||||
boundary === '?' ||
|
||||
boundary === '#'
|
||||
);
|
||||
}
|
||||
|
||||
return next.startsWith('/share/');
|
||||
}
|
||||
|
||||
export function normalizeOpenAppSignInNextParam(
|
||||
next: string | null,
|
||||
currentOrigin: string
|
||||
) {
|
||||
if (!next) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disallow protocol-relative urls like `//evil.example`.
|
||||
if (next.startsWith('//')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(next, currentOrigin);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only allow navigation within current origin.
|
||||
if (parsed.origin !== currentOrigin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalized = `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
||||
|
||||
if (!isAllowedOpenAppSignInNext(normalized)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
// return an AFFiNE app's url to be opened in desktop app
|
||||
export const getOpenUrlInDesktopAppLink = (
|
||||
url: string,
|
||||
|
||||
Reference in New Issue
Block a user