mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-09 13:15:51 +08:00
feat(core): improve login flow (#15219)
#### PR Dependency Tree * **PR #15219** 👈 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 secure, automatic auth session token refresh and request replay for expired-token responses across Android, iOS, and Electron. * Updated sign-in flows to manage sessions without returning tokens to the app layer. * Added “Devices” management UI with sign out per device and sign out all. * Enabled support for both Hashcash and Turnstile captcha providers. * **Bug Fixes** * Improved refresh de-duplication, inflight cancellation/clear behavior, and recovery from corrupted/invalid sessions. * **Tests** * Expanded auth-session, refresh/revoke, and replay coverage (Electron unit tests, Android instrumentation tests, iOS auth date parser tests). * **Chores** * Removed CAPTCHA site key from build-time configuration and adjusted CI test execution. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -19,21 +19,50 @@ import { type MessageCommunicapable, OpConsumer } from '@toeverything/infra/op';
|
||||
import { AsyncCall } from 'async-call-rpc';
|
||||
|
||||
let authTokenPort: MessagePort | undefined;
|
||||
const pendingTokenRequests = new Map<string, (token: string | null) => void>();
|
||||
const pendingTokenRequests = new Map<
|
||||
string,
|
||||
{
|
||||
resolve: (token: string | null) => void;
|
||||
reject: (error: Error) => void;
|
||||
}
|
||||
>();
|
||||
|
||||
configureSocketAuthMethod((endpoint, cb) => {
|
||||
readEndpointToken(endpoint)
|
||||
getValidAccessToken(endpoint)
|
||||
.then(token => cb(token ? { token, tokenType: 'jwt' } : {}))
|
||||
.catch(() => cb({}));
|
||||
.catch(() => cb({ error: 'AUTH_SESSION_TEMPORARILY_UNAVAILABLE' }));
|
||||
});
|
||||
|
||||
globalThis.addEventListener('message', e => {
|
||||
if (e.data.type === 'native-auth-token-channel') {
|
||||
if (e.data.type === 'auth-access-token-channel') {
|
||||
authTokenPort = e.ports[0] as MessagePort;
|
||||
authTokenPort.addEventListener('message', e => {
|
||||
const { id, token } = e.data as { id?: string; token?: string | null };
|
||||
const { id, token, error } = e.data as {
|
||||
id?: string;
|
||||
token?: string | null;
|
||||
error?: string;
|
||||
};
|
||||
if (!id) return;
|
||||
pendingTokenRequests.get(id)?.(token ?? null);
|
||||
const pending = pendingTokenRequests.get(id);
|
||||
if (error) {
|
||||
if (
|
||||
[
|
||||
'ACCESS_TOKEN_INVALID',
|
||||
'AUTH_SESSION_EXPIRED',
|
||||
'AUTH_SESSION_REVOKED',
|
||||
'REFRESH_TOKEN_INVALID',
|
||||
'REFRESH_TOKEN_REUSED',
|
||||
'UNSUPPORTED_CLIENT_VERSION',
|
||||
'AUTH_SESSION_EMPTY',
|
||||
].includes(error)
|
||||
) {
|
||||
pending?.resolve(null);
|
||||
} else {
|
||||
pending?.reject(new Error(error));
|
||||
}
|
||||
} else {
|
||||
pending?.resolve(token ?? null);
|
||||
}
|
||||
pendingTokenRequests.delete(id);
|
||||
});
|
||||
authTokenPort.start();
|
||||
@@ -66,20 +95,26 @@ globalThis.addEventListener('message', e => {
|
||||
}
|
||||
});
|
||||
|
||||
function readEndpointToken(endpoint: string) {
|
||||
function getValidAccessToken(endpoint: string) {
|
||||
if (!authTokenPort) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const id = `${Date.now()}:${Math.random()}`;
|
||||
return new Promise<string | null>(resolve => {
|
||||
return new Promise<string | null>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
pendingTokenRequests.delete(id);
|
||||
resolve(null);
|
||||
reject(new Error('AUTH_SESSION_TEMPORARILY_UNAVAILABLE'));
|
||||
}, 5000);
|
||||
pendingTokenRequests.set(id, token => {
|
||||
clearTimeout(timeout);
|
||||
resolve(token);
|
||||
pendingTokenRequests.set(id, {
|
||||
resolve: token => {
|
||||
clearTimeout(timeout);
|
||||
resolve(token);
|
||||
},
|
||||
reject: error => {
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
},
|
||||
});
|
||||
authTokenPort?.postMessage({ id, endpoint });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user