From ed31c680f7cb4d08985c820e8e1bf051ddc57acd Mon Sep 17 00:00:00 2001 From: Divyam <47589864+divyam234@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:11:16 +0530 Subject: [PATCH] fix(auth.js): clone request directly for bun (#790) * fix(auth.js): clone request directly for bun * added popup login hook * update README.md --- .changeset/slimy-geese-promise.md | 5 ++ packages/auth-js/README.md | 3 +- packages/auth-js/src/client.ts | 12 +++++ packages/auth-js/src/index.ts | 50 ++++++-------------- packages/auth-js/src/react.tsx | 73 ++++++++++++++++++++++++++++- packages/auth-js/test/index.test.ts | 4 +- 6 files changed, 106 insertions(+), 41 deletions(-) create mode 100644 .changeset/slimy-geese-promise.md diff --git a/.changeset/slimy-geese-promise.md b/.changeset/slimy-geese-promise.md new file mode 100644 index 00000000..aa39a419 --- /dev/null +++ b/.changeset/slimy-geese-promise.md @@ -0,0 +1,5 @@ +--- +'@hono/auth-js': patch +--- + +clone request directly for bun diff --git a/packages/auth-js/README.md b/packages/auth-js/README.md index d86661bf..3457bfaa 100644 --- a/packages/auth-js/README.md +++ b/packages/auth-js/README.md @@ -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 diff --git a/packages/auth-js/src/client.ts b/packages/auth-js/src/client.ts index d9afc032..95ebf09e 100644 --- a/packages/auth-js/src/client.ts +++ b/packages/auth-js/src/client.ts @@ -85,6 +85,18 @@ export type SessionContextValue = 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( path: string, config: { diff --git a/packages/auth-js/src/index.ts b/packages/auth-js/src/index.ts index 395a161c..9151b6dc 100644 --- a/packages/auth-js/src/index.ts +++ b/packages/auth-js/src/index.ts @@ -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' @@ -37,28 +37,7 @@ 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) @@ -66,30 +45,30 @@ export async function reqWithEnvUrl(req: Request, authUrl?: string) { 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 { 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') ?? '' }, @@ -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) } } diff --git a/packages/auth-js/src/react.tsx b/packages/auth-js/src/react.tsx index 612015b6..731fd7d0 100644 --- a/packages/auth-js/src/react.tsx +++ b/packages/auth-js/src/react.tsx @@ -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 = { @@ -380,3 +382,72 @@ export async function signOut( 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> { + onSuccess?: () => void + callbackUrl?: string +} + +export const useOauthPopupLogin = ( + provider: Parameters[0], + options: PopupLoginOptions = {} +) => { + const { width = 500, height = 500, title = 'Signin', onSuccess, callbackUrl = '/' } = options + + const [externalWindow, setExternalWindow] = useState() + + const [state, setState] = useState({ 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) => { + 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 } +} diff --git a/packages/auth-js/test/index.test.ts b/packages/auth-js/test/index.test.ts index 96670145..5942c74d 100644 --- a/packages/auth-js/test/index.test.ts +++ b/packages/auth-js/test/index.test.ts @@ -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) })