mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 19:32:20 +03:00
Rebased onto the current release/v3.8.51 tip as part of a combined provider-retirement/provenance merge batch (Designer Web, Felo Web, Runtime, and this GPL-derived Raycast/Hailuo Web removal all landed together). Conflicts resolved: - `config/quality/test-masking-allowlist.json`: additive merge of the Hailuo-Web/Raycast-auth/Raycast-local-extract entries alongside prior sibling retirement entries. - `docs/reference/PROVIDER_REFERENCE.md`: kept the branch's generated content (deferred to a future `npm run gen:provider-reference` regeneration pass). - `src/app/api/providers/[id]/test/webSessionTestDispatch.ts`: comment-only, dropped stale retired-provider examples. - `tests/snapshots/executors/executor-map.json`: recomputed `keyCount` to 137 (matches the actual merged `entries` object). - `tests/unit/provider-test-token-web-session-dispatch.test.ts`: kept both sibling assertions (hailuo-web + t3-chat-web), avoided duplicating the dedicated microsoft-designer-web test already present. Also recomputed the golden `RESERVED_PREFIX_COUNT` (397, down from 400) to reflect the 3 GPL-derived ids/aliases this PR removes from `REGISTRY`, and rebaselined `file-size-baseline.json` for the combined retirement-guard growth accumulated across the sibling PRs in this batch. Focused suite green (86 tests across authz/oauth-autoimport, public-route-exact-match, gpl-derived-provider-removals, migration-166, muse-spark-ws-auth-token, oauth-providers-config, provider-alias-uniqueness, provider-test-token-web-session-dispatch, providers-constants-split, ts7-executor-override-signatures, executor-map-golden, provider-node-reserved-prefix), plus `typecheck:core` and `check-file-size` clean. Thanks for the GPL-license cleanup — appreciated.
98 lines
3.8 KiB
TypeScript
98 lines
3.8 KiB
TypeScript
/**
|
|
* db/webSessionDedup.ts — pure helpers for de-duplicating web-session
|
|
* (cookie/token) provider credentials. Extracted from providers.ts so the
|
|
* cookie-dedup wiring there stays thin (#3368 PR6). No DB access here.
|
|
*/
|
|
|
|
/**
|
|
* Reduce a `provider_specific_data` record to a single comparable credential
|
|
* value. Cookie/token credentials are mirrored across a provider's storage
|
|
* keys (e.g. `cookie`, `sessionToken`, `token`) with the same secret value, so
|
|
* any one of them identifies the session. Returns the trimmed value, or null
|
|
* when no usable string credential is present.
|
|
*/
|
|
const PREFERRED_CREDENTIAL_KEYS = [
|
|
"cookie",
|
|
"token",
|
|
"sessionToken",
|
|
"session-token",
|
|
"sso",
|
|
"access_token",
|
|
"accessToken",
|
|
];
|
|
|
|
/** First trimmed non-empty string value among `keys` of `rec`, else null. */
|
|
function firstNonEmptyString(rec: Record<string, unknown>, keys: readonly string[]): string | null {
|
|
for (const key of keys) {
|
|
const value = rec[key];
|
|
if (typeof value === "string" && value.trim()) return value.trim();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function webSessionCredentialKey(psd: unknown): string | null {
|
|
if (!psd || typeof psd !== "object") return null;
|
|
const rec = psd as Record<string, unknown>;
|
|
// Prefer canonical credential keys, then fall back to the first non-empty
|
|
// string value (sorted for determinism).
|
|
return (
|
|
firstNonEmptyString(rec, PREFERRED_CREDENTIAL_KEYS) ??
|
|
firstNonEmptyString(rec, Object.keys(rec).sort())
|
|
);
|
|
}
|
|
|
|
/** Parse a stored `provider_specific_data` column (JSON string or object). */
|
|
export function parseProviderSpecificData(raw: unknown): Record<string, unknown> | null {
|
|
if (!raw) return null;
|
|
if (typeof raw === "object") return raw as Record<string, unknown>;
|
|
if (typeof raw === "string") {
|
|
try {
|
|
const parsed = JSON.parse(raw);
|
|
return parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Trimmed non-empty string, else null — local to avoid a cross-module import for one coercion. */
|
|
function nonEmptyString(value: unknown): string | null {
|
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
}
|
|
|
|
/**
|
|
* Two-sided disambiguator match: `true` when both sides agree, `false` when
|
|
* both carry a value and it differs, `undefined` when the field can't decide
|
|
* (at most one side carries it) — the caller then defers to other fields.
|
|
*/
|
|
function fieldMatch(incoming: string | null, existing: string | null): boolean | undefined {
|
|
if (incoming && existing) return incoming === existing;
|
|
if (incoming || existing) return false;
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Decide whether `row` (an existing `provider_connections` record) is the
|
|
* same OAuth identity as an incoming connection carrying `incomingUsername`
|
|
* and `incomingProfileArn` (#10815).
|
|
*
|
|
* Two independent disambiguators, either of which can prove "different
|
|
* account": `providerSpecificData.username` (generic username/IdP fallback) and
|
|
* `providerSpecificData.profileArn` (Kiro/AWS profile dedup — Kiro never
|
|
* sets `username`). A field only rules a match IN/OUT when both the
|
|
* incoming and existing record carry it; when neither carries either field
|
|
* the legacy bare-email match still applies unchanged.
|
|
*/
|
|
export function isMatchingOauthIdentity(
|
|
row: { provider_specific_data?: unknown },
|
|
incomingUsername: string | null,
|
|
incomingProfileArn: string | null
|
|
): boolean {
|
|
const existingPsd = parseProviderSpecificData(row.provider_specific_data);
|
|
const usernameMatch = fieldMatch(incomingUsername, nonEmptyString(existingPsd?.username));
|
|
const profileArnMatch = fieldMatch(incomingProfileArn, nonEmptyString(existingPsd?.profileArn));
|
|
if (usernameMatch === false || profileArnMatch === false) return false;
|
|
return true;
|
|
}
|