mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-25 16:42:16 +03:00
board #11340
This commit is contained in:
1
changelog.d/features/11340-web-session-contract.md
Normal file
1
changelog.d/features/11340-web-session-contract.md
Normal file
@@ -0,0 +1 @@
|
||||
- **feat(providers):** publish a management-authenticated versioned web-session credential contract from OmniRoute's canonical browser credential metadata ([#11340](https://github.com/diegosouzapw/OmniRoute/pull/11340)) — thanks @Zartharas
|
||||
10
src/app/api/providers/web-session-contract/route.ts
Normal file
10
src/app/api/providers/web-session-contract/route.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
import { buildWebSessionContract } from "@/lib/providers/webSessionContract";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
return NextResponse.json(buildWebSessionContract());
|
||||
}
|
||||
58
src/lib/providers/webSessionContract.ts
Normal file
58
src/lib/providers/webSessionContract.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
listExtractionConfigs,
|
||||
type TokenSource,
|
||||
} from "@omniroute/open-sse/services/tokenExtractionConfig.ts";
|
||||
import { getWebSessionCredentialRequirement } from "@/shared/providers/webSessionCredentials";
|
||||
|
||||
export const WEB_SESSION_CONTRACT_VERSION = 1;
|
||||
|
||||
export interface WebSessionContractProvider {
|
||||
providerId: string;
|
||||
displayName: string;
|
||||
loginUrl: string;
|
||||
homeUrl: string;
|
||||
tokenSources: TokenSource[];
|
||||
credential: {
|
||||
kind: "cookie" | "token";
|
||||
storageKeys: string[];
|
||||
acceptsFullCookieHeader: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WebSessionContract {
|
||||
version: typeof WEB_SESSION_CONTRACT_VERSION;
|
||||
providers: WebSessionContractProvider[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish only the canonical, non-secret metadata needed by external
|
||||
* credential brokers to capture credentials in the same shape OmniRoute
|
||||
* accepts. Provider instructions, polling state, and credential values are
|
||||
* intentionally excluded.
|
||||
*/
|
||||
export function buildWebSessionContract(): WebSessionContract {
|
||||
const providers = listExtractionConfigs().flatMap<WebSessionContractProvider>((config) => {
|
||||
const requirement = getWebSessionCredentialRequirement(config.providerId);
|
||||
if (!requirement || requirement.kind === "none") return [];
|
||||
|
||||
return [
|
||||
{
|
||||
providerId: config.providerId,
|
||||
displayName: config.displayName,
|
||||
loginUrl: config.loginUrl,
|
||||
homeUrl: config.homeUrl,
|
||||
tokenSources: config.tokenSources.map((source) => ({ ...source })),
|
||||
credential: {
|
||||
kind: requirement.kind,
|
||||
storageKeys: [...requirement.storageKeys],
|
||||
acceptsFullCookieHeader: requirement.acceptsFullCookieHeader,
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
return {
|
||||
version: WEB_SESSION_CONTRACT_VERSION,
|
||||
providers,
|
||||
};
|
||||
}
|
||||
99
tests/unit/web-session-contract.test.ts
Normal file
99
tests/unit/web-session-contract.test.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
import { listExtractionConfigs } from "../../open-sse/services/tokenExtractionConfig.ts";
|
||||
import {
|
||||
buildWebSessionContract,
|
||||
WEB_SESSION_CONTRACT_VERSION,
|
||||
} from "../../src/lib/providers/webSessionContract.ts";
|
||||
import { getWebSessionCredentialRequirement } from "../../src/shared/providers/webSessionCredentials.ts";
|
||||
|
||||
test("web-session contract mirrors canonical extraction and credential metadata", () => {
|
||||
const contract = buildWebSessionContract();
|
||||
assert.equal(contract.version, WEB_SESSION_CONTRACT_VERSION);
|
||||
|
||||
const expected = listExtractionConfigs().flatMap((config) => {
|
||||
const requirement = getWebSessionCredentialRequirement(config.providerId);
|
||||
return requirement && requirement.kind !== "none" ? [{ config, requirement }] : [];
|
||||
});
|
||||
|
||||
assert.equal(contract.providers.length, expected.length);
|
||||
assert.equal(
|
||||
new Set(contract.providers.map((provider) => provider.providerId)).size,
|
||||
expected.length
|
||||
);
|
||||
|
||||
for (const { config, requirement } of expected) {
|
||||
const published = contract.providers.find(
|
||||
(provider) => provider.providerId === config.providerId
|
||||
);
|
||||
assert.ok(published, `${config.providerId} must be published`);
|
||||
assert.equal(published.displayName, config.displayName);
|
||||
assert.equal(published.loginUrl, config.loginUrl);
|
||||
assert.equal(published.homeUrl, config.homeUrl);
|
||||
assert.deepEqual(published.tokenSources, config.tokenSources);
|
||||
assert.equal(published.credential.kind, requirement.kind);
|
||||
assert.deepEqual(published.credential.storageKeys, [...requirement.storageKeys]);
|
||||
assert.equal(published.credential.acceptsFullCookieHeader, requirement.acceptsFullCookieHeader);
|
||||
}
|
||||
});
|
||||
|
||||
test("web-session contract preserves representative token and cookie semantics", () => {
|
||||
const providers = new Map(
|
||||
buildWebSessionContract().providers.map((provider) => [provider.providerId, provider])
|
||||
);
|
||||
|
||||
assert.equal(providers.get("deepseek-web")?.credential.kind, "token");
|
||||
assert.equal(providers.get("zai-web")?.credential.kind, "token");
|
||||
assert.equal(providers.get("gemini-web")?.credential.kind, "cookie");
|
||||
assert.equal(providers.get("qwen-web")?.credential.kind, "cookie");
|
||||
|
||||
assert.ok(
|
||||
providers
|
||||
.get("deepseek-web")
|
||||
?.tokenSources.some((source) => source.type === "localStorage" && source.key === "userToken")
|
||||
);
|
||||
assert.ok(
|
||||
providers
|
||||
.get("gemini-web")
|
||||
?.tokenSources.some(
|
||||
(source) =>
|
||||
source.type === "cookie" &&
|
||||
source.name === "__Secure-1PSID" &&
|
||||
source.domain === ".google.com"
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test("web-session contract excludes credential values and operator-only guidance", () => {
|
||||
const serialized = JSON.stringify(buildWebSessionContract());
|
||||
|
||||
for (const forbidden of [
|
||||
"placeholder",
|
||||
"instructions",
|
||||
"pollingConfig",
|
||||
"credentialName",
|
||||
"guideSteps",
|
||||
"guideNote",
|
||||
]) {
|
||||
assert.equal(
|
||||
serialized.includes(`\"${forbidden}\"`),
|
||||
false,
|
||||
`${forbidden} must not be published`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("web-session contract route remains management-authenticated", () => {
|
||||
const source = readFileSync(
|
||||
new URL("../../src/app/api/providers/web-session-contract/route.ts", import.meta.url),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
const authCall = source.indexOf("requireManagementAuth(request)");
|
||||
const responseCall = source.indexOf("NextResponse.json(buildWebSessionContract())");
|
||||
|
||||
assert.ok(authCall >= 0, "route must require management authentication");
|
||||
assert.ok(responseCall > authCall, "authentication must run before contract publication");
|
||||
});
|
||||
Reference in New Issue
Block a user