mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-02 02:00:49 +08:00
fix(core): desktop e2e (#15062)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Sign-in flows now reliably propagate richer authentication results (user data and session type), improving persistence and reducing intermittent sign-in issues. * Native token handling gains a fallback for environments without encrypted storage, improving session reliability. * **New Features** * User-visible warning when sign-in is session-only because encrypted storage is unavailable. * **Chores** * Tooling ignore patterns updated to exclude .codex. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { notify } from '@affine/component';
|
||||
import { configureElectronStateStorageImpls } from '@affine/core/desktop/storage';
|
||||
import { configureCommonModules } from '@affine/core/modules';
|
||||
import { configureAppTabsHeaderModule } from '@affine/core/modules/app-tabs-header';
|
||||
@@ -25,6 +26,16 @@ import { configureDesktopWorkbenchModule } from '@affine/core/modules/workbench'
|
||||
import { configureBrowserWorkspaceFlavours } from '@affine/core/modules/workspace-engine';
|
||||
import { Framework } from '@toeverything/infra';
|
||||
|
||||
function notifySessionOnlySignIn(sessionOnly?: boolean) {
|
||||
if (!sessionOnly) return;
|
||||
|
||||
notify.warning({
|
||||
title: 'Sign-in is only valid for this session',
|
||||
message:
|
||||
'Encrypted storage is unavailable, so you will need to sign in again after restarting AFFiNE.',
|
||||
});
|
||||
}
|
||||
|
||||
export function setupModules() {
|
||||
const framework = new Framework();
|
||||
configureCommonModules(framework);
|
||||
@@ -75,26 +86,38 @@ export function setupModules() {
|
||||
|
||||
return {
|
||||
async signInMagicLink(email, token, clientNonce) {
|
||||
await apis.handler.auth.signInMagicLink(
|
||||
const result = await apis.handler.auth.signInMagicLink(
|
||||
endpoint,
|
||||
email,
|
||||
token,
|
||||
clientNonce
|
||||
);
|
||||
notifySessionOnlySignIn(result.sessionOnly);
|
||||
},
|
||||
async signInOauth(code, state, _provider, clientNonce) {
|
||||
return await apis.handler.auth.signInOauth(
|
||||
const result = await apis.handler.auth.signInOauth(
|
||||
endpoint,
|
||||
code,
|
||||
state,
|
||||
clientNonce
|
||||
);
|
||||
notifySessionOnlySignIn(result.sessionOnly);
|
||||
return result;
|
||||
},
|
||||
async signInPassword(credential) {
|
||||
await apis.handler.auth.signInPassword(endpoint, credential);
|
||||
const result = await apis.handler.auth.signInPassword(
|
||||
endpoint,
|
||||
credential
|
||||
);
|
||||
notifySessionOnlySignIn(result.sessionOnly);
|
||||
return result;
|
||||
},
|
||||
async signInOpenAppSignInCode(code) {
|
||||
await apis.handler.auth.signInOpenAppSignInCode(endpoint, code);
|
||||
const result = await apis.handler.auth.signInOpenAppSignInCode(
|
||||
endpoint,
|
||||
code
|
||||
);
|
||||
notifySessionOnlySignIn(result.sessionOnly);
|
||||
},
|
||||
async signOut() {
|
||||
await apis.handler.auth.signOut(endpoint);
|
||||
|
||||
@@ -19,6 +19,16 @@ export interface SignInResponse {
|
||||
redirectUri?: string;
|
||||
}
|
||||
|
||||
export interface PasswordSignInResponse extends SignInResponse {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
hasPassword: boolean | null;
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
sessionOnly?: boolean;
|
||||
}
|
||||
|
||||
interface ExchangeResponse {
|
||||
token?: string;
|
||||
}
|
||||
@@ -86,8 +96,9 @@ async function exchangeSession(endpoint: string, response: SignInResponse) {
|
||||
throw new Error('Missing native auth token.');
|
||||
}
|
||||
|
||||
setNativeAuthToken(endpoint, body.token);
|
||||
const persistent = setNativeAuthToken(endpoint, body.token);
|
||||
await clearAuthCookies(endpoint);
|
||||
return { persistent };
|
||||
}
|
||||
|
||||
export const authHandlers = {
|
||||
@@ -104,7 +115,8 @@ export const authHandlers = {
|
||||
client_nonce: clientNonce,
|
||||
});
|
||||
const body = await readJson<SignInResponse>(response);
|
||||
await exchangeSession(endpoint, body);
|
||||
const { persistent } = await exchangeSession(endpoint, body);
|
||||
return { sessionOnly: !persistent };
|
||||
},
|
||||
|
||||
signInOauth: async (
|
||||
@@ -120,8 +132,8 @@ export const authHandlers = {
|
||||
client_nonce: clientNonce,
|
||||
});
|
||||
const body = await readJson<SignInResponse>(response);
|
||||
await exchangeSession(endpoint, body);
|
||||
return { redirectUri: body.redirectUri };
|
||||
const { persistent } = await exchangeSession(endpoint, body);
|
||||
return { redirectUri: body.redirectUri, sessionOnly: !persistent };
|
||||
},
|
||||
|
||||
signInPassword: async (
|
||||
@@ -152,16 +164,20 @@ export const authHandlers = {
|
||||
password: credential.password,
|
||||
}),
|
||||
});
|
||||
const body = await readJson<SignInResponse>(response);
|
||||
await exchangeSession(endpoint, body);
|
||||
return body;
|
||||
const body = await readJson<PasswordSignInResponse>(response);
|
||||
const { persistent } = await exchangeSession(endpoint, body);
|
||||
return { ...body, sessionOnly: !persistent };
|
||||
},
|
||||
|
||||
signInOpenAppSignInCode: async (_e, endpoint: string, code: string) => {
|
||||
const response = await fetchAuth(endpoint, '/api/auth/open-app/sign-in', {
|
||||
code,
|
||||
});
|
||||
await exchangeSession(endpoint, await readJson(response));
|
||||
const { persistent } = await exchangeSession(
|
||||
endpoint,
|
||||
await readJson(response)
|
||||
);
|
||||
return { sessionOnly: !persistent };
|
||||
},
|
||||
|
||||
signOut: async (_e, endpoint: string) => {
|
||||
|
||||
@@ -11,6 +11,9 @@ type TokenRecord = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
// safeStorage may not be available in some environments (e.g. Linux without a keyring), so we fall back to an in-memory store in that case
|
||||
const memoryTokenStore: Record<string, string> = {};
|
||||
|
||||
function normalizeEndpoint(endpoint: string) {
|
||||
return new URL(endpoint).origin;
|
||||
}
|
||||
@@ -51,19 +54,33 @@ function decryptToken(value: string): TokenRecord | null {
|
||||
}
|
||||
|
||||
export function setNativeAuthToken(endpoint: string, token: string) {
|
||||
const normalizedEndpoint = normalizeEndpoint(endpoint);
|
||||
if (!safeStorage.isEncryptionAvailable()) {
|
||||
memoryTokenStore[normalizedEndpoint] = token;
|
||||
return false;
|
||||
}
|
||||
|
||||
const store = readStore();
|
||||
store[normalizeEndpoint(endpoint)] = encryptToken({ token });
|
||||
store[normalizedEndpoint] = encryptToken({ token });
|
||||
writeStore(store);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function deleteNativeAuthToken(endpoint: string) {
|
||||
const normalizedEndpoint = normalizeEndpoint(endpoint);
|
||||
delete memoryTokenStore[normalizedEndpoint];
|
||||
|
||||
const store = readStore();
|
||||
delete store[normalizeEndpoint(endpoint)];
|
||||
delete store[normalizedEndpoint];
|
||||
writeStore(store);
|
||||
}
|
||||
|
||||
export function getNativeAuthToken(endpoint: string) {
|
||||
const encrypted = readStore()[normalizeEndpoint(endpoint)];
|
||||
const normalizedEndpoint = normalizeEndpoint(endpoint);
|
||||
const memoryToken = memoryTokenStore[normalizedEndpoint];
|
||||
if (memoryToken) return memoryToken;
|
||||
|
||||
const encrypted = readStore()[normalizedEndpoint];
|
||||
if (!encrypted) return null;
|
||||
return decryptToken(encrypted)?.token ?? null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user