mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-23 21:38:44 +08:00
feat(server): passkey pre-refactor (#15060)
#### PR Dependency Tree * **PR #15060** 👈 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** * OpenApp native sign-in and native session exchange (JWT) for mobile & desktop. * Centralized short-lived auth challenge store for one-time tokens. * Encrypted per-endpoint token storage and native token handlers (Android, iOS, Electron). * **Improvements** * Richer auth-method reporting (password, magic link, OAuth, passkey) and improved sign-in flows. * Hardened magic-link, OAuth, and session issuance; JWT-backed sessions and websocket JWT support. * UX tweaks: form-based password submit, OTP autocomplete, adjusted captcha flow. * **Bug Fixes** * Expanded tests and auth-state resets to avoid cross-test leakage. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,4 +1,19 @@
|
||||
import { openDB } from 'idb';
|
||||
import { Auth } from './plugins/auth';
|
||||
|
||||
function authEndpointForUrl(url: string | URL) {
|
||||
try {
|
||||
const parsed = new URL(url, globalThis.location.origin);
|
||||
return parsed.protocol === 'http:' || parsed.protocol === 'https:'
|
||||
? parsed.origin
|
||||
: null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function canonicalEndpoint(endpoint: string) {
|
||||
return authEndpointForUrl(endpoint) ?? endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* the below code includes the custom fetch and xmlhttprequest implementation for ios webview.
|
||||
@@ -8,9 +23,11 @@ const rawFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const request = new Request(input, init);
|
||||
|
||||
const origin = new URL(request.url, globalThis.location.origin).origin;
|
||||
const origin = authEndpointForUrl(request.url);
|
||||
|
||||
const token = await readEndpointToken(origin);
|
||||
const token = origin
|
||||
? await readEndpointToken(origin).catch(() => null)
|
||||
: null;
|
||||
if (token) {
|
||||
request.headers.set('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
@@ -19,11 +36,30 @@ globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
};
|
||||
|
||||
const rawXMLHttpRequest = globalThis.XMLHttpRequest;
|
||||
const xhrRequestUrls = new WeakMap<XMLHttpRequest, string>();
|
||||
globalThis.XMLHttpRequest = class extends rawXMLHttpRequest {
|
||||
override send(body?: Document | XMLHttpRequestBodyInit | null): void {
|
||||
const origin = new URL(this.responseURL, globalThis.location.origin).origin;
|
||||
override open(
|
||||
method: string,
|
||||
url: string | URL,
|
||||
async: boolean = true,
|
||||
username?: string | null,
|
||||
password?: string | null
|
||||
): void {
|
||||
xhrRequestUrls.set(this, url.toString());
|
||||
return super.open(
|
||||
method,
|
||||
url,
|
||||
async,
|
||||
username ?? undefined,
|
||||
password ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
readEndpointToken(origin).then(
|
||||
override send(body?: Document | XMLHttpRequestBodyInit | null): void {
|
||||
const requestUrl = xhrRequestUrls.get(this);
|
||||
const origin = authEndpointForUrl(requestUrl ?? globalThis.location.href);
|
||||
|
||||
(origin ? readEndpointToken(origin) : Promise.resolve(null)).then(
|
||||
token => {
|
||||
if (token) {
|
||||
this.setRequestHeader('Authorization', `Bearer ${token}`);
|
||||
@@ -31,7 +67,7 @@ globalThis.XMLHttpRequest = class extends rawXMLHttpRequest {
|
||||
return super.send(body);
|
||||
},
|
||||
() => {
|
||||
throw new Error('Failed to read token');
|
||||
return super.send(body);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -40,26 +76,19 @@ globalThis.XMLHttpRequest = class extends rawXMLHttpRequest {
|
||||
export async function readEndpointToken(
|
||||
endpoint: string
|
||||
): Promise<string | null> {
|
||||
const idb = await openDB('affine-token', 1, {
|
||||
upgrade(db) {
|
||||
if (!db.objectStoreNames.contains('tokens')) {
|
||||
db.createObjectStore('tokens', { keyPath: 'endpoint' });
|
||||
}
|
||||
},
|
||||
const { token } = await Auth.readEndpointToken({
|
||||
endpoint: canonicalEndpoint(endpoint),
|
||||
});
|
||||
|
||||
const token = await idb.get('tokens', endpoint);
|
||||
return token ? token.token : null;
|
||||
return token ?? null;
|
||||
}
|
||||
|
||||
export async function writeEndpointToken(endpoint: string, token: string) {
|
||||
const db = await openDB('affine-token', 1, {
|
||||
upgrade(db) {
|
||||
if (!db.objectStoreNames.contains('tokens')) {
|
||||
db.createObjectStore('tokens', { keyPath: 'endpoint' });
|
||||
}
|
||||
},
|
||||
await Auth.writeEndpointToken({
|
||||
endpoint: canonicalEndpoint(endpoint),
|
||||
token,
|
||||
});
|
||||
|
||||
await db.put('tokens', { endpoint, token });
|
||||
}
|
||||
|
||||
export async function deleteEndpointToken(endpoint: string) {
|
||||
await Auth.deleteEndpointToken({ endpoint: canonicalEndpoint(endpoint) });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user