finalized project

This commit is contained in:
didier amyot committed 2023-06-13 14:05:09 -04:00
1 parent b8b3e348e8
commit 4b87e9d20c
15 files changed
+1027 -4

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
import { Cors } from "./cors.ts";
interface Req {
method: string;
headers: {
get(headerKey: string): string | null | undefined;
};
}
interface Res {
status?: number | string;
headers: {
get(headerKey: string): string | null | undefined;
set(headerKey: string, headerValue: string): any;
};
}
/**
* abcCors middleware wrapper
* @param o CorsOptions | CorsOptionsDelegate
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
*/
export const abcCors = <
RequestT extends Req = any,
ResponseT extends Res = any,
MiddlewareT extends (
next: (...args: any) => any,
) => (context: { request: RequestT; response: ResponseT }) => any = any,
>(
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
) => {
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
CorsOptionsDelegate<RequestT>
>(o);
return ((abcNext) =>
async (context) => {
const next = () => abcNext(context);
try {
const { request, response } = context;
const options = await corsOptionsDelegate(request);
const corsOptions = Cors.produceCorsOptions(options || {});
const originDelegate = Cors.produceOriginDelegate(corsOptions);
if (originDelegate) {
const requestMethod = request.method;
const getRequestHeader = (headerKey: string) =>
request.headers.get(headerKey);
const getResponseHeader = (headerKey: string) =>
response.headers.get(headerKey);
const setResponseHeader = (headerKey: string, headerValue: string) =>
response.headers.set(headerKey, headerValue);
const setStatus = (
statusCode: number,
) => (response.status = statusCode);
const end = () => {};
const origin = await originDelegate(getRequestHeader("origin"));
if (!origin) return next();
else {
corsOptions.origin = origin;
return new Cors({
corsOptions,
requestMethod,
getRequestHeader,
getResponseHeader,
setResponseHeader,
setStatus,
next,
end,
}).configureHeaders();
}
}
} catch (error) {
console.error(error);
}
return next();
}) as MiddlewareT;
};
+81
View File
@@ -0,0 +1,81 @@
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
import { Cors } from "./cors.ts";
interface Req {
method: string;
headers: {
get(headerKey: string): string | null | undefined;
};
}
interface Res {
status: (status: number) => any;
headers: {
get(headerKey: string): string | null | undefined;
set(headerKey: string, headerValue: string): any;
};
send: (body: any) => any;
end(...args: any): any;
}
/**
* attainCors middleware wrapper
* @param o CorsOptions | CorsOptionsDelegate
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
*/
export const attainCors = <
RequestT extends Req = any,
ResponseT extends Res = any,
MiddlewareT extends (request: RequestT, response: ResponseT) => any = any,
>(
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
) => {
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
CorsOptionsDelegate<RequestT>
>(o);
return async function cors(request, response) {
try {
const fakeNext = () => undefined;
const options = await corsOptionsDelegate(request);
const corsOptions = Cors.produceCorsOptions(options || {});
const originDelegate = Cors.produceOriginDelegate(corsOptions);
if (originDelegate) {
const requestMethod = request.method;
const getRequestHeader = (headerKey: string) =>
request.headers.get(headerKey);
const getResponseHeader = (headerKey: string) =>
response.headers.get(headerKey);
const setResponseHeader = (headerKey: string, headerValue: string) =>
response.headers.set(headerKey, headerValue);
const setStatus = (statusCode: number) => response.status(statusCode);
const end = response.end();
const origin = await originDelegate(getRequestHeader("origin"));
if (origin) {
corsOptions.origin = origin;
new Cors({
corsOptions,
requestMethod,
getRequestHeader,
getResponseHeader,
setResponseHeader,
setStatus,
next: fakeNext,
end,
}).configureHeaders();
}
}
if (request.method === "OPTIONS") {
response.send("");
}
} catch (error) {
console.error(error);
}
} as MiddlewareT;
};
+291
View File
@@ -0,0 +1,291 @@
import type {
CorsOptions,
CorsOptionsDelegate,
OriginDelegate,
} from "./types.ts";
interface DefaultCorsOptions {
origin: string;
methods: string;
preflightContinue: boolean;
optionsSuccessStatus: number;
}
interface CorsProps {
corsOptions: ReturnType<typeof Cors.produceCorsOptions>;
requestMethod: string;
getRequestHeader: (headerKey: string) => string | null | undefined;
getResponseHeader: (headerKey: string) => string | null | undefined;
setResponseHeader: (headerKey: string, headerValue: string) => any;
setStatus: (statusCode: number) => any;
next: (...args: any) => any;
end: (...args: any) => any;
}
export class Cors {
constructor(private props: CorsProps) {}
public static produceCorsOptions = (
corsOptions: CorsOptions = {},
defaultCorsOptions: DefaultCorsOptions = {
origin: "*",
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
preflightContinue: false,
optionsSuccessStatus: 204,
},
) => ({
...defaultCorsOptions,
...corsOptions,
});
public static produceCorsOptionsDelegate = <
OptionsCallbackT = CorsOptionsDelegate<any>,
>(
o?: CorsOptions | OptionsCallbackT,
) =>
typeof o === "function"
? (o as OptionsCallbackT)
: ((((_request: any) => o) as unknown) as OptionsCallbackT);
public static produceOriginDelegate = (
corsOptions: CorsProps["corsOptions"],
) => {
if (corsOptions.origin) {
if (typeof corsOptions.origin === "function") {
return corsOptions.origin as OriginDelegate;
}
return ((_requestOrigin) => corsOptions.origin) as OriginDelegate;
}
};
public static isOriginAllowed = (
requestOrigin: string | null | undefined,
allowedOrigin: CorsOptions["origin"],
): boolean => {
if (Array.isArray(allowedOrigin)) {
return allowedOrigin.some((ao) =>
Cors.isOriginAllowed(requestOrigin, ao)
);
} else if (typeof allowedOrigin === "string") {
return requestOrigin === allowedOrigin;
} else if (
allowedOrigin instanceof RegExp &&
typeof requestOrigin === "string"
) {
return allowedOrigin.test(requestOrigin);
} else return !!allowedOrigin;
};
public configureHeaders = () => {
const {
props: {
corsOptions,
requestMethod,
setResponseHeader,
setStatus,
next,
end,
},
configureOrigin,
} = this;
if (
typeof requestMethod === "string" &&
requestMethod.toUpperCase() === "OPTIONS"
) {
configureOrigin()
.configureCredentials()
.configureMethods()
.configureAllowedHeaders()
.configureMaxAge()
.configureExposedHeaders();
if (corsOptions.preflightContinue) return next();
else {
setStatus(corsOptions.optionsSuccessStatus);
setResponseHeader("Content-Length", "0");
return end();
}
} else {
configureOrigin().configureCredentials().configureExposedHeaders();
return next();
}
};
private configureOrigin = () => {
const {
props: { corsOptions, getRequestHeader, setResponseHeader },
setVaryHeader,
} = this;
if (!corsOptions.origin || corsOptions.origin === "*") {
setResponseHeader("Access-Control-Allow-Origin", "*");
} else if (typeof corsOptions.origin === "string") {
setResponseHeader("Access-Control-Allow-Origin", corsOptions.origin);
setVaryHeader("Origin");
} else {
const requestOrigin = getRequestHeader("origin") ??
getRequestHeader("Origin");
setResponseHeader(
"Access-Control-Allow-Origin",
Cors.isOriginAllowed(requestOrigin, corsOptions.origin)
? (requestOrigin as string)
: "false",
);
setVaryHeader("Origin");
}
return this;
};
private configureCredentials = () => {
const { corsOptions, setResponseHeader } = this.props;
if (corsOptions.credentials === true) {
setResponseHeader("Access-Control-Allow-Credentials", "true");
}
return this;
};
private configureMethods = () => {
const { corsOptions, setResponseHeader } = this.props;
let methods = corsOptions.methods;
setResponseHeader(
"Access-Control-Allow-Methods",
Array.isArray(methods) ? methods.join(",") : methods,
);
return this;
};
private configureAllowedHeaders = () => {
const {
props: { corsOptions, getRequestHeader, setResponseHeader },
setVaryHeader,
} = this;
let allowedHeaders = corsOptions.allowedHeaders;
if (!allowedHeaders) {
allowedHeaders = getRequestHeader("access-control-request-headers") ??
getRequestHeader("Access-Control-Request-Headers") ??
undefined;
setVaryHeader("Access-Control-request-Headers");
}
if (allowedHeaders?.length) {
setResponseHeader(
"Access-Control-Allow-Headers",
Array.isArray(allowedHeaders)
? allowedHeaders.join(",")
: allowedHeaders,
);
}
return this;
};
private configureMaxAge = () => {
const { corsOptions, setResponseHeader } = this.props;
const maxAge = (typeof corsOptions.maxAge === "number" ||
typeof corsOptions.maxAge === "string") &&
corsOptions.maxAge.toString();
if (maxAge && maxAge.length) {
setResponseHeader("Access-Control-Max-Age", maxAge);
}
return this;
};
private configureExposedHeaders = () => {
const { corsOptions, setResponseHeader } = this.props;
let exposedHeaders = corsOptions.exposedHeaders;
if (exposedHeaders?.length) {
setResponseHeader(
"Access-Control-Expose-Headers",
Array.isArray(exposedHeaders)
? exposedHeaders.join(",")
: exposedHeaders,
);
}
return this;
};
private setVaryHeader = (field: string) => {
const {
props: { getResponseHeader, setResponseHeader },
appendVaryHeader,
} = this;
let existingHeader = getResponseHeader("Vary") ?? "";
if (
existingHeader &&
typeof existingHeader === "string" &&
(existingHeader = appendVaryHeader(existingHeader, field))
) {
setResponseHeader("Vary", existingHeader);
}
};
private appendVaryHeader = (header: string, field: string) => {
const { parseVaryHeader } = this;
if (header === "*") return header;
let varyHeader = header;
const fields = parseVaryHeader(field);
const headers = parseVaryHeader(header.toLocaleLowerCase());
if (fields.includes("*") || headers.includes("*")) return "*";
fields.forEach((field) => {
const fld = field.toLowerCase();
if (headers.includes(fld)) {
headers.push(fld);
varyHeader = varyHeader ? `${varyHeader}, ${field}` : field;
}
});
return varyHeader;
};
private parseVaryHeader = (header: string) => {
let end = 0;
const list = [];
let start = 0;
for (let i = 0, len = header.length; i < len; i++) {
switch (header.charCodeAt(i)) {
case 0x20 /* */:
if (start === end) start = end = i + 1;
break;
case 0x2c /* , */:
list.push(header.substring(start, end));
start = end = i + 1;
break;
default:
end = i + 1;
break;
}
}
list.push(header.substring(start, end));
return list;
};
}
+86
View File
@@ -0,0 +1,86 @@
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
import { Cors } from "./cors.ts";
interface Req {
serverRequest: {
method: string;
headers: {
get(headerKey: string): string | null | undefined;
};
};
}
interface Res {
status?: number | string;
headers: {
get(headerKey: string): string | null | undefined;
set(headerKey: string, headerValue: string): any;
};
}
/**
* mithCors middleware wrapper
* @param o CorsOptions | CorsOptionsDelegate
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
*/
export const mithCors = <
RequestT extends Req = any,
ResponseT extends Res = any,
MiddlewareT extends (
request: RequestT,
response: ResponseT,
next: (...args: any) => any,
) => any = any,
>(
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
) => {
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
CorsOptionsDelegate<RequestT>
>(o);
return (async (request, response, next) => {
const serverRequest = request.serverRequest;
try {
const options = await corsOptionsDelegate(request);
const corsOptions = Cors.produceCorsOptions(options || {});
const originDelegate = Cors.produceOriginDelegate(corsOptions);
if (originDelegate) {
const requestMethod = serverRequest.method;
const getRequestHeader = (headerKey: string) =>
serverRequest.headers.get(headerKey);
const getResponseHeader = (headerKey: string) =>
response.headers.get(headerKey);
const setResponseHeader = (headerKey: string, headerValue: string) =>
response.headers.set(headerKey, headerValue);
const setStatus = (
statusCode: number,
) => (response.status = statusCode);
const end = () => {};
const origin = await originDelegate(getRequestHeader("origin"));
if (!origin) return next();
else {
corsOptions.origin = origin;
return new Cors({
corsOptions,
requestMethod,
getRequestHeader,
getResponseHeader,
setResponseHeader,
setStatus,
next,
end,
}).configureHeaders();
}
}
} catch (error) {
console.error(error);
}
return next();
}) as MiddlewareT;
};
+7
View File
@@ -0,0 +1,7 @@
export * from "./types.ts";
export { oakCors } from "./oakCors.ts";
export { abcCors } from "./abcCors.ts";
export { opineCors } from "./opineCors.ts";
export { mithCors } from "./mithCors.ts";
export { attainCors } from "./attainCors.ts";
+82
View File
@@ -0,0 +1,82 @@
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
import { Cors } from "./cors.ts";
interface Req {
method: string;
headers: {
get(headerKey: string): string | null | undefined;
};
}
interface Res {
status?: number | string;
headers: {
get(headerKey: string): string | null | undefined;
set(headerKey: string, headerValue: string): any;
};
}
/**
* oakCors middleware wrapper
* @param o CorsOptions | CorsOptionsDelegate
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
*/
export const oakCors = <
RequestT extends Req = any,
ResponseT extends Res = any,
MiddlewareT extends (
context: { request: RequestT; response: ResponseT },
next: (...args: any) => any,
) => any = any,
>(
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
) => {
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
CorsOptionsDelegate<RequestT>
>(o);
return (async ({ request, response }, next) => {
try {
const options = await corsOptionsDelegate(request);
const corsOptions = Cors.produceCorsOptions(options || {});
const originDelegate = Cors.produceOriginDelegate(corsOptions);
if (originDelegate) {
const requestMethod = request.method;
const getRequestHeader = (headerKey: string) =>
request.headers.get(headerKey);
const getResponseHeader = (headerKey: string) =>
response.headers.get(headerKey);
const setResponseHeader = (headerKey: string, headerValue: string) =>
response.headers.set(headerKey, headerValue);
const setStatus = (
statusCode: number,
) => (response.status = statusCode);
const end = () => {};
const origin = await originDelegate(getRequestHeader("origin"));
if (!origin) next();
else {
corsOptions.origin = origin;
return new Cors({
corsOptions,
requestMethod,
getRequestHeader,
getResponseHeader,
setResponseHeader,
setStatus,
next,
end,
}).configureHeaders();
}
}
} catch (error) {
console.error(error);
}
next();
}) as MiddlewareT;
};
+81
View File
@@ -0,0 +1,81 @@
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
import { Cors } from "./cors.ts";
interface Req {
method: string;
headers: {
get(name: string): string | null | undefined;
};
}
interface Res {
setStatus(code: any): this;
set(headerKey: string, headerValue: string): this;
get(headerKey: string): string | null | undefined;
end(...args: any): any;
}
/**
* opineCors middleware wrapper
* @param o CorsOptions | CorsOptionsDelegate
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
*/
export const opineCors = <
RequestT extends Req = Req,
ResponseT extends Res = any,
MiddlewareT extends (
request: RequestT,
response: ResponseT,
next: (...args: any) => any,
) => any = any,
>(
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
) => {
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
CorsOptionsDelegate<RequestT>
>(o);
return (async (request, response, next) => {
try {
const options = await corsOptionsDelegate(request);
const corsOptions = Cors.produceCorsOptions(options || {});
const originDelegate = Cors.produceOriginDelegate(corsOptions);
if (originDelegate) {
const requestMethod = request.method;
const getRequestHeader = (headerKey: string) =>
request.headers.get(headerKey);
const getResponseHeader = (headerKey: string) =>
response.get(headerKey);
const setResponseHeader = (headerKey: string, headerValue: string) =>
response.set(headerKey, headerValue);
const setStatus = (statusCode: number) =>
response.setStatus(statusCode);
const end = () => response.end();
const origin = await originDelegate(getRequestHeader("origin"));
if (!origin) return next();
else {
corsOptions.origin = origin;
return new Cors({
corsOptions,
requestMethod,
getRequestHeader,
getResponseHeader,
setResponseHeader,
setStatus,
next,
end,
}).configureHeaders();
}
}
} catch (error) {
console.error(error);
}
return next();
}) as MiddlewareT;
};
+228
View File
@@ -0,0 +1,228 @@
/**
* CorsOptions
*
* An Object that describes how CORS middleware should behave. The default configuration is the equivalent of:
*
* ```ts
* {
* "origin": "*",
* "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
* "preflightContinue": false,
* "optionsSuccessStatus": 204,
* }
* ```
*
* @link https://github.com/tajpouria/cors#configuration-options
*/
export type CorsOptions = {
/**
* Configures the Access-Control-Allow-Origin CORS header.
*
* Examples:
*
* - Boolean
*
* set origin to true to reflect the request origin, as defined by req.header('Origin'):
*
* ```ts
* app.use(cors({ origin: true }));
*
* ```
* or set it to false to disable CORS:
*
* ```ts
* app.use(cors({ origin: false }));
*
* ```
*
* - String
*
* set origin to a specific origin. For example if you set it to "http://example.com" only requests from "http://example.com" will be allowed:
*
* ```ts
* app.use(cors({ origin: 'http://example.com' }));
*
* ```
*
* - RegExp
*
* set origin to a regular expression pattern which will be used to test the request origin. If it's a match, the request origin will be reflected. For example the pattern /example\.com$/ will reflect any request that is coming from an origin ending with "example.com":
*
* ```ts
* app.use(cors({ origin: /example\.com$/ }));
*
* ```
*
* - Array
*
* set origin to an array of valid origins. Each origin can be a String or a RegExp. For example ["http://example1.com", /\.example2\.com$/] will accept any request from "http://example1.com" or from a subdomain of "example2.com":
*
* ```ts
* app.use(cors({ origin: ["http://example1.com", /\.example2\.com$/] }));
*
* ```
*
* - Function
*
* set origin to a function implementing some custom logic. The function takes the request origin as the first parameter and a callback (called as callback(err, origin), where origin is a non-function value of the origin option) as the second:
*
* ```ts
* app.use(cors({
* origin: async (requestOrigin) => {
* await loadOriginsFromDataBase(); // Simulate asynchronous task
* return ["http://example1.com", /\.example2\.com$/];
* },
* }));
*
* ```
*
*/
origin?: boolean | string | RegExp | (string | RegExp)[] | OriginDelegate;
/**
* Configures the Access-Control-Allow-Methods CORS header.
*
* Examples:
*
* - String
*
* Expects a comma-delimited string (ex: 'GET,PUT,POST'):
*
* ```ts
* app.use(cors({ methods: 'GET,PUT,POST' }));
*
* ```
*
* - Array
*
* or an array (ex: ['GET', 'PUT', 'POST'])
*
* ```ts
* app.use(cors({ methods: ['GET', 'PUT', 'POST'] }));
*
* ```
*
*/
methods?: string | string[];
/**
* Configures the Access-Control-Allow-Headers CORS header. If not specified, defaults to reflecting the headers specified in the request's Access-Control-Request-Headers header.
*
* Examples:
*
* - String
*
* Expects a comma-delimited string (ex: 'Content-Type,Authorization'):
*
* ```ts
* app.use(cors({ allowedHeaders: 'Content-Type,Authorization' }));
*
* ```
*
* - Array
*
* or an array (ex: ['Content-Type', 'Authorization']):
*
* ```ts
* app.use(cors({ allowedHeaders: ['Content-Type', 'Authorization'] }));
*
* ```
*
*/
allowedHeaders?: string | string[];
/**
* Configures the Access-Control-Expose-Headers CORS header. If not specified, no custom headers are exposed.
*
* Examples:
*
* - String
*
* Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range'):
*
* ```ts
* app.use(cors({ exposedHeaders: 'Content-Range,X-Content-Range' }));
*
* ```
*
*/
exposedHeaders?: string | string[];
/**
* Configures the Access-Control-Allow-Credentials CORS header. It is omitted by default.
*
* Examples:
*
* - Boolean
*
* Set to true to pass the header:
*
* ```ts
* app.use(cors({ credentials: true }));
*
* ```
*
*/
credentials?: boolean;
/**
* Configures the Access-Control-Max-Age CORS header. It is omitted by default.
*
* Examples:
*
* - Number
*
* Set to an integer to pass the header:
*
* ```ts
* app.use(cors({ maxAge: 1 }));
*
* ```
*
*/
maxAge?: number;
/**
* Pass the CORS preflight response to the next handler:
*
* Examples:
*
* - Boolean
*
* ```ts
* app.use(cors({ preflightContinue: true }));
*
* ```
*
*/
preflightContinue?: boolean;
/**
* Provides a status code to use for successful OPTIONS requests, since some legacy browsers (IE11, various SmartTVs) choke on 204.
*
* Examples:
*
* - Number
*
* ```ts
* app.use(cors({ optionsSuccessStatus: 200 }));
*
* ```
*
*/
optionsSuccessStatus?: number;
};
/**
* @link https://github.com/tajpouria/cors#configuring-cors-w-dynamic-origin
*/
export type OriginDelegate = (
requestOrigin: string | undefined | null,
) => CorsOptions["origin"] | void | Promise<CorsOptions["origin"] | void>;
/**
* @link https://github.com/tajpouria/cors#configuring-cors-asynchronously
*/
export type CorsOptionsDelegate<RequestT = any> = (
request: RequestT,
) => CorsOptions | void | Promise<CorsOptions | void>;