chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
export type FetchCredentialsMode = "omit" | "same-origin" | "include";
|
||||
|
||||
export type RestProviderProfile = Readonly<{
|
||||
providerId: string;
|
||||
baseUrl: string;
|
||||
allowedCredentialsModes: readonly FetchCredentialsMode[];
|
||||
redirect: "error";
|
||||
referrerPolicy: "no-referrer";
|
||||
}>;
|
||||
|
||||
export type RestAuthProfile = Readonly<{
|
||||
authProfileId: string;
|
||||
transport: "ANONYMOUS" | "BEARER_HEADER" | "SAME_ORIGIN_COOKIE";
|
||||
credentials: FetchCredentialsMode;
|
||||
allowedCredentialHeaders: readonly ("authorization" | "x-csrf-token")[];
|
||||
}>;
|
||||
|
||||
export type RestCsrfProfile = Readonly<{
|
||||
csrfProfileId: string;
|
||||
mode: "NONE" | "HEADER";
|
||||
headerName: "x-csrf-token" | null;
|
||||
}>;
|
||||
|
||||
export const REST_AUTH_PROFILES = Object.freeze({
|
||||
REFERENCE_EXTERNAL_BEARER: Object.freeze({
|
||||
authProfileId: "REFERENCE_EXTERNAL_BEARER",
|
||||
transport: "BEARER_HEADER",
|
||||
credentials: "omit",
|
||||
allowedCredentialHeaders: Object.freeze(["authorization"] as const),
|
||||
}),
|
||||
ANONYMOUS: Object.freeze({
|
||||
authProfileId: "ANONYMOUS",
|
||||
transport: "ANONYMOUS",
|
||||
credentials: "omit",
|
||||
allowedCredentialHeaders: Object.freeze([]),
|
||||
}),
|
||||
} satisfies Readonly<Record<string, RestAuthProfile>>);
|
||||
|
||||
export const REST_CSRF_PROFILES = Object.freeze({
|
||||
NO_CSRF_BEARER: Object.freeze({
|
||||
csrfProfileId: "NO_CSRF_BEARER",
|
||||
mode: "NONE",
|
||||
headerName: null,
|
||||
}),
|
||||
} satisfies Readonly<Record<string, RestCsrfProfile>>);
|
||||
|
||||
export function createRestProviderProfile(
|
||||
providerId: string,
|
||||
baseUrl: string,
|
||||
allowedCredentialsModes: readonly FetchCredentialsMode[] = ["omit"],
|
||||
): RestProviderProfile {
|
||||
const parsed = new URL(baseUrl);
|
||||
const localHttp =
|
||||
parsed.protocol === "http:" &&
|
||||
["localhost", "127.0.0.1", "[::1]"].includes(parsed.hostname);
|
||||
if (
|
||||
!providerId ||
|
||||
(parsed.protocol !== "https:" && !localHttp) ||
|
||||
parsed.username ||
|
||||
parsed.password ||
|
||||
parsed.search ||
|
||||
parsed.hash ||
|
||||
allowedCredentialsModes.length === 0 ||
|
||||
new Set(allowedCredentialsModes).size !== allowedCredentialsModes.length
|
||||
) {
|
||||
throw new TypeError("Invalid REST provider profile.");
|
||||
}
|
||||
return Object.freeze({
|
||||
providerId,
|
||||
baseUrl: parsed.href,
|
||||
allowedCredentialsModes: Object.freeze([...allowedCredentialsModes]),
|
||||
redirect: "error",
|
||||
referrerPolicy: "no-referrer",
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveRestSecurityProfiles(
|
||||
operation: Readonly<{
|
||||
method: string;
|
||||
auth: "none" | "external-session";
|
||||
authProfileId?: string;
|
||||
csrfProfileId?: string;
|
||||
}>,
|
||||
provider: RestProviderProfile,
|
||||
authProfiles: Readonly<Record<string, RestAuthProfile>> = REST_AUTH_PROFILES,
|
||||
csrfProfiles: Readonly<Record<string, RestCsrfProfile>> = REST_CSRF_PROFILES,
|
||||
): Readonly<{ auth: RestAuthProfile; csrf: RestCsrfProfile }> {
|
||||
const auth = authProfiles[operation.authProfileId ?? ""];
|
||||
const csrf = csrfProfiles[operation.csrfProfileId ?? ""];
|
||||
const unsafe = !["GET", "HEAD", "OPTIONS"].includes(operation.method);
|
||||
if (
|
||||
!auth ||
|
||||
!csrf ||
|
||||
!provider.allowedCredentialsModes.includes(auth.credentials) ||
|
||||
(operation.auth === "none" && auth.transport !== "ANONYMOUS") ||
|
||||
(operation.auth === "external-session" &&
|
||||
auth.transport === "ANONYMOUS") ||
|
||||
(auth.transport === "BEARER_HEADER" && csrf.mode !== "NONE") ||
|
||||
(unsafe &&
|
||||
auth.transport === "SAME_ORIGIN_COOKIE" &&
|
||||
csrf.mode !== "HEADER")
|
||||
) {
|
||||
throw new TypeError("REST security profiles are incoherent.");
|
||||
}
|
||||
return Object.freeze({ auth, csrf });
|
||||
}
|
||||
|
||||
export function validateRestProfileBindings(
|
||||
operations: Readonly<
|
||||
Record<
|
||||
string,
|
||||
Readonly<{
|
||||
contractVersion?: number;
|
||||
operationId: string;
|
||||
method: string;
|
||||
auth: "none" | "external-session";
|
||||
providerId?: string;
|
||||
authProfileId?: string;
|
||||
csrfProfileId?: string;
|
||||
}>
|
||||
>
|
||||
>,
|
||||
providerCredentialModes: Readonly<
|
||||
Record<string, readonly FetchCredentialsMode[]>
|
||||
>,
|
||||
authProfiles: Readonly<Record<string, RestAuthProfile>> = REST_AUTH_PROFILES,
|
||||
csrfProfiles: Readonly<Record<string, RestCsrfProfile>> = REST_CSRF_PROFILES,
|
||||
): true {
|
||||
for (const operation of Object.values(operations)) {
|
||||
if (operation.contractVersion !== 2) continue;
|
||||
const allowed = providerCredentialModes[operation.providerId ?? ""];
|
||||
if (!allowed) {
|
||||
throw new TypeError(
|
||||
`Unregistered REST provider binding: ${operation.operationId}`,
|
||||
);
|
||||
}
|
||||
resolveRestSecurityProfiles(
|
||||
operation,
|
||||
Object.freeze({
|
||||
providerId: operation.providerId ?? "",
|
||||
baseUrl: "https://contract.invalid/",
|
||||
allowedCredentialsModes: allowed,
|
||||
redirect: "error",
|
||||
referrerPolicy: "no-referrer",
|
||||
}),
|
||||
authProfiles,
|
||||
csrfProfiles,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user