Skip to content

Commit

Permalink
fix(auth-js): use env in hono/adapter and add tests (#486)
Browse files Browse the repository at this point in the history
* fix(auth-js): use `env` in `hono/adapter` and add tests

* add changeset

* polyfill crypto
  • Loading branch information
yusukebe committed Apr 30, 2024
1 parent 173fed2 commit 1895955
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/silver-students-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hono/auth-js': patch
---

fix: use `env` in `hono/adapter` and add tests
2 changes: 1 addition & 1 deletion packages/auth-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
15 changes: 8 additions & 7 deletions packages/auth-js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' {
Expand Down Expand Up @@ -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
}
}

Expand All @@ -62,8 +63,8 @@ function setEnvDefaults(env: AuthEnv, config: AuthConfig) {

export async function getAuthUser(c: Context): Promise<AuthUser | null> {
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') ?? '' },
})
Expand Down Expand Up @@ -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)
}
}
121 changes: 113 additions & 8 deletions packages/auth-js/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand All @@ -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(() => {
Expand Down Expand Up @@ -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: '[email protected]', 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)
})
})
16 changes: 11 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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

Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 1895955

Please sign in to comment.