mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-22 12:36:24 +08:00
feat(core): improve auth handling (#15271)
fix #15270 fix #15260 fix #15257 #### PR Dependency Tree * **PR #15271** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal)
This commit is contained in:
@@ -8,9 +8,10 @@ import {
|
||||
type AuthTokenResponse,
|
||||
classifyAuthError,
|
||||
} from '@affine/auth';
|
||||
import { app, net, safeStorage } from 'electron';
|
||||
import { app, safeStorage } from 'electron';
|
||||
|
||||
import { logger } from '../logger';
|
||||
import { authFetch } from './transport';
|
||||
|
||||
const FILEPATH = path.join(app.getPath('userData'), 'auth-sessions.json');
|
||||
const TEMP_FILEPATH = `${FILEPATH}.tmp`;
|
||||
@@ -105,7 +106,7 @@ function storage(endpoint: string) {
|
||||
}
|
||||
|
||||
async function refresh(endpoint: string, refreshToken: string) {
|
||||
const response = await net.fetch(
|
||||
const response = await authFetch(
|
||||
new URL('/api/auth/session/refresh', endpoint).toString(),
|
||||
{
|
||||
method: 'POST',
|
||||
@@ -192,7 +193,7 @@ export async function revokeAuthSession(endpoint: string) {
|
||||
await getAuthSessionBroker(normalized).revoke(
|
||||
'sign-out',
|
||||
async (refreshToken: string) => {
|
||||
const response = await net.fetch(
|
||||
const response = await authFetch(
|
||||
new URL('/api/auth/session/revoke', normalized).toString(),
|
||||
{
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import os from 'node:os';
|
||||
|
||||
import type { AuthTokenResponse } from '@affine/auth';
|
||||
import { net, session } from 'electron';
|
||||
import { session } from 'electron';
|
||||
|
||||
import { logger } from '../logger';
|
||||
import type { NamespaceHandlers } from '../type';
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
revokeAuthSession,
|
||||
setAuthSession,
|
||||
} from './auth-session';
|
||||
import { authFetch, getAuthTransportSession } from './transport';
|
||||
|
||||
export interface SignInResponse {
|
||||
id?: string;
|
||||
@@ -47,14 +48,21 @@ function authUrl(endpoint: string, path: string) {
|
||||
async function readJson<T>(response: Response): Promise<T> {
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
throw new Error(text || response.statusText);
|
||||
let message = text || response.statusText;
|
||||
try {
|
||||
const error = JSON.parse(text);
|
||||
if (typeof error.message === 'string') {
|
||||
message = error.message;
|
||||
}
|
||||
} catch {}
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return text ? JSON.parse(text) : ({} as T);
|
||||
}
|
||||
|
||||
async function fetchAuth(endpoint: string, path: string, body?: unknown) {
|
||||
return await net.fetch(authUrl(endpoint, path), {
|
||||
return await authFetch(authUrl(endpoint, path), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
@@ -66,18 +74,21 @@ async function fetchAuth(endpoint: string, path: string, body?: unknown) {
|
||||
}
|
||||
|
||||
async function clearAuthCookies(endpoint: string) {
|
||||
const sessions = [session.defaultSession, getAuthTransportSession()];
|
||||
await Promise.all(
|
||||
authCookieNames.map(name =>
|
||||
session.defaultSession.cookies
|
||||
.remove(endpoint, name)
|
||||
.catch(error =>
|
||||
logger.debug(
|
||||
'failed to clear native auth cookie',
|
||||
endpoint,
|
||||
name,
|
||||
error
|
||||
sessions.flatMap(authSession =>
|
||||
authCookieNames.map(name =>
|
||||
authSession.cookies
|
||||
.remove(endpoint, name)
|
||||
.catch(error =>
|
||||
logger.debug(
|
||||
'failed to clear native auth cookie',
|
||||
endpoint,
|
||||
name,
|
||||
error
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -148,7 +159,7 @@ export const authHandlers = {
|
||||
challenge?: string;
|
||||
}
|
||||
) => {
|
||||
const response = await net.fetch(authUrl(endpoint, '/api/auth/sign-in'), {
|
||||
const response = await authFetch(authUrl(endpoint, '/api/auth/sign-in'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { session } from 'electron';
|
||||
|
||||
const AUTH_TRANSPORT_PARTITION = 'affine-auth-transport';
|
||||
|
||||
export function getAuthTransportSession() {
|
||||
return session.fromPartition(AUTH_TRANSPORT_PARTITION, { cache: false });
|
||||
}
|
||||
|
||||
export function authFetch(input: string | Request, init?: RequestInit) {
|
||||
return getAuthTransportSession().fetch(input, init);
|
||||
}
|
||||
@@ -18,6 +18,36 @@ if (isDev) {
|
||||
protocol = 'affine-dev';
|
||||
}
|
||||
|
||||
const authMethods = new Set(['magic-link', 'oauth', 'open-app-signin']);
|
||||
|
||||
function summarizeDeepLink(rawUrl: string) {
|
||||
try {
|
||||
const url = new URL(rawUrl);
|
||||
const method = url.searchParams.get('method');
|
||||
const server = url.searchParams.get('server');
|
||||
let serverOrigin: string | undefined;
|
||||
try {
|
||||
serverOrigin = server ? new URL(server).origin : undefined;
|
||||
} catch {
|
||||
serverOrigin = undefined;
|
||||
}
|
||||
return {
|
||||
protocol: url.protocol,
|
||||
action: url.hostname,
|
||||
method: method && authMethods.has(method) ? method : undefined,
|
||||
serverOrigin,
|
||||
};
|
||||
} catch {
|
||||
return { valid: false };
|
||||
}
|
||||
}
|
||||
|
||||
function logDeepLinkFailure(rawUrl: string, error: unknown) {
|
||||
logger.error('failed to handle affine url', summarizeDeepLink(rawUrl), {
|
||||
error: error instanceof Error ? error.name : typeof error,
|
||||
});
|
||||
}
|
||||
|
||||
export function setupDeepLink(app: App) {
|
||||
if (process.defaultApp) {
|
||||
if (process.argv.length >= 2) {
|
||||
@@ -30,14 +60,14 @@ export function setupDeepLink(app: App) {
|
||||
}
|
||||
|
||||
app.on('open-url', (event, url) => {
|
||||
logger.log('open-url', url);
|
||||
logger.log('open-url', summarizeDeepLink(url));
|
||||
if (url.startsWith(`${protocol}://`)) {
|
||||
event.preventDefault();
|
||||
app
|
||||
.whenReady()
|
||||
.then(() => handleAffineUrl(url))
|
||||
.catch(e => {
|
||||
logger.error('failed to handle affine url', e);
|
||||
logDeepLinkFailure(url, e);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -55,7 +85,7 @@ export function setupDeepLink(app: App) {
|
||||
if (url?.startsWith(`${protocol}://`)) {
|
||||
event.preventDefault();
|
||||
handleAffineUrl(url).catch(e => {
|
||||
logger.error('failed to handle affine url', e);
|
||||
logDeepLinkFailure(url, e);
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -66,10 +96,15 @@ export function setupDeepLink(app: App) {
|
||||
// app may be brought up without having a running instance
|
||||
// need to read the url from the command line
|
||||
const url = process.argv.at(-1);
|
||||
logger.log('url from argv', process.argv, url);
|
||||
logger.log(
|
||||
'url from argv',
|
||||
url?.startsWith(`${protocol}://`)
|
||||
? summarizeDeepLink(url)
|
||||
: { deepLink: false, argumentCount: process.argv.length }
|
||||
);
|
||||
if (url?.startsWith(`${protocol}://`)) {
|
||||
handleAffineUrl(url).catch(e => {
|
||||
logger.error('failed to handle affine url', e);
|
||||
logDeepLinkFailure(url, e);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -78,7 +113,7 @@ export function setupDeepLink(app: App) {
|
||||
async function handleAffineUrl(url: string) {
|
||||
await showMainWindow();
|
||||
|
||||
logger.info('open affine url', url);
|
||||
logger.info('open affine url', summarizeDeepLink(url));
|
||||
const urlObj = new URL(url);
|
||||
|
||||
if (urlObj.hostname === 'authentication') {
|
||||
@@ -93,7 +128,7 @@ async function handleAffineUrl(url: string) {
|
||||
method !== 'open-app-signin') ||
|
||||
!payload
|
||||
) {
|
||||
logger.error('Invalid authentication url', url);
|
||||
logger.error('Invalid authentication url', summarizeDeepLink(url));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ export const registerHandlers = () => {
|
||||
return await handleIpcMessage(e, ...args);
|
||||
} catch (error) {
|
||||
logger.error(`error in ipc handler when calling ${args[0]}`, error);
|
||||
return null;
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user