From 9bdb66d3c5b150d34ebe30d561f15bdfed3873a0 Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Thu, 28 Sep 2023 16:00:48 +0200 Subject: [PATCH] feat: make it possible to use our middleware in some unsupported frameworks --- framework/custom/index.d.ts | 3 + framework/custom/index.js | 6 + lib/build/framework/custom/framework.d.ts | 76 +++++++++ lib/build/framework/custom/framework.js | 138 ++++++++++++++++ lib/build/framework/custom/index.d.ts | 13 ++ lib/build/framework/custom/index.js | 33 ++++ lib/ts/framework/custom/framework.ts | 186 ++++++++++++++++++++++ lib/ts/framework/custom/index.ts | 20 +++ 8 files changed, 475 insertions(+) create mode 100644 framework/custom/index.d.ts create mode 100644 framework/custom/index.js create mode 100644 lib/build/framework/custom/framework.d.ts create mode 100644 lib/build/framework/custom/framework.js create mode 100644 lib/build/framework/custom/index.d.ts create mode 100644 lib/build/framework/custom/index.js create mode 100644 lib/ts/framework/custom/framework.ts create mode 100644 lib/ts/framework/custom/index.ts diff --git a/framework/custom/index.d.ts b/framework/custom/index.d.ts new file mode 100644 index 0000000000..0279b7e196 --- /dev/null +++ b/framework/custom/index.d.ts @@ -0,0 +1,3 @@ +export * from "../../lib/build/framework/custom"; +import * as _default from "../../lib/build/framework/custom"; +export default _default; diff --git a/framework/custom/index.js b/framework/custom/index.js new file mode 100644 index 0000000000..2cb2677008 --- /dev/null +++ b/framework/custom/index.js @@ -0,0 +1,6 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +exports.__esModule = true; +__export(require("../../lib/build/framework/custom")); diff --git a/lib/build/framework/custom/framework.d.ts b/lib/build/framework/custom/framework.d.ts new file mode 100644 index 0000000000..2e0c7ca239 --- /dev/null +++ b/lib/build/framework/custom/framework.d.ts @@ -0,0 +1,76 @@ +// @ts-nocheck +import type { HTTPMethod } from "../../types"; +import { BaseRequest } from "../request"; +import { BaseResponse } from "../response"; +declare type RequestInfo = { + url: string; + method: HTTPMethod; + headers: Headers; + cookies: Record; + query: Record; + getJSONBody: () => Promise; + getFormBody: () => Promise; +}; +export declare class PreParsedRequest extends BaseRequest { + private request; + constructor(request: RequestInfo); + getFormData: () => Promise; + getKeyValueFromQuery: (key: string) => string | undefined; + getJSONBody: () => Promise; + getMethod: () => HTTPMethod; + getCookieValue: (key: string) => string | undefined; + getHeaderValue: (key: string) => string | undefined; + getOriginalURL: () => string; +} +export declare type CookieInfo = { + key: string; + value: string; + domain: string | undefined; + secure: boolean; + httpOnly: boolean; + expires: number; + path: string; + sameSite: "strict" | "lax" | "none"; +}; +export declare class CollectingResponse extends BaseResponse { + statusCode: number; + readonly headers: Headers; + readonly cookies: CookieInfo[]; + body?: string; + constructor(); + sendHTMLResponse: (html: string) => void; + setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; + removeHeader: (key: string) => void; + setCookie: ( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" + ) => void; + /** + * @param {number} statusCode + */ + setStatusCode: (statusCode: number) => void; + sendJSONResponse: (content: any) => void; +} +export declare type NextFunction = (err?: any) => void; +export declare const middleware: () => ( + request: BaseRequest, + response: BaseResponse, + next: NextFunction +) => Promise; +export declare const errorHandler: () => ( + err: any, + request: BaseRequest, + response: BaseResponse, + next: NextFunction +) => Promise; +export declare const CustomFrameworkWrapper: { + middleware: () => (request: BaseRequest, response: BaseResponse, next: NextFunction) => Promise; + errorHandler: () => (err: any, request: BaseRequest, response: BaseResponse, next: NextFunction) => Promise; +}; +export {}; diff --git a/lib/build/framework/custom/framework.js b/lib/build/framework/custom/framework.js new file mode 100644 index 0000000000..8d31076e34 --- /dev/null +++ b/lib/build/framework/custom/framework.js @@ -0,0 +1,138 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CustomFrameworkWrapper = exports.errorHandler = exports.middleware = exports.CollectingResponse = exports.PreParsedRequest = void 0; +const utils_1 = require("../../utils"); +const request_1 = require("../request"); +const response_1 = require("../response"); +const supertokens_1 = __importDefault(require("../../supertokens")); +class PreParsedRequest extends request_1.BaseRequest { + constructor(request) { + super(); + this.getFormData = async () => { + return this.request.getFormBody(); + }; + this.getKeyValueFromQuery = (key) => { + if (this.request.query === undefined) { + return undefined; + } + let value = this.request.query[key]; + if (value === undefined || typeof value !== "string") { + return undefined; + } + return value; + }; + this.getJSONBody = async () => { + return this.request.getJSONBody(); + }; + this.getMethod = () => { + return utils_1.normaliseHttpMethod(this.request.method); + }; + this.getCookieValue = (key) => { + return this.request.cookies[key]; + }; + this.getHeaderValue = (key) => { + var _a; + return (_a = this.request.headers.get(key)) !== null && _a !== void 0 ? _a : undefined; + }; + this.getOriginalURL = () => { + return this.request.url; + }; + this.original = request; + this.request = request; + } +} +exports.PreParsedRequest = PreParsedRequest; +class CollectingResponse extends response_1.BaseResponse { + constructor() { + super(); + this.sendHTMLResponse = (html) => { + this.headers.set("Content-Type", "text/html"); + this.body = html; + }; + this.setHeader = (key, value, allowDuplicateKey) => { + if (allowDuplicateKey) { + this.headers.append(key, value); + } else { + this.headers.set(key, value); + } + }; + this.removeHeader = (key) => { + this.headers.delete(key); + }; + this.setCookie = (key, value, domain, secure, httpOnly, expires, path, sameSite) => { + this.cookies.push({ key, value, domain, secure, httpOnly, expires, path, sameSite }); + }; + /** + * @param {number} statusCode + */ + this.setStatusCode = (statusCode) => { + this.statusCode = statusCode; + }; + this.sendJSONResponse = (content) => { + this.headers.set("Content-Type", "application/json"); + this.body = JSON.stringify(content); + }; + this.headers = new Headers(); + this.statusCode = 200; + this.cookies = []; + } +} +exports.CollectingResponse = CollectingResponse; +const middleware = () => { + return async (request, response, next) => { + let supertokens; + const userContext = utils_1.makeDefaultUserContextFromAPI(request); + try { + supertokens = supertokens_1.default.getInstanceOrThrowError(); + const result = await supertokens.middleware(request, response, userContext); + if (!result) { + return next(); + } + } catch (err) { + if (supertokens) { + try { + await supertokens.errorHandler(err, request, response); + } catch (_a) { + next(err); + } + } else { + next(err); + } + } + }; +}; +exports.middleware = middleware; +const errorHandler = () => { + return async (err, request, response, next) => { + let supertokens = supertokens_1.default.getInstanceOrThrowError(); + try { + await supertokens.errorHandler(err, request, response); + } catch (err) { + return next(err); + } + }; +}; +exports.errorHandler = errorHandler; +exports.CustomFrameworkWrapper = { + middleware: exports.middleware, + errorHandler: exports.errorHandler, +}; diff --git a/lib/build/framework/custom/index.d.ts b/lib/build/framework/custom/index.d.ts new file mode 100644 index 0000000000..564acbc669 --- /dev/null +++ b/lib/build/framework/custom/index.d.ts @@ -0,0 +1,13 @@ +// @ts-nocheck +export { PreParsedRequest, CollectingResponse } from "./framework"; +export declare const middleware: () => ( + request: import("..").BaseRequest, + response: import("..").BaseResponse, + next: import("./framework").NextFunction +) => Promise; +export declare const errorHandler: () => ( + err: any, + request: import("..").BaseRequest, + response: import("..").BaseResponse, + next: import("./framework").NextFunction +) => Promise; diff --git a/lib/build/framework/custom/index.js b/lib/build/framework/custom/index.js new file mode 100644 index 0000000000..31170e3519 --- /dev/null +++ b/lib/build/framework/custom/index.js @@ -0,0 +1,33 @@ +"use strict"; +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.errorHandler = exports.middleware = exports.CollectingResponse = exports.PreParsedRequest = void 0; +const framework_1 = require("./framework"); +var framework_2 = require("./framework"); +Object.defineProperty(exports, "PreParsedRequest", { + enumerable: true, + get: function () { + return framework_2.PreParsedRequest; + }, +}); +Object.defineProperty(exports, "CollectingResponse", { + enumerable: true, + get: function () { + return framework_2.CollectingResponse; + }, +}); +exports.middleware = framework_1.CustomFrameworkWrapper.middleware; +exports.errorHandler = framework_1.CustomFrameworkWrapper.errorHandler; diff --git a/lib/ts/framework/custom/framework.ts b/lib/ts/framework/custom/framework.ts new file mode 100644 index 0000000000..0519251f2b --- /dev/null +++ b/lib/ts/framework/custom/framework.ts @@ -0,0 +1,186 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import type { HTTPMethod } from "../../types"; +import { makeDefaultUserContextFromAPI, normaliseHttpMethod } from "../../utils"; +import { BaseRequest } from "../request"; +import { BaseResponse } from "../response"; +import SuperTokens from "../../supertokens"; + +type RequestInfo = { + url: string; + method: HTTPMethod; + headers: Headers; + cookies: Record; + query: Record; + getJSONBody: () => Promise; + getFormBody: () => Promise; +}; + +export class PreParsedRequest extends BaseRequest { + private request: RequestInfo; + + constructor(request: RequestInfo) { + super(); + this.original = request; + this.request = request; + } + + getFormData = async (): Promise => { + return this.request.getFormBody(); + }; + + getKeyValueFromQuery = (key: string): string | undefined => { + if (this.request.query === undefined) { + return undefined; + } + let value = this.request.query[key]; + if (value === undefined || typeof value !== "string") { + return undefined; + } + return value; + }; + + getJSONBody = async (): Promise => { + return this.request.getJSONBody(); + }; + + getMethod = (): HTTPMethod => { + return normaliseHttpMethod(this.request.method); + }; + + getCookieValue = (key: string): string | undefined => { + return this.request.cookies[key]; + }; + + getHeaderValue = (key: string): string | undefined => { + return this.request.headers.get(key) ?? undefined; + }; + + getOriginalURL = (): string => { + return this.request.url; + }; +} + +export type CookieInfo = { + key: string; + value: string; + domain: string | undefined; + secure: boolean; + httpOnly: boolean; + expires: number; + path: string; + sameSite: "strict" | "lax" | "none"; +}; + +export class CollectingResponse extends BaseResponse { + public statusCode: number; + public readonly headers: Headers; + public readonly cookies: CookieInfo[]; + public body?: string; + + constructor() { + super(); + this.headers = new Headers(); + this.statusCode = 200; + this.cookies = []; + } + + sendHTMLResponse = (html: string) => { + this.headers.set("Content-Type", "text/html"); + this.body = html; + }; + + setHeader = (key: string, value: string, allowDuplicateKey: boolean) => { + if (allowDuplicateKey) { + this.headers.append(key, value); + } else { + this.headers.set(key, value); + } + }; + + removeHeader = (key: string) => { + this.headers.delete(key); + }; + + setCookie = ( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" + ) => { + this.cookies.push({ key, value, domain, secure, httpOnly, expires, path, sameSite }); + }; + + /** + * @param {number} statusCode + */ + setStatusCode = (statusCode: number) => { + this.statusCode = statusCode; + }; + + sendJSONResponse = (content: any) => { + this.headers.set("Content-Type", "application/json"); + this.body = JSON.stringify(content); + }; +} + +export type NextFunction = (err?: any) => void; + +export const middleware = () => { + return async (request: BaseRequest, response: BaseResponse, next: NextFunction) => { + let supertokens; + const userContext = makeDefaultUserContextFromAPI(request); + + try { + supertokens = SuperTokens.getInstanceOrThrowError(); + const result = await supertokens.middleware(request, response, userContext); + if (!result) { + return next(); + } + } catch (err) { + if (supertokens) { + try { + await supertokens.errorHandler(err, request, response); + } catch { + next(err); + } + } else { + next(err); + } + } + }; +}; + +export const errorHandler = () => { + return async (err: any, request: BaseRequest, response: BaseResponse, next: NextFunction) => { + let supertokens = SuperTokens.getInstanceOrThrowError(); + + try { + await supertokens.errorHandler(err, request, response); + } catch (err) { + return next(err); + } + }; +}; + +export const CustomFrameworkWrapper = { + middleware, + errorHandler, +}; diff --git a/lib/ts/framework/custom/index.ts b/lib/ts/framework/custom/index.ts new file mode 100644 index 0000000000..0f01e9556c --- /dev/null +++ b/lib/ts/framework/custom/index.ts @@ -0,0 +1,20 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { CustomFrameworkWrapper } from "./framework"; +export { PreParsedRequest, CollectingResponse } from "./framework"; + +export const middleware = CustomFrameworkWrapper.middleware; +export const errorHandler = CustomFrameworkWrapper.errorHandler;