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 cryptopull/487/head
parent
173fed200a
commit
18959557f4
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@hono/auth-js': patch
|
||||
---
|
||||
|
||||
fix: use `env` in `hono/adapter` and add tests
|
|
@ -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",
|
||||
|
|
|
@ -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<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') ?? '' },
|
||||
})
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
16
yarn.lock
16
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"
|
||||
|
|
Loading…
Reference in New Issue