mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 01:49:51 +08:00
feat(server): improve oidc compatibility (#14686)
fix #13938 fix #14683 fix #14532 #### PR Dependency Tree * **PR #14686** 👈 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** * Flexible OIDC claim mapping for email/name, automatic OIDC discovery retry with exponential backoff, and explicit OAuth flow modes (popup vs redirect) propagated through the auth flow. * **Bug Fixes** * Stricter OIDC email validation, clearer error messages listing attempted claim candidates, and improved callback redirect handling for various flow scenarios. * **Tests** * Added unit tests covering OIDC behaviors, backoff scheduler/promise utilities, and frontend OAuth flow parsing/redirect logic. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import test from 'ava';
|
||||
import Sinon from 'sinon';
|
||||
|
||||
import {
|
||||
exponentialBackoffDelay,
|
||||
ExponentialBackoffScheduler,
|
||||
} from '../promise';
|
||||
|
||||
test('exponentialBackoffDelay should cap exponential growth at maxDelayMs', t => {
|
||||
t.is(exponentialBackoffDelay(0, { baseDelayMs: 100, maxDelayMs: 500 }), 100);
|
||||
t.is(exponentialBackoffDelay(1, { baseDelayMs: 100, maxDelayMs: 500 }), 200);
|
||||
t.is(exponentialBackoffDelay(3, { baseDelayMs: 100, maxDelayMs: 500 }), 500);
|
||||
});
|
||||
|
||||
test('ExponentialBackoffScheduler should track pending callback and increase delay per attempt', async t => {
|
||||
const clock = Sinon.useFakeTimers();
|
||||
t.teardown(() => {
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
const calls: number[] = [];
|
||||
const scheduler = new ExponentialBackoffScheduler({
|
||||
baseDelayMs: 100,
|
||||
maxDelayMs: 500,
|
||||
});
|
||||
|
||||
t.is(
|
||||
scheduler.schedule(() => {
|
||||
calls.push(1);
|
||||
}),
|
||||
100
|
||||
);
|
||||
t.true(scheduler.pending);
|
||||
t.is(
|
||||
scheduler.schedule(() => {
|
||||
calls.push(2);
|
||||
}),
|
||||
null
|
||||
);
|
||||
|
||||
await clock.tickAsync(100);
|
||||
t.deepEqual(calls, [1]);
|
||||
t.false(scheduler.pending);
|
||||
|
||||
t.is(
|
||||
scheduler.schedule(() => {
|
||||
calls.push(3);
|
||||
}),
|
||||
200
|
||||
);
|
||||
await clock.tickAsync(200);
|
||||
t.deepEqual(calls, [1, 3]);
|
||||
});
|
||||
|
||||
test('ExponentialBackoffScheduler reset should clear pending work and restart from the base delay', t => {
|
||||
const scheduler = new ExponentialBackoffScheduler({
|
||||
baseDelayMs: 100,
|
||||
maxDelayMs: 500,
|
||||
});
|
||||
|
||||
t.is(
|
||||
scheduler.schedule(() => {}),
|
||||
100
|
||||
);
|
||||
t.true(scheduler.pending);
|
||||
|
||||
scheduler.reset();
|
||||
t.false(scheduler.pending);
|
||||
t.is(
|
||||
scheduler.schedule(() => {}),
|
||||
100
|
||||
);
|
||||
|
||||
scheduler.clear();
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { setTimeout as delay } from 'node:timers/promises';
|
||||
|
||||
import { defer as rxjsDefer, retry } from 'rxjs';
|
||||
|
||||
@@ -52,5 +52,61 @@ export function defer(dispose: () => Promise<void>) {
|
||||
}
|
||||
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return setTimeout(ms);
|
||||
return delay(ms);
|
||||
}
|
||||
|
||||
export function exponentialBackoffDelay(
|
||||
attempt: number,
|
||||
{
|
||||
baseDelayMs,
|
||||
maxDelayMs,
|
||||
factor = 2,
|
||||
}: { baseDelayMs: number; maxDelayMs: number; factor?: number }
|
||||
): number {
|
||||
return Math.min(
|
||||
baseDelayMs * Math.pow(factor, Math.max(0, attempt)),
|
||||
maxDelayMs
|
||||
);
|
||||
}
|
||||
|
||||
export class ExponentialBackoffScheduler {
|
||||
#attempt = 0;
|
||||
#timer: ReturnType<typeof globalThis.setTimeout> | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly options: {
|
||||
baseDelayMs: number;
|
||||
maxDelayMs: number;
|
||||
factor?: number;
|
||||
}
|
||||
) {}
|
||||
|
||||
get pending() {
|
||||
return this.#timer !== null;
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (this.#timer) {
|
||||
clearTimeout(this.#timer);
|
||||
this.#timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.#attempt = 0;
|
||||
this.clear();
|
||||
}
|
||||
|
||||
schedule(callback: () => void) {
|
||||
if (this.#timer) return null;
|
||||
|
||||
const timeout = exponentialBackoffDelay(this.#attempt, this.options);
|
||||
this.#timer = globalThis.setTimeout(() => {
|
||||
this.#timer = null;
|
||||
callback();
|
||||
}, timeout);
|
||||
this.#attempt += 1;
|
||||
|
||||
return timeout;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user