fix(auth.js): clone request directly for bun (#790)
* fix(auth.js): clone request directly for bun * added popup login hook * update README.mdpull/791/head
parent
2d1a89df26
commit
ed31c680f7
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@hono/auth-js': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
clone request directly for bun
|
|
@ -112,8 +112,7 @@ const useSession = () => {
|
||||||
return { session: data, status }
|
return { session: data, status }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
For more details on how to Popup Oauth Login see [example](https://github.com/divyam234/next-auth-hono-react)
|
||||||
Working example repo https://github.com/divyam234/next-auth-hono-react
|
|
||||||
|
|
||||||
## Author
|
## Author
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,18 @@ export type SessionContextValue<R extends boolean = false> = R extends true
|
||||||
status: 'unauthenticated' | 'loading'
|
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>(
|
export async function fetchData<T = any>(
|
||||||
path: string,
|
path: string,
|
||||||
config: {
|
config: {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import type { AdapterUser } from '@auth/core/adapters'
|
||||||
import type { JWT } from '@auth/core/jwt'
|
import type { JWT } from '@auth/core/jwt'
|
||||||
import type { Session } from '@auth/core/types'
|
import type { Session } from '@auth/core/types'
|
||||||
import type { Context, MiddlewareHandler } from 'hono'
|
import type { Context, MiddlewareHandler } from 'hono'
|
||||||
import { env, getRuntimeKey } from 'hono/adapter'
|
import { env } from 'hono/adapter'
|
||||||
import { HTTPException } from 'hono/http-exception'
|
import { HTTPException } from 'hono/http-exception'
|
||||||
import { setEnvDefaults as coreSetEnvDefaults } from '@auth/core'
|
import { setEnvDefaults as coreSetEnvDefaults } from '@auth/core'
|
||||||
|
|
||||||
|
@ -37,28 +37,7 @@ export function setEnvDefaults(env: AuthEnv, config: AuthConfig) {
|
||||||
coreSetEnvDefaults(env, config)
|
coreSetEnvDefaults(env, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cloneRequest(input: URL | string, request: Request, headers?: Headers) {
|
export function reqWithEnvUrl(req: Request, authUrl?: string) {
|
||||||
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) {
|
|
||||||
if (authUrl) {
|
if (authUrl) {
|
||||||
const reqUrlObj = new URL(req.url)
|
const reqUrlObj = new URL(req.url)
|
||||||
const authUrlObj = new URL(authUrl)
|
const authUrlObj = new URL(authUrl)
|
||||||
|
@ -66,30 +45,30 @@ export async function reqWithEnvUrl(req: Request, authUrl?: string) {
|
||||||
for (const prop of props) {
|
for (const prop of props) {
|
||||||
if (authUrlObj[prop]) reqUrlObj[prop] = authUrlObj[prop]
|
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 newReq = new Request(req)
|
||||||
const headers = new Headers(req.headers)
|
const url = new URL(newReq.url)
|
||||||
const proto = headers.get('x-forwarded-proto')
|
const proto = newReq.headers.get('x-forwarded-proto')
|
||||||
const host = headers.get('x-forwarded-host') ?? headers.get('host')
|
const host = newReq.headers.get('x-forwarded-host') ?? newReq.headers.get('host')
|
||||||
if (proto != null) url.protocol = proto.endsWith(':') ? proto : `${proto}:`
|
if (proto != null) url.protocol = proto.endsWith(':') ? proto : `${proto}:`
|
||||||
if (host != null) {
|
if (host != null) {
|
||||||
url.host = host
|
url.host = host
|
||||||
const portMatch = host.match(/:(\d+)$/)
|
const portMatch = host.match(/:(\d+)$/)
|
||||||
if (portMatch) url.port = portMatch[1]
|
if (portMatch) url.port = portMatch[1]
|
||||||
else url.port = ''
|
else url.port = ''
|
||||||
headers.delete('x-forwarded-host')
|
newReq.headers.delete('x-forwarded-host')
|
||||||
headers.delete('Host')
|
newReq.headers.delete('Host')
|
||||||
headers.set('Host', 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> {
|
export async function getAuthUser(c: Context): Promise<AuthUser | null> {
|
||||||
const config = c.get('authConfig')
|
const config = c.get('authConfig')
|
||||||
const ctxEnv = env(c) as AuthEnv
|
const ctxEnv = env(c) as AuthEnv
|
||||||
setEnvDefaults(ctxEnv, config)
|
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 origin = new URL(authReq.url).origin
|
||||||
const request = new Request(`${origin}${config.basePath}/session`, {
|
const request = new Request(`${origin}${config.basePath}/session`, {
|
||||||
headers: { cookie: c.req.header('cookie') ?? '' },
|
headers: { cookie: c.req.header('cookie') ?? '' },
|
||||||
|
@ -150,8 +129,7 @@ export function authHandler(): MiddlewareHandler {
|
||||||
throw new HTTPException(500, { message: 'Missing AUTH_SECRET' })
|
throw new HTTPException(500, { message: 'Missing AUTH_SECRET' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const authReq = await reqWithEnvUrl(c.req.raw, ctxEnv.AUTH_URL)
|
const res = await Auth(reqWithEnvUrl(c.req.raw, ctxEnv.AUTH_URL), config)
|
||||||
const res = await Auth(authReq, config)
|
|
||||||
return new Response(res.body, res)
|
return new Response(res.body, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,11 @@ import {
|
||||||
type ClientSafeProvider,
|
type ClientSafeProvider,
|
||||||
type SignOutParams,
|
type SignOutParams,
|
||||||
type SignOutResponse,
|
type SignOutResponse,
|
||||||
|
WindowProps,
|
||||||
|
AuthState,
|
||||||
} from './client'
|
} from './client'
|
||||||
import type { LoggerInstance, Session } from '@auth/core/types'
|
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'
|
import type { BuiltInProviderType, RedirectableProviderType } from '@auth/core/providers'
|
||||||
|
|
||||||
const logger: LoggerInstance = {
|
const logger: LoggerInstance = {
|
||||||
|
@ -380,3 +382,72 @@ export async function signOut<R extends boolean = true>(
|
||||||
|
|
||||||
return data as R extends true ? undefined : SignOutResponse
|
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 }
|
||||||
|
}
|
||||||
|
|
|
@ -186,12 +186,12 @@ describe('Credentials Provider', () => {
|
||||||
headers,
|
headers,
|
||||||
})
|
})
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
const obj = await res.json<{
|
const obj = await res.json() as {
|
||||||
token: {
|
token: {
|
||||||
name: string
|
name: string
|
||||||
email: string
|
email: string
|
||||||
}
|
}
|
||||||
}>()
|
}
|
||||||
expect(obj.token.name).toBe(user.name)
|
expect(obj.token.name).toBe(user.name)
|
||||||
expect(obj.token.email).toBe(user.email)
|
expect(obj.token.email).toBe(user.email)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue