From 5fadb1f6c2526f04087ceaa0a408f53e6f58592d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivo=20Ilic=CC=81?= Date: Sun, 19 Feb 2023 13:40:47 -0500 Subject: [PATCH 1/3] Added the authjs-server middleware package --- .gitignore | 3 +- package.json | 1 + packages/authjs-server/.env.example | 3 + packages/authjs-server/CHANGELOG.md | 1 + packages/authjs-server/README.md | 45 +++++++++++++++ packages/authjs-server/jest.config.js | 1 + packages/authjs-server/package.json | 41 ++++++++++++++ packages/authjs-server/src/index.ts | 67 +++++++++++++++++++++++ packages/authjs-server/test/index.test.ts | 26 +++++++++ packages/authjs-server/tsconfig.cjs.json | 8 +++ packages/authjs-server/tsconfig.esm.json | 8 +++ packages/authjs-server/tsconfig.json | 9 +++ packages/firebase-auth/package.json | 4 +- packages/graphql-server/package.json | 2 +- packages/hello/package.json | 2 +- packages/qwik-city/package.json | 2 +- packages/sentry/package.json | 4 +- packages/zod-validator/package.json | 2 +- yarn.lock | 44 +++++++++++++++ 19 files changed, 264 insertions(+), 9 deletions(-) create mode 100644 packages/authjs-server/.env.example create mode 100644 packages/authjs-server/CHANGELOG.md create mode 100644 packages/authjs-server/README.md create mode 100644 packages/authjs-server/jest.config.js create mode 100644 packages/authjs-server/package.json create mode 100644 packages/authjs-server/src/index.ts create mode 100644 packages/authjs-server/test/index.test.ts create mode 100644 packages/authjs-server/tsconfig.cjs.json create mode 100644 packages/authjs-server/tsconfig.esm.json create mode 100644 packages/authjs-server/tsconfig.json diff --git a/.gitignore b/.gitignore index 54c755069..47fbb6e88 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ yarn-error.log *.tgz # for debug or playing -sandbox \ No newline at end of file +sandbox +.env \ No newline at end of file diff --git a/package.json b/package.json index 9bc0cfb61..a597677c0 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "build:sentry": "yarn workspace @hono/sentry build", "build:firebase-auth": "yarn workspace @hono/firebase-auth build", "build:trpc-server": "yarn workspace @hono/trpc-server build", + "build:authjs-server": "yarn workspace @hono/authjs-server build", "build": "run-p build:*" }, "license": "MIT", diff --git a/packages/authjs-server/.env.example b/packages/authjs-server/.env.example new file mode 100644 index 000000000..9678ed1ed --- /dev/null +++ b/packages/authjs-server/.env.example @@ -0,0 +1,3 @@ +AUTH_SECRET= +GITHUB_ID= +GITHUB_SECRET= \ No newline at end of file diff --git a/packages/authjs-server/CHANGELOG.md b/packages/authjs-server/CHANGELOG.md new file mode 100644 index 000000000..97b5b17e6 --- /dev/null +++ b/packages/authjs-server/CHANGELOG.md @@ -0,0 +1 @@ +# @honojs/auth-js diff --git a/packages/authjs-server/README.md b/packages/authjs-server/README.md new file mode 100644 index 000000000..a0ffc828c --- /dev/null +++ b/packages/authjs-server/README.md @@ -0,0 +1,45 @@ +# Auth.js Server Middleware for Hono + +Auth.js Server Middleware adapts [Auth.js](https://authjs.dev/) server as middleware for Hono. + +## Usage + +```ts +import { authjsServer } from '@hono/auth-js-server' +import { Hono } from 'hono' + +const app = new Hono() + +const authOpts: HonoAuthConfig = { + providers: [ + //@ts-expect-error issue https://github.com/nextauthjs/next-auth/issues/6174 + GitHub({ + clientId: process.env.GITHUB_ID as string, + clientSecret: process.env.GITHUB_SECRET as string, + }), + ], + debug: true, +} + +app.use('/auth/*', authjsServer(authOpts)) + +export default app +``` + +## Testing + +Copy the example .env file and populate it with credentials from GitHub + +``` +cp .env.examaple .env +``` + +## Author + +Ivo Ilić + +Based on the [Auth.js frameworks-solid-start package](https://github.com/nextauthjs/next-auth/tree/main/packages/frameworks-solid-start) by [OrJDev](https://github.com/OrJDev) + +## License + +MIT diff --git a/packages/authjs-server/jest.config.js b/packages/authjs-server/jest.config.js new file mode 100644 index 000000000..f697d8316 --- /dev/null +++ b/packages/authjs-server/jest.config.js @@ -0,0 +1 @@ +module.exports = require('../../jest.config.js') diff --git a/packages/authjs-server/package.json b/packages/authjs-server/package.json new file mode 100644 index 000000000..7985bcb3e --- /dev/null +++ b/packages/authjs-server/package.json @@ -0,0 +1,41 @@ +{ + "name": "@hono/authjs-server", + "version": "0.0.1", + "description": "Auth.js Server Middleware for Hono", + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "types": "dist/esm/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "test": "jest", + "build:cjs": "tsc -p tsconfig.cjs.json", + "build:esm": "tsc -p tsconfig.esm.json", + "build": "rimraf dist && yarn build:cjs && yarn build:esm", + "prerelease": "yarn build && yarn test", + "release": "yarn publish" + }, + "license": "MIT", + "private": false, + "publishConfig": { + "registry": "https://registry.npmjs.org", + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/honojs/middleware.git" + }, + "homepage": "https://github.com/honojs/middleware", + "peerDependencies": { + "@auth/core": "^0.4.0", + "hono": "3.*" + }, + "devDependencies": { + "@auth/core": "^0.4.0", + "hono": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0" + } +} diff --git a/packages/authjs-server/src/index.ts b/packages/authjs-server/src/index.ts new file mode 100644 index 000000000..f206e2c42 --- /dev/null +++ b/packages/authjs-server/src/index.ts @@ -0,0 +1,67 @@ +import { Auth } from '@auth/core' +import type { AuthAction, AuthConfig, Session } from '@auth/core/types' +import type { Context, MiddlewareHandler } from 'hono' + +export interface HonoAuthConfig extends AuthConfig { + /** + * Defines the base path for the auth routes. + * @default '/auth' + */ + prefix?: string +} + +const actions: AuthAction[] = [ + 'providers', + 'session', + 'csrf', + 'signin', + 'signout', + 'callback', + 'verify-request', + 'error', +] + +function HonoAuthHandler(prefix: string, authOptions: HonoAuthConfig) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + return async (c: Context) => { + const { req } = c + const url = new URL(req.url) + const action = url.pathname.slice(prefix.length + 1).split('/')[0] as AuthAction + + if (!actions.includes(action) || !url.pathname.startsWith(prefix + '/')) { + return + } + + return await Auth(req.raw, authOptions) + } +} + +export const authjsServer = (config: HonoAuthConfig): MiddlewareHandler => { + const { prefix = '/auth', ...authOptions } = config + authOptions.secret ??= process.env.AUTH_SECRET + authOptions.trustHost ??= !!( + process.env.AUTH_TRUST_HOST ?? + process.env.VERCEL ?? + process.env.NODE_ENV !== 'production' + ) + return HonoAuthHandler(prefix, authOptions) +} + +export type GetSessionResult = Promise + +export async function getSession(req: Request, options: AuthConfig): GetSessionResult { + options.secret ??= process.env.AUTH_SECRET + options.trustHost ??= true + + const url = new URL('/auth/session', req.url) + const response = await Auth(new Request(url, { headers: req.headers }), options) + + const { status = 200 } = response + + const data = await response.json() + + if (!data || !Object.keys(data).length) return null + if (status === 200) return data as Session + const error = data as { message?: string } + throw new Error(error?.message) +} diff --git a/packages/authjs-server/test/index.test.ts b/packages/authjs-server/test/index.test.ts new file mode 100644 index 000000000..925049911 --- /dev/null +++ b/packages/authjs-server/test/index.test.ts @@ -0,0 +1,26 @@ +import GitHub from '@auth/core/providers/github' +import { Hono } from 'hono' +import { authjsServer, type HonoAuthConfig } from '../src' + +describe('Auth.js Adapter Middleware', () => { + const app = new Hono() + + const authOpts: HonoAuthConfig = { + providers: [ + //@ts-expect-error issue https://github.com/nextauthjs/next-auth/issues/6174 + GitHub({ + clientId: process.env.GITHUB_ID as string, + clientSecret: process.env.GITHUB_SECRET as string, + }), + ], + debug: true, + } + + app.use('/auth/*', authjsServer(authOpts)) + + it('Should return 200 response', async () => { + const req = new Request('http://localhost/auth/error') + const res = await app.request(req) + expect(res.status).toBe(200) + }) +}) diff --git a/packages/authjs-server/tsconfig.cjs.json b/packages/authjs-server/tsconfig.cjs.json new file mode 100644 index 000000000..b8bf50ee9 --- /dev/null +++ b/packages/authjs-server/tsconfig.cjs.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS", + "declaration": false, + "outDir": "./dist/cjs" + } +} \ No newline at end of file diff --git a/packages/authjs-server/tsconfig.esm.json b/packages/authjs-server/tsconfig.esm.json new file mode 100644 index 000000000..8130f1a53 --- /dev/null +++ b/packages/authjs-server/tsconfig.esm.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "declaration": true, + "outDir": "./dist/esm" + } +} \ No newline at end of file diff --git a/packages/authjs-server/tsconfig.json b/packages/authjs-server/tsconfig.json new file mode 100644 index 000000000..6c1a39902 --- /dev/null +++ b/packages/authjs-server/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + }, + "include": [ + "src/**/*.ts" + ], +} \ No newline at end of file diff --git a/packages/firebase-auth/package.json b/packages/firebase-auth/package.json index be4ac955b..675f0f4d6 100644 --- a/packages/firebase-auth/package.json +++ b/packages/firebase-auth/package.json @@ -34,14 +34,14 @@ "hono": "^2.7.2" }, "devDependencies": { - "hono": "^2.7.2", "@cloudflare/workers-types": "^3.14.1", "@types/jest": "^28.1.4", "firebase-tools": "^11.4.0", + "hono": "^2.7.2", "jest": "^28.1.2", "jest-environment-miniflare": "^2.6.0", "prettier": "^2.7.1", "ts-jest": "^28.0.5", "typescript": "^4.7.4" } -} \ No newline at end of file +} diff --git a/packages/graphql-server/package.json b/packages/graphql-server/package.json index 94328b48b..d07760585 100644 --- a/packages/graphql-server/package.json +++ b/packages/graphql-server/package.json @@ -58,4 +58,4 @@ "engines": { "node": ">=16.0.0" } -} \ No newline at end of file +} diff --git a/packages/hello/package.json b/packages/hello/package.json index bad23964b..7f34f42ca 100644 --- a/packages/hello/package.json +++ b/packages/hello/package.json @@ -30,4 +30,4 @@ "devDependencies": { "hono": "^2.7.2" } -} \ No newline at end of file +} diff --git a/packages/qwik-city/package.json b/packages/qwik-city/package.json index 538da310b..a7c26550b 100644 --- a/packages/qwik-city/package.json +++ b/packages/qwik-city/package.json @@ -37,4 +37,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/packages/sentry/package.json b/packages/sentry/package.json index 2399b26b8..2b04ccd30 100644 --- a/packages/sentry/package.json +++ b/packages/sentry/package.json @@ -21,13 +21,13 @@ "replacer": "dist/replacer.js" }, "license": "MIT", - "private": false, "repository": { "type": "git", "url": "https://github.com/honojs/middleware.git" }, "homepage": "https://github.com/honojs/middleware", "author": "Samuel Lippert (https://github.com/sam-lippert)", + "private": false, "publishConfig": { "registry": "https://registry.npmjs.org", "access": "public" @@ -61,4 +61,4 @@ "ts-jest": "^28.0.5", "typescript": "^4.7.4" } -} \ No newline at end of file +} diff --git a/packages/zod-validator/package.json b/packages/zod-validator/package.json index 0ca603667..40adf9e11 100644 --- a/packages/zod-validator/package.json +++ b/packages/zod-validator/package.json @@ -35,4 +35,4 @@ "hono": "^3.0.0", "zod": "3.19.1" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 794c2c746..d8be1e1fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20,6 +20,18 @@ call-me-maybe "^1.0.1" js-yaml "^4.1.0" +"@auth/core@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.4.0.tgz#271e763cab2ba7f75760a601e0a62ee033e888b7" + integrity sha512-wHVljvVGPmKSjlxQUYZCOL4GGe26YqhT351sA2REE43YskaqRMYh1TVjBxpXcdG8/P8bw2DsHgl+aBEV3P5+KQ== + dependencies: + "@panva/hkdf" "^1.0.2" + cookie "0.5.0" + jose "^4.11.1" + oauth4webapi "^2.0.6" + preact "10.11.3" + preact-render-to-string "5.2.3" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" @@ -1341,6 +1353,11 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.3.1.tgz#ba07b864a3c955f061aa30ea3ef7f4ae4449794a" integrity sha512-wU5J8rUoo32oSef/rFpOT1HIjLjAv3qIDHkw1QIhODV3OpAVHi5oVzlouozg9obUmZKtbZ0qUe/m7FP0y0yBzA== +"@panva/hkdf@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.0.4.tgz#4e02bb248402ff6c5c024e23a68438e2b0e69d67" + integrity sha512-003xWiCuvePbLaPHT+CRuaV4GlyCAVm6XYSbBZDHoWZGn1mNkVKFaDbGJjjxmEFvizUwlCoM6O18FCBMMky2zQ== + "@pkgr/utils@^2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03" @@ -6414,6 +6431,11 @@ join-path@^1.1.1: url-join "0.0.1" valid-url "^1" +jose@^4.11.1: + version "4.12.0" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.12.0.tgz#7f00cd2f82499b91623cd413b7b5287fd52651ed" + integrity sha512-wW1u3cK81b+SFcHjGC8zw87yuyUweEFe0UJirrXEw1NasW00eF7sZjeG3SLBGz001ozxQ46Y9sofDvhBmWFtXQ== + js-sdsl@^4.1.4: version "4.2.0" resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.2.0.tgz#278e98b7bea589b8baaf048c20aeb19eb7ad09d0" @@ -8004,6 +8026,11 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +oauth4webapi@^2.0.6: + version "2.1.0" + resolved "https://registry.yarnpkg.com/oauth4webapi/-/oauth4webapi-2.1.0.tgz#1f52c5ed1a8b2fe4743be86581654428905dd932" + integrity sha512-0YjXQA3viCby/So8rja4sBrdLMTTyzuQTzotMTT6uAgqmqanrpt8eBfLLaugUZaMZSzt3IdjJdGadr5YTC+Cww== + object-assign@^4, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -8532,6 +8559,18 @@ portfinder@^1.0.32: debug "^3.2.7" mkdirp "^0.5.6" +preact-render-to-string@5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz#23d17376182af720b1060d5a4099843c7fe92fe4" + integrity sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA== + dependencies: + pretty-format "^3.8.0" + +preact@10.11.3: + version "10.11.3" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.11.3.tgz#8a7e4ba19d3992c488b0785afcc0f8aa13c78d19" + integrity sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg== + preferred-pm@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/preferred-pm/-/preferred-pm-3.0.3.tgz#1b6338000371e3edbce52ef2e4f65eb2e73586d6" @@ -8581,6 +8620,11 @@ pretty-format@^29.0.0, pretty-format@^29.3.1: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385" + integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" From 407266b02e29090b27875aa79aa65ffbde0b2edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivo=20Ilic=CC=81?= Date: Sun, 19 Feb 2023 22:18:48 -0500 Subject: [PATCH 2/3] Removed session function, and environment variables. Updated test --- packages/authjs-server/src/index.ts | 25 -------------------- packages/authjs-server/test/index.test.ts | 28 +++++++++++++---------- 2 files changed, 16 insertions(+), 37 deletions(-) diff --git a/packages/authjs-server/src/index.ts b/packages/authjs-server/src/index.ts index f206e2c42..3e364abaf 100644 --- a/packages/authjs-server/src/index.ts +++ b/packages/authjs-server/src/index.ts @@ -38,30 +38,5 @@ function HonoAuthHandler(prefix: string, authOptions: HonoAuthConfig) { export const authjsServer = (config: HonoAuthConfig): MiddlewareHandler => { const { prefix = '/auth', ...authOptions } = config - authOptions.secret ??= process.env.AUTH_SECRET - authOptions.trustHost ??= !!( - process.env.AUTH_TRUST_HOST ?? - process.env.VERCEL ?? - process.env.NODE_ENV !== 'production' - ) return HonoAuthHandler(prefix, authOptions) } - -export type GetSessionResult = Promise - -export async function getSession(req: Request, options: AuthConfig): GetSessionResult { - options.secret ??= process.env.AUTH_SECRET - options.trustHost ??= true - - const url = new URL('/auth/session', req.url) - const response = await Auth(new Request(url, { headers: req.headers }), options) - - const { status = 200 } = response - - const data = await response.json() - - if (!data || !Object.keys(data).length) return null - if (status === 200) return data as Session - const error = data as { message?: string } - throw new Error(error?.message) -} diff --git a/packages/authjs-server/test/index.test.ts b/packages/authjs-server/test/index.test.ts index 925049911..030ee1da5 100644 --- a/packages/authjs-server/test/index.test.ts +++ b/packages/authjs-server/test/index.test.ts @@ -5,18 +5,22 @@ import { authjsServer, type HonoAuthConfig } from '../src' describe('Auth.js Adapter Middleware', () => { const app = new Hono() - const authOpts: HonoAuthConfig = { - providers: [ - //@ts-expect-error issue https://github.com/nextauthjs/next-auth/issues/6174 - GitHub({ - clientId: process.env.GITHUB_ID as string, - clientSecret: process.env.GITHUB_SECRET as string, - }), - ], - debug: true, - } - - app.use('/auth/*', authjsServer(authOpts)) + app.use('/auth/*', (c, next) => { + const authOpts: HonoAuthConfig = { + secret: process.env.AUTH_SECRET as string, + trustHost: true, + providers: [ + //@ts-expect-error issue https://github.com/nextauthjs/next-auth/issues/6174 + GitHub({ + clientId: process.env.GITHUB_ID as string, + clientSecret: process.env.GITHUB_SECRET as string, + }), + ], + debug: true, + } + const auth = authjsServer(authOpts) + return auth(c, next) + }) it('Should return 200 response', async () => { const req = new Request('http://localhost/auth/error') From 953cc94bab4001c6a1f8bf73d594d0f2cc686e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivo=20Ilic=CC=81?= Date: Sat, 11 Mar 2023 15:00:08 -0500 Subject: [PATCH 3/3] Update the tests to resolve the ESM issues --- packages/authjs-server/jest.config.js | 10 +++++++++- packages/authjs-server/package.json | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/authjs-server/jest.config.js b/packages/authjs-server/jest.config.js index f697d8316..27898c2e0 100644 --- a/packages/authjs-server/jest.config.js +++ b/packages/authjs-server/jest.config.js @@ -1 +1,9 @@ -module.exports = require('../../jest.config.js') +const baseConfig = require('../../jest.config.js') + +module.exports = { + ...baseConfig, + transform: { + '^.+\\.(ts|tsx)$': ['ts-jest', { useESM: true }], + }, + extensionsToTreatAsEsm: ['.ts'], +} diff --git a/packages/authjs-server/package.json b/packages/authjs-server/package.json index 7985bcb3e..6eb198976 100644 --- a/packages/authjs-server/package.json +++ b/packages/authjs-server/package.json @@ -9,7 +9,7 @@ "dist" ], "scripts": { - "test": "jest", + "test": "NODE_OPTIONS=--experimental-vm-modules jest", "build:cjs": "tsc -p tsconfig.cjs.json", "build:esm": "tsc -p tsconfig.esm.json", "build": "rimraf dist && yarn build:cjs && yarn build:esm", @@ -28,11 +28,11 @@ }, "homepage": "https://github.com/honojs/middleware", "peerDependencies": { - "@auth/core": "^0.4.0", + "@auth/core": "^0.5.1", "hono": "3.*" }, "devDependencies": { - "@auth/core": "^0.4.0", + "@auth/core": "^0.5.1", "hono": "^3.0.0" }, "engines": {