Skip to content

Commit

Permalink
fix(auth.js): clone request directly for bun (#790)
Browse files Browse the repository at this point in the history
* fix(auth.js): clone request directly for bun

* added popup login hook

* update README.md
  • Loading branch information
divyam234 authored Oct 24, 2024
1 parent 2d1a89d commit ed31c68
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 41 deletions.
5 changes: 5 additions & 0 deletions .changeset/slimy-geese-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hono/auth-js': patch
---

clone request directly for bun
3 changes: 1 addition & 2 deletions packages/auth-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ const useSession = () => {
return { session: data, status }
}
```

Working example repo https://github.com/divyam234/next-auth-hono-react
For more details on how to Popup Oauth Login see [example](https://github.com/divyam234/next-auth-hono-react)

## Author

Expand Down
12 changes: 12 additions & 0 deletions packages/auth-js/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ export type SessionContextValue<R extends boolean = false> = R extends true
status: 'unauthenticated' | 'loading'
}

export type WindowProps = {
url: string
title: string
width: number
height: number
}

export type AuthState = {
status: 'loading' | 'success' | 'errored'
error?: string
}

export async function fetchData<T = any>(
path: string,
config: {
Expand Down
50 changes: 14 additions & 36 deletions packages/auth-js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +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, getRuntimeKey } from 'hono/adapter'
import { env } from 'hono/adapter'
import { HTTPException } from 'hono/http-exception'
import { setEnvDefaults as coreSetEnvDefaults } from '@auth/core'

Expand Down Expand Up @@ -37,59 +37,38 @@ export function setEnvDefaults(env: AuthEnv, config: AuthConfig) {
coreSetEnvDefaults(env, config)
}

async function cloneRequest(input: URL | string, request: Request, headers?: Headers) {
if (getRuntimeKey() === 'bun') {
return new Request(input, {
method: request.method,
headers: headers ?? new Headers(request.headers),
body:
request.method === 'GET' || request.method === 'HEAD' ? undefined : await request.blob(),
referrer: 'referrer' in request ? (request.referrer as string) : undefined,
referrerPolicy: request.referrerPolicy,
mode: request.mode,
credentials: request.credentials,
cache: request.cache,
redirect: request.redirect,
integrity: request.integrity,
keepalive: request.keepalive,
signal: request.signal,
})
}
return new Request(input, request)
}

export async function reqWithEnvUrl(req: Request, authUrl?: string) {
export function reqWithEnvUrl(req: Request, authUrl?: string) {
if (authUrl) {
const reqUrlObj = new URL(req.url)
const authUrlObj = new URL(authUrl)
const props = ['hostname', 'protocol', 'port', 'password', 'username'] as const
for (const prop of props) {
if (authUrlObj[prop]) reqUrlObj[prop] = authUrlObj[prop]
}
return cloneRequest(reqUrlObj.href, req)
return new Request(reqUrlObj.href, req)
}
const url = new URL(req.url)
const headers = new Headers(req.headers)
const proto = headers.get('x-forwarded-proto')
const host = headers.get('x-forwarded-host') ?? headers.get('host')
const newReq = new Request(req)
const url = new URL(newReq.url)
const proto = newReq.headers.get('x-forwarded-proto')
const host = newReq.headers.get('x-forwarded-host') ?? newReq.headers.get('host')
if (proto != null) url.protocol = proto.endsWith(':') ? proto : `${proto}:`
if (host != null) {
url.host = host
const portMatch = host.match(/:(\d+)$/)
if (portMatch) url.port = portMatch[1]
else url.port = ''
headers.delete('x-forwarded-host')
headers.delete('Host')
headers.set('Host', host)
newReq.headers.delete('x-forwarded-host')
newReq.headers.delete('Host')
newReq.headers.set('Host', host)
}
return cloneRequest(url.href, req, headers)
return new Request(url.href, newReq)
}

export async function getAuthUser(c: Context): Promise<AuthUser | null> {
const config = c.get('authConfig')
const ctxEnv = env(c) as AuthEnv
setEnvDefaults(ctxEnv, config)
const authReq = await reqWithEnvUrl(c.req.raw, ctxEnv.AUTH_URL)
const authReq = reqWithEnvUrl(c.req.raw, ctxEnv.AUTH_URL)
const origin = new URL(authReq.url).origin
const request = new Request(`${origin}${config.basePath}/session`, {
headers: { cookie: c.req.header('cookie') ?? '' },
Expand Down Expand Up @@ -149,9 +128,8 @@ export function authHandler(): MiddlewareHandler {
if (!config.secret || config.secret.length === 0) {
throw new HTTPException(500, { message: 'Missing AUTH_SECRET' })
}

const authReq = await reqWithEnvUrl(c.req.raw, ctxEnv.AUTH_URL)
const res = await Auth(authReq, config)

const res = await Auth(reqWithEnvUrl(c.req.raw, ctxEnv.AUTH_URL), config)
return new Response(res.body, res)
}
}
73 changes: 72 additions & 1 deletion packages/auth-js/src/react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import {
type ClientSafeProvider,
type SignOutParams,
type SignOutResponse,
WindowProps,
AuthState,
} from './client'
import type { LoggerInstance, Session } from '@auth/core/types'
import { useContext, useEffect, useMemo } from 'react'
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
import type { BuiltInProviderType, RedirectableProviderType } from '@auth/core/providers'

const logger: LoggerInstance = {
Expand Down Expand Up @@ -380,3 +382,72 @@ export async function signOut<R extends boolean = true>(

return data as R extends true ? undefined : SignOutResponse
}

const createPopup = ({ url, title, height, width }: WindowProps) => {
const left = window.screenX + (window.outerWidth - width) / 2
const top = window.screenY + (window.outerHeight - height) / 2.5
const externalPopup = window.open(
url,
title,
`width=${width},height=${height},left=${left},top=${top}`
)
return externalPopup
}

interface PopupLoginOptions extends Partial<Omit<WindowProps, 'url'>> {
onSuccess?: () => void
callbackUrl?: string
}

export const useOauthPopupLogin = (
provider: Parameters<typeof signIn>[0],
options: PopupLoginOptions = {}
) => {
const { width = 500, height = 500, title = 'Signin', onSuccess, callbackUrl = '/' } = options

const [externalWindow, setExternalWindow] = useState<Window | null>()

const [state, setState] = useState<AuthState>({ status: 'loading' })

const popUpSignin = useCallback(async () => {
const res = await signIn(provider, {
redirect: false,
callbackUrl,
})

if (res?.error) {
setState({ status: 'errored', error: res.error })
return
}
setExternalWindow(
createPopup({
url: res?.url as string,
title,
width,
height,
})
)
}, [])

useEffect(() => {
const handleMessage = (event: MessageEvent<AuthState>) => {
if (event.origin !== window.location.origin) return
if (event.data.status) {
setState(event.data)
if (event.data.status === 'success') {
onSuccess?.()
}
externalWindow?.close()
}
}

window.addEventListener('message', handleMessage)

return () => {
window.removeEventListener('message', handleMessage)
externalWindow?.close()
}
}, [externalWindow])

return { popUpSignin, ...state }
}
4 changes: 2 additions & 2 deletions packages/auth-js/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,12 @@ describe('Credentials Provider', () => {
headers,
})
expect(res.status).toBe(200)
const obj = await res.json<{
const obj = await res.json() as {
token: {
name: string
email: string
}
}>()
}
expect(obj.token.name).toBe(user.name)
expect(obj.token.email).toBe(user.email)
})
Expand Down

0 comments on commit ed31c68

Please sign in to comment.