2023-12-29 03:30:25 +08:00
|
|
|
import type { AuthConfig as AuthConfigCore } from '@auth/core'
|
|
|
|
import { Auth } from '@auth/core'
|
|
|
|
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'
|
2024-07-01 08:57:02 +08:00
|
|
|
import { env ,getRuntimeKey} from 'hono/adapter'
|
2023-12-29 03:30:25 +08:00
|
|
|
import { HTTPException } from 'hono/http-exception'
|
2024-05-29 00:18:18 +08:00
|
|
|
import { setEnvDefaults as coreSetEnvDefaults } from '@auth/core'
|
2023-12-29 03:30:25 +08:00
|
|
|
|
2024-07-01 08:57:02 +08:00
|
|
|
|
2023-12-29 03:30:25 +08:00
|
|
|
declare module 'hono' {
|
|
|
|
interface ContextVariableMap {
|
|
|
|
authUser: AuthUser
|
|
|
|
authConfig: AuthConfig
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export type AuthEnv = {
|
2024-05-04 10:42:10 +08:00
|
|
|
AUTH_URL?: string
|
2023-12-29 03:30:25 +08:00
|
|
|
AUTH_SECRET: string
|
|
|
|
AUTH_REDIRECT_PROXY_URL?: string
|
|
|
|
[key: string]: string | undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
export type AuthUser = {
|
|
|
|
session: Session
|
|
|
|
token?: JWT
|
|
|
|
user?: AdapterUser
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface AuthConfig extends Omit<AuthConfigCore, 'raw'> {}
|
|
|
|
|
|
|
|
export type ConfigHandler = (c: Context) => AuthConfig
|
|
|
|
|
2024-05-29 00:18:18 +08:00
|
|
|
export function setEnvDefaults(env: AuthEnv, config: AuthConfig) {
|
|
|
|
config.secret ??= env.AUTH_SECRET
|
|
|
|
config.basePath ||= '/api/auth'
|
|
|
|
coreSetEnvDefaults(env, config)
|
|
|
|
}
|
|
|
|
|
2024-07-01 08:57:02 +08:00
|
|
|
async function cloneRequest(input: URL | string, request: Request){
|
|
|
|
|
|
|
|
if ( getRuntimeKey() === "bun") {
|
|
|
|
return new Request(input, {
|
|
|
|
method: request.method,
|
|
|
|
headers:new Headers(request.headers),
|
|
|
|
body:
|
|
|
|
request.method === "GET" || request.method === "HEAD"
|
|
|
|
? undefined
|
|
|
|
: await request.blob(),
|
|
|
|
// @ts-ignore: TS2353
|
|
|
|
referrer: "referrer" in request ? (request.referrer as string) : undefined,
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
referrerPolicy: request.referrerPolicy as any,
|
|
|
|
mode: request.mode,
|
|
|
|
credentials: request.credentials,
|
|
|
|
// @ts-ignore: TS2353
|
|
|
|
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){
|
2024-04-26 09:57:56 +08:00
|
|
|
if (authUrl) {
|
|
|
|
const reqUrlObj = new URL(req.url)
|
|
|
|
const authUrlObj = new URL(authUrl)
|
|
|
|
const props = ['hostname', 'protocol', 'port', 'password', 'username'] as const
|
2024-04-30 16:25:05 +08:00
|
|
|
props.forEach((prop) => (reqUrlObj[prop] = authUrlObj[prop]))
|
2024-07-01 08:57:02 +08:00
|
|
|
return cloneRequest(reqUrlObj.href, req)
|
2024-04-26 09:57:56 +08:00
|
|
|
} else {
|
2024-05-29 00:18:18 +08:00
|
|
|
const url = new URL(req.url)
|
|
|
|
const proto = req.headers.get('x-forwarded-proto')
|
|
|
|
const host = req.headers.get('x-forwarded-host') ?? req.headers.get('host')
|
|
|
|
if (proto != null) url.protocol = proto.endsWith(':') ? proto : proto + ':'
|
2024-07-01 08:57:02 +08:00
|
|
|
if (host!=null) {
|
2024-05-29 00:18:18 +08:00
|
|
|
url.host = host
|
|
|
|
const portMatch = host.match(/:(\d+)$/)
|
|
|
|
if (portMatch) url.port = portMatch[1]
|
|
|
|
else url.port = ''
|
2024-07-01 08:57:02 +08:00
|
|
|
req.headers.delete("x-forwarded-host")
|
|
|
|
req.headers.delete("Host")
|
|
|
|
req.headers.set("Host", host)
|
2023-12-29 03:30:25 +08:00
|
|
|
}
|
2024-07-01 08:57:02 +08:00
|
|
|
return cloneRequest(url.href, req)
|
2024-05-29 00:18:18 +08:00
|
|
|
}
|
2023-12-29 03:30:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export async function getAuthUser(c: Context): Promise<AuthUser | null> {
|
|
|
|
const config = c.get('authConfig')
|
2024-07-01 08:57:02 +08:00
|
|
|
const ctxEnv = env(c) as AuthEnv
|
2024-05-04 10:42:10 +08:00
|
|
|
setEnvDefaults(ctxEnv, config)
|
2024-07-01 08:57:02 +08:00
|
|
|
const authReq = await reqWithEnvUrl(c.req.raw, ctxEnv.AUTH_URL)
|
|
|
|
const origin = new URL(authReq.url).origin
|
2024-01-27 21:15:06 +08:00
|
|
|
const request = new Request(`${origin}${config.basePath}/session`, {
|
2023-12-29 03:30:25 +08:00
|
|
|
headers: { cookie: c.req.header('cookie') ?? '' },
|
|
|
|
})
|
|
|
|
|
|
|
|
let authUser: AuthUser = {} as AuthUser
|
|
|
|
|
|
|
|
const response = (await Auth(request, {
|
|
|
|
...config,
|
|
|
|
callbacks: {
|
|
|
|
...config.callbacks,
|
|
|
|
async session(...args) {
|
|
|
|
authUser = args[0]
|
2024-01-29 21:53:43 +08:00
|
|
|
const session = (await config.callbacks?.session?.(...args)) ?? args[0].session
|
2023-12-29 03:30:25 +08:00
|
|
|
const user = args[0].user ?? args[0].token
|
|
|
|
return { user, ...session } satisfies Session
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})) as Response
|
|
|
|
|
|
|
|
const session = (await response.json()) as Session | null
|
|
|
|
|
|
|
|
return session && session.user ? authUser : null
|
|
|
|
}
|
|
|
|
|
|
|
|
export function verifyAuth(): MiddlewareHandler {
|
|
|
|
return async (c, next) => {
|
|
|
|
const authUser = await getAuthUser(c)
|
|
|
|
const isAuth = !!authUser?.token || !!authUser?.user
|
|
|
|
if (!isAuth) {
|
|
|
|
const res = new Response('Unauthorized', {
|
|
|
|
status: 401,
|
|
|
|
})
|
|
|
|
throw new HTTPException(401, { res })
|
2024-01-29 21:53:43 +08:00
|
|
|
} else {
|
|
|
|
c.set('authUser', authUser)
|
|
|
|
}
|
2023-12-29 03:30:25 +08:00
|
|
|
|
|
|
|
await next()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function initAuthConfig(cb: ConfigHandler): MiddlewareHandler {
|
|
|
|
return async (c, next) => {
|
|
|
|
const config = cb(c)
|
|
|
|
c.set('authConfig', config)
|
|
|
|
await next()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function authHandler(): MiddlewareHandler {
|
|
|
|
return async (c) => {
|
|
|
|
const config = c.get('authConfig')
|
2024-07-01 08:57:02 +08:00
|
|
|
const ctxEnv = env(c) as AuthEnv
|
2024-05-29 00:18:18 +08:00
|
|
|
|
2024-05-04 10:42:10 +08:00
|
|
|
setEnvDefaults(ctxEnv, config)
|
2023-12-29 03:30:25 +08:00
|
|
|
|
2024-05-29 00:18:18 +08:00
|
|
|
if (!config.secret || config.secret.length === 0) {
|
2023-12-29 03:30:25 +08:00
|
|
|
throw new HTTPException(500, { message: 'Missing AUTH_SECRET' })
|
|
|
|
}
|
|
|
|
|
2024-07-01 08:57:02 +08:00
|
|
|
const authReq = await reqWithEnvUrl(c.req.raw, ctxEnv.AUTH_URL)
|
|
|
|
const res = await Auth(authReq, config)
|
2023-12-29 03:30:25 +08:00
|
|
|
return new Response(res.body, res)
|
|
|
|
}
|
2024-05-29 00:18:18 +08:00
|
|
|
}
|