feat: 기능 추가 과정중

This commit is contained in:
donghyeon-ka
2026-07-30 15:58:20 +09:00
parent d3ef801fe6
commit 6c52cdb916
648 changed files with 126325 additions and 6680 deletions
+151
View File
@@ -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;
}