From 18959557f45851a0109a63de3e865329c30d4fcc Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Tue, 30 Apr 2024 17:25:05 +0900 Subject: [PATCH] fix(auth-js): use `env` in `hono/adapter` and add tests (#486) * fix(auth-js): use `env` in `hono/adapter` and add tests * add changeset * polyfill crypto --- .changeset/silver-students-listen.md | 5 ++ packages/auth-js/package.json | 2 +- packages/auth-js/src/index.ts | 15 ++-- packages/auth-js/test/index.test.ts | 121 +++++++++++++++++++++++++-- yarn.lock | 16 ++-- 5 files changed, 138 insertions(+), 21 deletions(-) create mode 100644 .changeset/silver-students-listen.md diff --git a/.changeset/silver-students-listen.md b/.changeset/silver-students-listen.md new file mode 100644 index 00000000..a551ad98 --- /dev/null +++ b/.changeset/silver-students-listen.md @@ -0,0 +1,5 @@ +--- +'@hono/auth-js': patch +--- + +fix: use `env` in `hono/adapter` and add tests diff --git a/packages/auth-js/package.json b/packages/auth-js/package.json index b44a81f2..c1a6a7b2 100644 --- a/packages/auth-js/package.json +++ b/packages/auth-js/package.json @@ -57,7 +57,7 @@ "react": ">=18" }, "devDependencies": { - "@auth/core": "^0.24.0", + "@auth/core": "^0.30.0", "@types/react": "^18", "hono": "^3.11.7", "jest": "^29.7.0", diff --git a/packages/auth-js/src/index.ts b/packages/auth-js/src/index.ts index 5fb49b09..9f4480f4 100644 --- a/packages/auth-js/src/index.ts +++ b/packages/auth-js/src/index.ts @@ -4,6 +4,7 @@ import type { AdapterUser } from '@auth/core/adapters' import type { JWT } from '@auth/core/jwt' import type { Session } from '@auth/core/types' import type { Context, MiddlewareHandler } from 'hono' +import { env } from 'hono/adapter' import { HTTPException } from 'hono/http-exception' declare module 'hono' { @@ -34,10 +35,10 @@ export function reqWithEnvUrl(req: Request, authUrl?: string): Request { const reqUrlObj = new URL(req.url) const authUrlObj = new URL(authUrl) const props = ['hostname', 'protocol', 'port', 'password', 'username'] as const - props.forEach(prop => reqUrlObj[prop] = authUrlObj[prop]) - return new Request(reqUrlObj.href, req); + props.forEach((prop) => (reqUrlObj[prop] = authUrlObj[prop])) + return new Request(reqUrlObj.href, req) } else { - return req; + return req } } @@ -62,8 +63,8 @@ function setEnvDefaults(env: AuthEnv, config: AuthConfig) { export async function getAuthUser(c: Context): Promise { const config = c.get('authConfig') - setEnvDefaults(c.env, config) - const origin = c.env.AUTH_URL ? new URL(c.env.AUTH_URL).origin : new URL(c.req.url).origin + setEnvDefaults(env(c), config) + const origin = env(c)['AUTH_URL'] ? new URL(env(c)['AUTH_URL']).origin : new URL(c.req.url).origin const request = new Request(`${origin}${config.basePath}/session`, { headers: { cookie: c.req.header('cookie') ?? '' }, }) @@ -117,13 +118,13 @@ export function authHandler(): MiddlewareHandler { return async (c) => { const config = c.get('authConfig') - setEnvDefaults(c.env, config) + setEnvDefaults(env(c), config) if (!config.secret) { throw new HTTPException(500, { message: 'Missing AUTH_SECRET' }) } - const res = await Auth(reqWithEnvUrl(c.req.raw, c.env.AUTH_URL), config) + const res = await Auth(reqWithEnvUrl(c.req.raw, env(c)['AUTH_URL']), config) return new Response(res.body, res) } } diff --git a/packages/auth-js/test/index.test.ts b/packages/auth-js/test/index.test.ts index 5218cd25..5fab774a 100644 --- a/packages/auth-js/test/index.test.ts +++ b/packages/auth-js/test/index.test.ts @@ -1,14 +1,19 @@ import { webcrypto } from 'node:crypto' +import { skipCSRFCheck } from '@auth/core' +import type { Adapter } from '@auth/core/adapters' +import Credentials from '@auth/core/providers/credentials' import { Hono } from 'hono' -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' +import type { AuthConfig } from '../src' import { authHandler, verifyAuth, initAuthConfig, reqWithEnvUrl } from '../src' // @ts-expect-error - global crypto //needed for node 18 and below but should work in node 20 and above global.crypto = webcrypto -describe('Auth.js Adapter Middleware', () => { +describe('Config', () => { it('Should return 500 if AUTH_SECRET is missing', async () => { + globalThis.process.env = { AUTH_SECRET: '' } const app = new Hono() app.use('/*', (c, next) => { @@ -32,14 +37,10 @@ describe('Auth.js Adapter Middleware', () => { expect(await res.text()).toBe('Missing AUTH_SECRET') }) - it('Should return 200 auth initial config is correct', async () => { + it('Should return 200 auth initial config is correct', async () => { + globalThis.process.env = { AUTH_SECRET: 'secret' } const app = new Hono() - app.use('/*', (c, next) => { - c.env = { AUTH_SECRET: 'secret' } - return next() - }) - app.use( '/*', initAuthConfig(() => { @@ -90,3 +91,107 @@ describe('reqWithEnvUrl()', () => { expect(newReq.url.toString()).toBe('https://auth-url-base/request-path') }) }) + +describe('Credentials Provider', () => { + const mockAdapter: Adapter = { + createVerificationToken: vi.fn(), + useVerificationToken: vi.fn(), + getUserByEmail: vi.fn(), + createUser: vi.fn(), + getUser: vi.fn(), + getUserByAccount: vi.fn(), + updateUser: vi.fn(), + linkAccount: vi.fn(), + createSession: vi.fn(), + getSessionAndUser: vi.fn(), + updateSession: vi.fn(), + deleteSession: vi.fn(), + } + + globalThis.process.env = { + AUTH_SECRET: 'secret', + } + + const user = { email: 'hono@hono.hono', name: 'Hono' } + + const app = new Hono() + + app.use('*', initAuthConfig(getAuthConfig)) + + app.use('/api/auth/*', authHandler()) + + app.use('/api/*', verifyAuth()) + + app.get('/api/protected', (c) => { + const auth = c.get('authUser') + return c.json(auth) + }) + + const credentials = Credentials({ + credentials: { + password: {}, + }, + authorize: (credentials) => { + if (credentials.password === 'password') { + return user + } + return null + }, + }) + + function getAuthConfig(): AuthConfig { + return { + secret: 'secret', + providers: [credentials], + adapter: mockAdapter, + skipCSRFCheck, + callbacks: { + jwt: ({ token, user }) => { + if (user) { + token.id = user.id + } + return token + }, + }, + session: { + strategy: 'jwt', + }, + } + } + + let cookie = [''] + + it('Should not authorize and return 302 - /api/auth/callback/credentials', async () => { + const res = await app.request('/api/auth/callback/credentials', { + method: 'POST', + }) + expect(res.status).toBe(302) + expect(res.headers.get('location')).toBe( + 'http://localhost/api/auth/signin?error=CredentialsSignin&code=credentials' + ) + }) + + it('Should authorize and return 302 - /api/auth/callback/credentials', async () => { + const res = await app.request('http://localhost/api/auth/callback/credentials', { + method: 'POST', + body: new URLSearchParams({ + password: 'password', + }), + }) + expect(res.status).toBe(302) + expect(res.headers.get('location')).toBe('http://localhost') + cookie = res.headers.getSetCookie() + }) + + it('Should authorize and return 200 - /api/protected', async () => { + const headers = new Headers() + headers.append('cookie', cookie[1]) + const res = await app.request('http://localhost/api/protected', { + headers, + }) + expect(res.status).toBe(200) + const obj = await res.json() + expect(obj['token']['name']).toBe(user.name) + expect(obj['token']['email']).toBe(user.email) + }) +}) diff --git a/yarn.lock b/yarn.lock index 1e985dd2..26c52654 100644 --- a/yarn.lock +++ b/yarn.lock @@ -45,9 +45,9 @@ __metadata: languageName: node linkType: hard -"@auth/core@npm:^0.24.0": - version: 0.24.0 - resolution: "@auth/core@npm:0.24.0" +"@auth/core@npm:^0.30.0": + version: 0.30.0 + resolution: "@auth/core@npm:0.30.0" dependencies: "@panva/hkdf": "npm:^1.1.1" "@types/cookie": "npm:0.6.0" @@ -57,11 +57,17 @@ __metadata: preact: "npm:10.11.3" preact-render-to-string: "npm:5.2.3" peerDependencies: + "@simplewebauthn/browser": ^9.0.1 + "@simplewebauthn/server": ^9.0.2 nodemailer: ^6.8.0 peerDependenciesMeta: + "@simplewebauthn/browser": + optional: true + "@simplewebauthn/server": + optional: true nodemailer: optional: true - checksum: b8d8c66bc35d18a6ffa80e21b122747cb0c40826f68eb8c22a1b4dda01aba62c2050a1d5e4997e92a6756edad4103faafe8d9a49c7278d991e690c3ebbdb6035 + checksum: caa94cc9b42c354fef57e337a844bca0c0770ac809ba1cf00b30d7fc2e383d1d42dafeb3e39b9dde92b85a9eb821cde905b662638bfe98d8e2439c6d3e64c8cd languageName: node linkType: hard @@ -1779,7 +1785,7 @@ __metadata: version: 0.0.0-use.local resolution: "@hono/auth-js@workspace:packages/auth-js" dependencies: - "@auth/core": "npm:^0.24.0" + "@auth/core": "npm:^0.30.0" "@types/react": "npm:^18" hono: "npm:^3.11.7" jest: "npm:^29.7.0"