diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c79fa2b8..2b87a62d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +# Adds + +- Support for h3 framework. + ## [11.0.0] - 2022-07-05 ### Breaking change: @@ -159,6 +163,10 @@ SuperTokens.init({ }) ``` +### Adds + +- Support for h3 freamwork. + ## [9.2.3] - 2022-06-03 - Changes `getUserMetadata` to return `any` type for metadata so that it's easier to use. diff --git a/add-ts-no-check.js b/add-ts-no-check.js index 4a256e98f..0edd5f5c8 100644 --- a/add-ts-no-check.js +++ b/add-ts-no-check.js @@ -8,7 +8,7 @@ glob(__dirname + "/lib/**/*.d.ts", (err, files) => { let lines = contents.split("\n"); let newContents = `// @ts-nocheck`; for (line of lines) { - if (line.match(/\/\/\/\ \ +import { H3Event } from "h3"; +import type { IncomingMessage, ServerResponse } from "http"; +import { SessionContainerInterface } from "../../recipe/session/types"; +import { BaseRequest } from "../request"; +import { BaseResponse } from "../response"; +import { Framework } from "../types"; +export declare class H3Request extends BaseRequest { + private request; + constructor(request: IncomingMessage); + getCookieValue: (key: string) => string | undefined; + getFormData: () => Promise; + getMethod: () => import("../../types").HTTPMethod; + getHeaderValue: (key: string) => string | undefined; + getOriginalURL: () => string; + getKeyValueFromQuery: (key: string) => string | undefined; + getJSONBody: () => Promise; +} +export declare class H3ResponseTokens extends BaseResponse { + private response; + private statusCode; + constructor(response: ServerResponse); + sendHTMLResponse: (html: string) => void; + setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void; + setCookie: ( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" + ) => void; + setStatusCode: (statusCode: number) => void; + sendJSONResponse: (content: any) => void; +} +export interface SessionRequest extends IncomingMessage { + session?: SessionContainerInterface; +} +export declare const middlware: () => ( + req: IncomingMessage, + res: ServerResponse, + next: (err?: Error | undefined) => any +) => Promise; +export declare const errorHandler: () => (event: H3Event, errorPlain: Error, statusCode: number) => Promise; +export interface H3Framework extends Framework { + middlware: () => (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => any) => Promise; + errorHandler: () => (event: H3Event, errorPlain: Error, statusCode: number) => Promise; +} +export declare const H3Wrapper: H3Framework; diff --git a/lib/build/framework/h3/framework.js b/lib/build/framework/h3/framework.js new file mode 100644 index 000000000..09d148c15 --- /dev/null +++ b/lib/build/framework/h3/framework.js @@ -0,0 +1,185 @@ +"use strict"; +var __awaiter = + (this && this.__awaiter) || + function (thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P + ? value + : new P(function (resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const h3_1 = require("h3"); +const supertokens_1 = require("../../supertokens"); +const utils_1 = require("../../utils"); +const request_1 = require("../request"); +const response_1 = require("../response"); +const utils_2 = require("../utils"); +class H3Request extends request_1.BaseRequest { + constructor(request) { + super(); + this.getCookieValue = (key) => { + return utils_2.getCookieValueFromIncomingMessage(this.request, key); + }; + this.getFormData = () => + __awaiter(this, void 0, void 0, function* () { + return utils_2.useRawBody(this.request); + }); + this.getMethod = () => { + return utils_1.normaliseHttpMethod(this.request.method); + }; + this.getHeaderValue = (key) => { + return utils_2.getHeaderValueFromIncomingMessage(this.request, key); + }; + this.getOriginalURL = () => { + return this.request.url; + }; + this.getKeyValueFromQuery = (key) => { + let path = this.request.url || "/"; + const queryIndex = path.lastIndexOf("?"); + if (queryIndex > -1) { + const queryArray = path.substring(queryIndex + 1, path.length).split("&"); + const index = queryArray.findIndex((el) => el.includes(key)); + if (index === -1) return undefined; + const value = queryArray[index].split("=")[1]; + if (value === undefined || typeof value !== "string") { + return undefined; + } + return value; + } else { + return undefined; + } + }; + this.getJSONBody = () => + __awaiter(this, void 0, void 0, function* () { + return yield utils_2.useBody(this.request); + }); + this.original = request; + this.request = request; + } +} +exports.H3Request = H3Request; +class H3ResponseTokens extends response_1.BaseResponse { + constructor(response) { + super(); + this.sendHTMLResponse = (html) => { + if (this.response.writable) { + this.response.setHeader("Content-Type", "text/html"); + this.response.statusCode = this.statusCode; + this.response.end(html); + } + }; + this.setHeader = (key, value, allowDuplicateKey) => { + try { + const allheaders = this.response.getHeaders(); + let existingValue = allheaders[key]; + // we have the this.response.header for compatibility with nextJS + if (existingValue === undefined) { + this.response.setHeader(key, value); + } else if (allowDuplicateKey) { + this.response.setHeader(key, existingValue + ", " + value); + } else { + // we overwrite the current one with the new one + this.response.setHeader(key, value); + } + } catch (err) { + throw new Error("Error while setting header with key: " + key + " and value: " + value); + } + }; + this.setCookie = (key, value, domain, secure, httpOnly, expires, path, sameSite) => { + utils_2.setCookieForServerResponse( + this.response, + key, + value, + domain, + secure, + httpOnly, + expires, + path, + sameSite + ); + }; + this.setStatusCode = (statusCode) => { + if (this.response.writable) { + this.statusCode = statusCode; + } + }; + this.sendJSONResponse = (content) => { + if (this.response.writable) { + content = JSON.stringify(content); + this.response.setHeader("Content-Type", "application/json"); + this.response.statusCode = this.statusCode; + this.response.end(content, "utf-8"); + } + }; + this.original = response; + this.response = response; + this.statusCode = 200; + } +} +exports.H3ResponseTokens = H3ResponseTokens; +exports.middlware = () => { + return (req, res, next) => + __awaiter(void 0, void 0, void 0, function* () { + let supertokens; + const request = new H3Request(req); + const response = new H3ResponseTokens(res); + try { + supertokens = supertokens_1.default.getInstanceOrThrowError(); + const result = yield supertokens.middleware(request, response); + if (!result) { + return next(); + } + } catch (err) { + if (supertokens) { + try { + yield supertokens.errorHandler(err, request, response); + } catch (_a) { + next(err); + } + } else { + next(err); + } + } + }); +}; +exports.errorHandler = () => { + return (event, errorPlain, statusCode) => + __awaiter(void 0, void 0, void 0, function* () { + const error = h3_1.createError(errorPlain); + error.statusCode = statusCode; + h3_1.sendError(event, error); + }); +}; +exports.H3Wrapper = { + middlware: exports.middlware, + errorHandler: exports.errorHandler, + wrapRequest: (unwrapped) => { + return new H3Request(unwrapped.req); + }, + wrapResponse: (unwrapped) => { + return new H3ResponseTokens(unwrapped.res); + }, +}; diff --git a/lib/build/framework/h3/index.d.ts b/lib/build/framework/h3/index.d.ts new file mode 100644 index 000000000..27774d1ef --- /dev/null +++ b/lib/build/framework/h3/index.d.ts @@ -0,0 +1,15 @@ +// @ts-nocheck +/// +export type { SessionRequest } from "./framework"; +export declare const middleware: () => ( + req: import("http").IncomingMessage, + res: import("http").ServerResponse, + next: (err?: Error | undefined) => any +) => Promise; +export declare const errorHandler: () => ( + event: import("h3").H3Event, + errorPlain: Error, + statusCode: number +) => Promise; +export declare const wrapRequest: (unwrapped: any) => import("..").BaseRequest; +export declare const wrapResponse: (unwrapped: any) => import("..").BaseResponse; diff --git a/lib/build/framework/h3/index.js b/lib/build/framework/h3/index.js new file mode 100644 index 000000000..fa25d0f02 --- /dev/null +++ b/lib/build/framework/h3/index.js @@ -0,0 +1,7 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const framework_1 = require("./framework"); +exports.middleware = framework_1.H3Wrapper.middlware; +exports.errorHandler = framework_1.H3Wrapper.errorHandler; +exports.wrapRequest = framework_1.H3Wrapper.wrapRequest; +exports.wrapResponse = framework_1.H3Wrapper.wrapResponse; diff --git a/lib/build/framework/index.d.ts b/lib/build/framework/index.d.ts index 8fd8891ce..ae5393bdb 100644 --- a/lib/build/framework/index.d.ts +++ b/lib/build/framework/index.d.ts @@ -7,6 +7,7 @@ import * as hapiFramework from "./hapi"; import * as loopbackFramework from "./loopback"; import * as koaFramework from "./koa"; import * as awsLambdaFramework from "./awsLambda"; +import * as h3Framework from "./h3"; declare const _default: { express: typeof expressFramework; fastify: typeof fastifyFramework; @@ -14,6 +15,7 @@ declare const _default: { loopback: typeof loopbackFramework; koa: typeof koaFramework; awsLambda: typeof awsLambdaFramework; + h3: typeof h3Framework; }; export default _default; export declare let express: typeof expressFramework; @@ -22,3 +24,4 @@ export declare let hapi: typeof hapiFramework; export declare let loopback: typeof loopbackFramework; export declare let koa: typeof koaFramework; export declare let awsLambda: typeof awsLambdaFramework; +export declare let h3: typeof h3Framework; diff --git a/lib/build/framework/index.js b/lib/build/framework/index.js index de9aa84e2..222e80db6 100644 --- a/lib/build/framework/index.js +++ b/lib/build/framework/index.js @@ -24,6 +24,7 @@ const hapiFramework = require("./hapi"); const loopbackFramework = require("./loopback"); const koaFramework = require("./koa"); const awsLambdaFramework = require("./awsLambda"); +const h3Framework = require("./h3"); exports.default = { express: expressFramework, fastify: fastifyFramework, @@ -31,6 +32,7 @@ exports.default = { loopback: loopbackFramework, koa: koaFramework, awsLambda: awsLambdaFramework, + h3: h3Framework, }; exports.express = expressFramework; exports.fastify = fastifyFramework; @@ -38,3 +40,4 @@ exports.hapi = hapiFramework; exports.loopback = loopbackFramework; exports.koa = koaFramework; exports.awsLambda = awsLambdaFramework; +exports.h3 = h3Framework; diff --git a/lib/build/framework/types.d.ts b/lib/build/framework/types.d.ts index f685b5e0a..96e89c023 100644 --- a/lib/build/framework/types.d.ts +++ b/lib/build/framework/types.d.ts @@ -1,5 +1,5 @@ // @ts-nocheck -export declare type TypeFramework = "express" | "fastify" | "hapi" | "loopback" | "koa" | "awsLambda"; +export declare type TypeFramework = "express" | "fastify" | "hapi" | "loopback" | "koa" | "awsLambda" | "h3"; import { BaseRequest, BaseResponse } from "."; export declare let SchemaFramework: { type: string; diff --git a/lib/build/framework/types.js b/lib/build/framework/types.js index e3e7797ed..a635dfe9f 100644 --- a/lib/build/framework/types.js +++ b/lib/build/framework/types.js @@ -2,5 +2,5 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemaFramework = { type: "string", - enum: ["express", "fastify", "hapi", "loopback", "koa", "awsLambda"], + enum: ["express", "fastify", "hapi", "loopback", "koa", "awsLambda", "h3"], }; diff --git a/lib/build/framework/utils.d.ts b/lib/build/framework/utils.d.ts index 1fafc184f..987586d65 100644 --- a/lib/build/framework/utils.d.ts +++ b/lib/build/framework/utils.d.ts @@ -24,6 +24,8 @@ export declare function setHeaderForExpressLikeResponse( value: string, allowDuplicateKey: boolean ): void; +export declare function useRawBody(req: IncomingMessage): Promise; +export declare function useBody(req: IncomingMessage): Promise; /** * * @param res diff --git a/lib/build/framework/utils.js b/lib/build/framework/utils.js index 86fcbc54b..60e674cd7 100644 --- a/lib/build/framework/utils.js +++ b/lib/build/framework/utils.js @@ -50,6 +50,7 @@ const body_parser_1 = require("body-parser"); const http_1 = require("http"); const error_1 = require("../error"); const constants_1 = require("./constants"); +const destr_1 = require("destr"); function getCookieValueFromHeaders(headers, key) { if (headers === undefined || headers === null) { return undefined; @@ -237,6 +238,41 @@ function setHeaderForExpressLikeResponse(res, key, value, allowDuplicateKey) { } } exports.setHeaderForExpressLikeResponse = setHeaderForExpressLikeResponse; +function useRawBody(req) { + const RawBodySymbol = Symbol("h3RawBody"); + if (RawBodySymbol in this.request) { + const promise = Promise.resolve(req[RawBodySymbol]); + return promise.then((buff) => buff.toString("utf-8")); + } + if ("body" in req) { + return Promise.resolve(this.request.body); + } + const promise = (req[RawBodySymbol] = new Promise((resolve, reject) => { + const bodyData = []; + req.on("error", (err) => reject(err)) + .on("data", (chunk) => { + bodyData.push(chunk); + }) + .on("end", () => { + resolve(Buffer.concat(bodyData)); + }); + })); + return promise.then((buff) => buff.toString("utf-8")); +} +exports.useRawBody = useRawBody; +function useBody(req) { + return __awaiter(this, void 0, void 0, function* () { + const ParsedBodySymbol = Symbol("h3RawBody"); + if (ParsedBodySymbol in req) { + return req[ParsedBodySymbol]; + } + const body = yield useRawBody(req); + const json = destr_1.default(body); + req[ParsedBodySymbol] = json; + return json; + }); +} +exports.useBody = useBody; /** * * @param res diff --git a/lib/build/recipe/session/framework/h3.d.ts b/lib/build/recipe/session/framework/h3.d.ts new file mode 100644 index 000000000..d5d122039 --- /dev/null +++ b/lib/build/recipe/session/framework/h3.d.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +/// +import type { VerifySessionOptions } from "../types"; +import type { SessionRequest } from "../../../framework/h3"; +import { ServerResponse } from "http"; +export declare function verifySession( + options?: VerifySessionOptions +): (req: SessionRequest, res: ServerResponse, next: (err?: Error | undefined) => any) => Promise; diff --git a/lib/build/recipe/session/framework/h3.js b/lib/build/recipe/session/framework/h3.js new file mode 100644 index 000000000..e5a0d2968 --- /dev/null +++ b/lib/build/recipe/session/framework/h3.js @@ -0,0 +1,56 @@ +"use strict"; +var __awaiter = + (this && this.__awaiter) || + function (thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P + ? value + : new P(function (resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator["throw"](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + }; +Object.defineProperty(exports, "__esModule", { value: true }); +const recipe_1 = require("../recipe"); +const framework_1 = require("../../../framework/h3/framework"); +const supertokens_1 = require("../../../supertokens"); +function verifySession(options) { + return (req, res, next) => + __awaiter(this, void 0, void 0, function* () { + const request = new framework_1.H3Request(req); + const response = new framework_1.H3ResponseTokens(res); + try { + const sessionRecipe = recipe_1.default.getInstanceOrThrowError(); + req.session = yield sessionRecipe.verifySession(options, request, response); + next(); + } catch (err) { + try { + const supertokens = supertokens_1.default.getInstanceOrThrowError(); + yield supertokens.errorHandler(err, request, response); + } catch (err) { + next(err); + } + } + }); +} +exports.verifySession = verifySession; diff --git a/lib/build/recipe/session/framework/index.d.ts b/lib/build/recipe/session/framework/index.d.ts index dfba9d1f7..79cfd213e 100644 --- a/lib/build/recipe/session/framework/index.d.ts +++ b/lib/build/recipe/session/framework/index.d.ts @@ -5,6 +5,7 @@ import * as hapiFramework from "./hapi"; import * as loopbackFramework from "./loopback"; import * as koaFramework from "./koa"; import * as awsLambdaFramework from "./awsLambda"; +import * as h3Framework from "./h3"; declare const _default: { express: typeof expressFramework; fastify: typeof fastifyFramework; @@ -12,6 +13,7 @@ declare const _default: { loopback: typeof loopbackFramework; koa: typeof koaFramework; awsLambda: typeof awsLambdaFramework; + h3: typeof h3Framework; }; export default _default; export declare let express: typeof expressFramework; @@ -20,3 +22,4 @@ export declare let hapi: typeof hapiFramework; export declare let loopback: typeof loopbackFramework; export declare let koa: typeof koaFramework; export declare let awsLambda: typeof awsLambdaFramework; +export declare let h3: typeof h3Framework; diff --git a/lib/build/recipe/session/framework/index.js b/lib/build/recipe/session/framework/index.js index 0f0ccf5cd..6bee76f22 100644 --- a/lib/build/recipe/session/framework/index.js +++ b/lib/build/recipe/session/framework/index.js @@ -20,6 +20,7 @@ const hapiFramework = require("./hapi"); const loopbackFramework = require("./loopback"); const koaFramework = require("./koa"); const awsLambdaFramework = require("./awsLambda"); +const h3Framework = require("./h3"); exports.default = { express: expressFramework, fastify: fastifyFramework, @@ -27,6 +28,7 @@ exports.default = { loopback: loopbackFramework, koa: koaFramework, awsLambda: awsLambdaFramework, + h3: h3Framework, }; exports.express = expressFramework; exports.fastify = fastifyFramework; @@ -34,3 +36,4 @@ exports.hapi = hapiFramework; exports.loopback = loopbackFramework; exports.koa = koaFramework; exports.awsLambda = awsLambdaFramework; +exports.h3 = h3Framework; diff --git a/lib/ts/framework/h3/framework.ts b/lib/ts/framework/h3/framework.ts new file mode 100644 index 000000000..e6df8218b --- /dev/null +++ b/lib/ts/framework/h3/framework.ts @@ -0,0 +1,172 @@ +import { createError, H3Event, sendError } from "h3"; +import type { IncomingMessage, ServerResponse } from "http"; +import { SessionContainerInterface } from "../../recipe/session/types"; +import SuperTokens from "../../supertokens"; +import { normaliseHttpMethod } from "../../utils"; +import { BaseRequest } from "../request"; +import { BaseResponse } from "../response"; +import { Framework } from "../types"; +import { + getCookieValueFromIncomingMessage, + getHeaderValueFromIncomingMessage, + useBody, + useRawBody, + setCookieForServerResponse, +} from "../utils"; + +export class H3Request extends BaseRequest { + private request: IncomingMessage; + constructor(request: IncomingMessage) { + super(); + this.original = request; + this.request = request; + } + getCookieValue = (key: string) => { + return getCookieValueFromIncomingMessage(this.request, key); + }; + getFormData = async (): Promise => { + return useRawBody(this.request); + }; + getMethod = () => { + return normaliseHttpMethod(this.request.method!); + }; + getHeaderValue = (key: string) => { + return getHeaderValueFromIncomingMessage(this.request, key); + }; + getOriginalURL = () => { + return this.request.url!; + }; + getKeyValueFromQuery = (key: string) => { + let path = this.request.url || "/"; + const queryIndex = path.lastIndexOf("?"); + if (queryIndex > -1) { + const queryArray = path.substring(queryIndex + 1, path.length).split("&"); + const index = queryArray.findIndex((el) => el.includes(key)); + if (index === -1) return undefined; + const value = queryArray[index].split("=")[1]; + if (value === undefined || typeof value !== "string") { + return undefined; + } + return value; + } else { + return undefined; + } + }; + getJSONBody = async () => { + return await useBody(this.request); + }; +} + +export class H3ResponseTokens extends BaseResponse { + private response: ServerResponse; + private statusCode: number; + constructor(response: ServerResponse) { + super(); + this.original = response; + this.response = response; + this.statusCode = 200; + } + + sendHTMLResponse = (html: string) => { + if (this.response.writable) { + this.response.setHeader("Content-Type", "text/html"); + this.response.statusCode = this.statusCode; + this.response.end(html); + } + }; + setHeader = (key: string, value: string, allowDuplicateKey: boolean) => { + try { + const allheaders = this.response.getHeaders(); + let existingValue = allheaders[key]; + + // we have the this.response.header for compatibility with nextJS + if (existingValue === undefined) { + this.response.setHeader(key, value); + } else if (allowDuplicateKey) { + this.response.setHeader(key, existingValue + ", " + value); + } else { + // we overwrite the current one with the new one + this.response.setHeader(key, value); + } + } catch (err) { + throw new Error("Error while setting header with key: " + key + " and value: " + value); + } + }; + setCookie = ( + key: string, + value: string, + domain: string | undefined, + secure: boolean, + httpOnly: boolean, + expires: number, + path: string, + sameSite: "strict" | "lax" | "none" + ) => { + setCookieForServerResponse(this.response, key, value, domain, secure, httpOnly, expires, path, sameSite); + }; + setStatusCode = (statusCode: number) => { + if (this.response.writable) { + this.statusCode = statusCode; + } + }; + sendJSONResponse = (content: any) => { + if (this.response.writable) { + content = JSON.stringify(content); + this.response.setHeader("Content-Type", "application/json"); + this.response.statusCode = this.statusCode; + this.response.end(content, "utf-8"); + } + }; +} +export interface SessionRequest extends IncomingMessage { + session?: SessionContainerInterface; +} + +export const middlware = () => { + return async (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => any) => { + let supertokens; + const request = new H3Request(req); + const response = new H3ResponseTokens(res); + try { + supertokens = SuperTokens.getInstanceOrThrowError(); + const result = await supertokens.middleware(request, response); + 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 (event: H3Event, errorPlain: Error, statusCode: number) => { + const error = createError(errorPlain); + error.statusCode = statusCode; + sendError(event, error); + }; +}; + +export interface H3Framework extends Framework { + middlware: () => (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => any) => Promise; + errorHandler: () => (event: H3Event, errorPlain: Error, statusCode: number) => Promise; +} + +export const H3Wrapper: H3Framework = { + middlware, + errorHandler, + wrapRequest: (unwrapped) => { + return new H3Request(unwrapped.req); + }, + wrapResponse: (unwrapped) => { + return new H3ResponseTokens(unwrapped.res); + }, +}; diff --git a/lib/ts/framework/h3/index.ts b/lib/ts/framework/h3/index.ts new file mode 100644 index 000000000..5dcc5a938 --- /dev/null +++ b/lib/ts/framework/h3/index.ts @@ -0,0 +1,7 @@ +import { H3Wrapper } from "./framework"; +export type { SessionRequest } from "./framework"; + +export const middleware = H3Wrapper.middlware; +export const errorHandler = H3Wrapper.errorHandler; +export const wrapRequest = H3Wrapper.wrapRequest; +export const wrapResponse = H3Wrapper.wrapResponse; diff --git a/lib/ts/framework/index.ts b/lib/ts/framework/index.ts index 3d38c264a..e83df0391 100644 --- a/lib/ts/framework/index.ts +++ b/lib/ts/framework/index.ts @@ -21,6 +21,7 @@ import * as hapiFramework from "./hapi"; import * as loopbackFramework from "./loopback"; import * as koaFramework from "./koa"; import * as awsLambdaFramework from "./awsLambda"; +import * as h3Framework from "./h3"; export default { express: expressFramework, @@ -29,6 +30,7 @@ export default { loopback: loopbackFramework, koa: koaFramework, awsLambda: awsLambdaFramework, + h3: h3Framework, }; export let express = expressFramework; @@ -37,3 +39,4 @@ export let hapi = hapiFramework; export let loopback = loopbackFramework; export let koa = koaFramework; export let awsLambda = awsLambdaFramework; +export let h3 = h3Framework; diff --git a/lib/ts/framework/types.ts b/lib/ts/framework/types.ts index 5707c65d5..505adc5e4 100644 --- a/lib/ts/framework/types.ts +++ b/lib/ts/framework/types.ts @@ -12,12 +12,12 @@ * License for the specific language governing permissions and limitations * under the License. */ -export type TypeFramework = "express" | "fastify" | "hapi" | "loopback" | "koa" | "awsLambda"; +export type TypeFramework = "express" | "fastify" | "hapi" | "loopback" | "koa" | "awsLambda" | "h3"; import { BaseRequest, BaseResponse } from "."; export let SchemaFramework = { type: "string", - enum: ["express", "fastify", "hapi", "loopback", "koa", "awsLambda"], + enum: ["express", "fastify", "hapi", "loopback", "koa", "awsLambda", "h3"], }; export interface Framework { diff --git a/lib/ts/framework/utils.ts b/lib/ts/framework/utils.ts index c7ad29993..39e7eaf07 100644 --- a/lib/ts/framework/utils.ts +++ b/lib/ts/framework/utils.ts @@ -22,6 +22,7 @@ import STError from "../error"; import type { HTTPMethod } from "../types"; import { NextApiRequest } from "next"; import { COOKIE_HEADER } from "./constants"; +import destr from "destr"; export function getCookieValueFromHeaders(headers: any, key: string): string | undefined { if (headers === undefined || headers === null) { @@ -223,6 +224,42 @@ export function setHeaderForExpressLikeResponse(res: Response, key: string, valu } } +export function useRawBody(req: IncomingMessage): Promise { + const RawBodySymbol = Symbol("h3RawBody"); + if (RawBodySymbol in this.request) { + const promise = Promise.resolve((req as any)[RawBodySymbol]); + return promise.then((buff) => buff.toString("utf-8")); + } + if ("body" in req) { + return Promise.resolve((this.request as any).body); + } + + const promise = ((req as any)[RawBodySymbol] = new Promise((resolve, reject) => { + const bodyData: any[] = []; + req.on("error", (err) => reject(err)) + .on("data", (chunk) => { + bodyData.push(chunk); + }) + .on("end", () => { + resolve(Buffer.concat(bodyData)); + }); + })); + + return promise.then((buff) => buff.toString("utf-8")); +} + +export async function useBody(req: IncomingMessage): Promise { + const ParsedBodySymbol = Symbol("h3RawBody"); + if (ParsedBodySymbol in req) { + return (req as any)[ParsedBodySymbol]; + } + + const body = (await useRawBody(req)) as string; + + const json = destr(body); + (req as any)[ParsedBodySymbol] = json; + return json; +} /** * * @param res diff --git a/lib/ts/recipe/session/framework/h3.ts b/lib/ts/recipe/session/framework/h3.ts new file mode 100644 index 000000000..9431f0015 --- /dev/null +++ b/lib/ts/recipe/session/framework/h3.ts @@ -0,0 +1,25 @@ +import Session from "../recipe"; +import type { VerifySessionOptions } from "../types"; +import { H3Request, H3ResponseTokens } from "../../../framework/h3/framework"; +import type { SessionRequest } from "../../../framework/h3"; +import SuperTokens from "../../../supertokens"; +import { ServerResponse } from "http"; + +export function verifySession(options?: VerifySessionOptions) { + return async (req: SessionRequest, res: ServerResponse, next: (err?: Error) => any) => { + const request = new H3Request(req); + const response = new H3ResponseTokens(res); + try { + const sessionRecipe = Session.getInstanceOrThrowError(); + req.session = await sessionRecipe.verifySession(options, request, response); + next(); + } catch (err) { + try { + const supertokens = SuperTokens.getInstanceOrThrowError(); + await supertokens.errorHandler(err, request, response); + } catch (err) { + next(err); + } + } + }; +} diff --git a/lib/ts/recipe/session/framework/index.ts b/lib/ts/recipe/session/framework/index.ts index c984f7879..5cd93c271 100644 --- a/lib/ts/recipe/session/framework/index.ts +++ b/lib/ts/recipe/session/framework/index.ts @@ -18,6 +18,7 @@ import * as hapiFramework from "./hapi"; import * as loopbackFramework from "./loopback"; import * as koaFramework from "./koa"; import * as awsLambdaFramework from "./awsLambda"; +import * as h3Framework from "./h3"; export default { express: expressFramework, @@ -26,6 +27,7 @@ export default { loopback: loopbackFramework, koa: koaFramework, awsLambda: awsLambdaFramework, + h3: h3Framework, }; export let express = expressFramework; @@ -34,3 +36,4 @@ export let hapi = hapiFramework; export let loopback = loopbackFramework; export let koa = koaFramework; export let awsLambda = awsLambdaFramework; +export let h3 = h3Framework; diff --git a/package-lock.json b/package-lock.json index 13924b03d..ea5cde0ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "co-body": "6.1.0", "cookie": "0.4.0", "debug": "^4.3.3", + "destr": "^1.1.1", "jsonwebtoken": "^8.5.1", "jwks-rsa": "^2.0.5", "libphonenumber-js": "^1.9.44", @@ -45,6 +46,7 @@ "express": "4.17.1", "fastify": "3.18.1", "glob": "7.1.7", + "h3": "^0.7.6", "koa": "^2.13.3", "lambda-tester": "^4.0.1", "loopback-datasource-juggler": "^4.26.0", @@ -2878,6 +2880,11 @@ "node": ">= 0.6" } }, + "node_modules/cookie-es": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-0.5.0.tgz", + "integrity": "sha512-RyZrFi6PNpBFbIaQjXDlFIhFVqV42QeKSZX1yQIl6ihImq6vcHNGMtqQ/QzY3RMPuYSkvsRwtnt5M9NeYxKt0g==" + }, "node_modules/cookie-parser": { "version": "1.4.6", "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", @@ -3202,6 +3209,11 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/destr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/destr/-/destr-1.1.1.tgz", + "integrity": "sha512-QqkneF8LrYmwATMdnuD2MLI3GHQIcBnG6qFC2q9bSH430VTCDAVjcspPmUaKhPGtAtPAftIUFqY1obQYQuwmbg==" + }, "node_modules/destroy": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.1.0.tgz", @@ -4102,6 +4114,17 @@ "node": ">=4.x" } }, + "node_modules/h3": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/h3/-/h3-0.7.6.tgz", + "integrity": "sha512-OoxDWBBpGNAStSVCOQagN+3EKRbCSgwQKFIeu4p4XvsLvd4L9KaQV6GDY5H8ZDYHsWfnsx4KTa2eFwSw530jnQ==", + "dependencies": { + "cookie-es": "^0.5.0", + "destr": "^1.1.1", + "radix3": "^0.1.1", + "ufo": "^0.8.3" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -7199,6 +7222,11 @@ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", "dev": true }, + "node_modules/radix3": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-0.1.1.tgz", + "integrity": "sha512-9Np01fn+penHvC05A9EkRpyObPMS0ht3t1UP6KlnQPCfTNzArmEZW/+t2SLsDtPcZyXPDbYCGWA8dSQqWaVwQQ==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -8473,6 +8501,11 @@ "node": ">=4.2.0" } }, + "node_modules/ufo": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-0.8.3.tgz", + "integrity": "sha512-AIkk06G21y/P+NCatfU+1qldCmI0XCszZLn8AkuKotffF3eqCvlce0KuwM7ZemLE/my0GSYADOAeM5zDYWMB+A==" + }, "node_modules/unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -11754,6 +11787,11 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" }, + "cookie-es": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-0.5.0.tgz", + "integrity": "sha512-RyZrFi6PNpBFbIaQjXDlFIhFVqV42QeKSZX1yQIl6ihImq6vcHNGMtqQ/QzY3RMPuYSkvsRwtnt5M9NeYxKt0g==" + }, "cookie-parser": { "version": "1.4.6", "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", @@ -12016,6 +12054,11 @@ "minimalistic-assert": "^1.0.0" } }, + "destr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/destr/-/destr-1.1.1.tgz", + "integrity": "sha512-QqkneF8LrYmwATMdnuD2MLI3GHQIcBnG6qFC2q9bSH430VTCDAVjcspPmUaKhPGtAtPAftIUFqY1obQYQuwmbg==" + }, "destroy": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.1.0.tgz", @@ -12732,6 +12775,17 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "h3": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/h3/-/h3-0.7.6.tgz", + "integrity": "sha512-OoxDWBBpGNAStSVCOQagN+3EKRbCSgwQKFIeu4p4XvsLvd4L9KaQV6GDY5H8ZDYHsWfnsx4KTa2eFwSw530jnQ==", + "requires": { + "cookie-es": "^0.5.0", + "destr": "^1.1.1", + "radix3": "^0.1.1", + "ufo": "^0.8.3" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -15193,6 +15247,11 @@ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", "dev": true }, + "radix3": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-0.1.1.tgz", + "integrity": "sha512-9Np01fn+penHvC05A9EkRpyObPMS0ht3t1UP6KlnQPCfTNzArmEZW/+t2SLsDtPcZyXPDbYCGWA8dSQqWaVwQQ==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -16253,6 +16312,11 @@ "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", "dev": true }, + "ufo": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-0.8.3.tgz", + "integrity": "sha512-AIkk06G21y/P+NCatfU+1qldCmI0XCszZLn8AkuKotffF3eqCvlce0KuwM7ZemLE/my0GSYADOAeM5zDYWMB+A==" + }, "unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", diff --git a/package.json b/package.json index ada63e1ff..a712de14e 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "co-body": "6.1.0", "cookie": "0.4.0", "debug": "^4.3.3", + "destr": "^1.1.1", "jsonwebtoken": "^8.5.1", "jwks-rsa": "^2.0.5", "libphonenumber-js": "^1.9.44", @@ -49,6 +50,7 @@ "verify-apple-id-token": "^2.1.0" }, "devDependencies": { + "h3": "^0.7.6", "@hapi/hapi": "^20.2.0", "@koa/router": "^10.1.1", "@loopback/core": "2.16.2", diff --git a/recipe/session/framework/h3/index.d.ts b/recipe/session/framework/h3/index.d.ts new file mode 100644 index 000000000..b76888d1e --- /dev/null +++ b/recipe/session/framework/h3/index.d.ts @@ -0,0 +1,3 @@ +export * from "../../../../lib/build/recipe/session/framework/h3"; +import * as _default from "../../../../lib/build/recipe/session/framework/h3"; +export default _default; diff --git a/recipe/session/framework/h3/index.js b/recipe/session/framework/h3/index.js new file mode 100644 index 000000000..06fb4c8b7 --- /dev/null +++ b/recipe/session/framework/h3/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/recipe/session/framework/h3"));