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:
DarkSky
2026-02-05 21:35:36 +08:00
committed by GitHub
parent 161eb302fd
commit 944fab36ac
29 changed files with 845 additions and 125 deletions
@@ -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,