Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = {
files: [
"apps/dev/pages/api/auth/[...nextauth].ts",
"docs/{sidebars,docusaurus.config}.js",
"packages/core/src/index.ts",
],
options: { printWidth: 150 },
},
Expand Down
8 changes: 0 additions & 8 deletions apps/dev/nextjs/pages/api/examples/session.js

This file was deleted.

21 changes: 21 additions & 0 deletions apps/dev/nextjs/pages/api/examples/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Auth, SessionRequest } from "@auth/core"
import { authConfig } from "../auth/[...nextauth]"

export default async function handle(req: Request) {
authConfig.secret = process.env.AUTH_SECRET

const response = await Auth(new SessionRequest(req), authConfig)
const session = await response.session()
if (!session) {
return new Response("Not authenticated", { status: 401 })
}

console.log(session.user) // Do something with the session
// Pass the original headers to set cookies (eg.: updating the session expiry)
response.headers.set("content-type", "text/plain")
return new Response("Authenticated", { headers: response.headers })
}

export const config = {
runtime: "experimental-edge",
}
4 changes: 2 additions & 2 deletions docs/src/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,6 @@ html[data-theme="dark"] #carbonads .carbon-poweredby {
See: https://github.com/TypeStrong/typedoc/issues/2006
*/
/* h3.anchor + p:has(code, strong), */ /** hack did not work as it hides property types elsewhere */
#classes {
/* #classes {
display: none;
}
} */
63 changes: 31 additions & 32 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ import { logger, setLogger, type LoggerInstance } from "./lib/utils/logger.js"
import { toInternalRequest, toResponse } from "./lib/web.js"

import type { Adapter } from "./adapters.js"
import type {
CallbacksOptions,
CookiesOptions,
EventCallbacks,
PagesOptions,
SessionOptions,
Theme,
} from "./types.js"
import type { CallbacksOptions, CookiesOptions, EventCallbacks, PagesOptions, SessionOptions, Theme } from "./types.js"
import type { Provider } from "./providers/index.js"
import { JWTOptions } from "./jwt.js"
import { ProvidersRequest, ProvidersResponse, SessionRequest, SessionResponse } from "./lib/web-extension.js"

export * from "./lib/web-extension.js"

/** Returns a special {@link SessionResponse} instance to read the session from the request. */
export function Auth(request: SessionRequest, config: AuthConfig): Promise<SessionResponse>
/** Returns a special {@link ProvidersResponse} instance to read the list of providers in a client-safe way. */
export function Auth(request: ProvidersRequest, config: AuthConfig): Promise<ProvidersResponse>
/**
* Core functionality provided by Auth.js.
*
Expand All @@ -69,19 +69,18 @@ import { JWTOptions } from "./jwt.js"
*```
* @see [Documentation](https://authjs.dev)
*/
export async function Auth(
request: Request,
config: AuthConfig
): Promise<Response> {
export function Auth(request: Request, config: AuthConfig): Promise<Response>
export async function Auth(request: Request, config: AuthConfig): Promise<Response> {
setLogger(config.logger, config.debug)

const isAuthRequest = request instanceof SessionRequest || request instanceof ProvidersRequest

if (isAuthRequest) config.trustHost = true

const internalRequest = await toInternalRequest(request)
if (internalRequest instanceof Error) {
logger.error(internalRequest)
return new Response(
`Error: This action with HTTP ${request.method} is not supported.`,
{ status: 400 }
)
return new Response(`Error: This action with HTTP ${request.method} is not supported.`, { status: 400 })
}

const assertionResult = assertConfig(internalRequest, config)
Expand All @@ -92,14 +91,10 @@ export async function Auth(
// Bail out early if there's an error in the user config
logger.error(assertionResult)
const htmlPages = ["signin", "signout", "error", "verify-request"]
if (
!htmlPages.includes(internalRequest.action) ||
internalRequest.method !== "GET"
) {
if (!htmlPages.includes(internalRequest.action) || internalRequest.method !== "GET") {
return new Response(
JSON.stringify({
message:
"There was a problem with the server configuration. Check the server logs for more information.",
message: "There was a problem with the server configuration. Check the server logs for more information.",
code: assertionResult.name,
}),
{ status: 500, headers: { "Content-Type": "application/json" } }
Expand All @@ -108,19 +103,11 @@ export async function Auth(

const { pages, theme } = config

const authOnErrorPage =
pages?.error &&
internalRequest.url.searchParams
.get("callbackUrl")
?.startsWith(pages.error)
const authOnErrorPage = pages?.error && internalRequest.url.searchParams.get("callbackUrl")?.startsWith(pages.error)

if (!pages?.error || authOnErrorPage) {
if (authOnErrorPage) {
logger.error(
new ErrorPageLoop(
`The error page ${pages?.error} should not require authentication`
)
)
logger.error(new ErrorPageLoop(`The error page ${pages?.error} should not require authentication`))
}
const render = renderPage({ theme })
const page = render.error({ error: "Configuration" })
Expand All @@ -144,6 +131,18 @@ export async function Auth(
headers: response.headers,
})
}

if (isAuthRequest) {
switch (request.action) {
case "session":
return new SessionResponse(response.body, response)
case "providers":
return new ProvidersResponse(response.body, response)
default:
return response
}
}

return response
}

Expand Down
95 changes: 95 additions & 0 deletions packages/core/src/lib/web-extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { AuthAction, Session } from "../types.js"
import { PublicProvider } from "./routes/providers.js"

/** @internal */
export abstract class AuthRequest extends Request {
abstract action: AuthAction
}

/**
* Extends the standard {@link Request} to add a `session()` method on the response
* for retrieving the {@link Session} object.
*/
export class SessionRequest extends AuthRequest {
action = "session" as const
constructor(req: Request) {
super(req.url, req)
}
}

export class SessionResponse extends Response {
action = "session" as const

/**
* Returns the {@link Session} object from the response, or `null`
* if the session is unavailable (config error, not authenticated, etc.).
*
* @example
* ```ts
* export default async function handle(req: Request) {
* const response = await Auth(new SessionRequest(req), authConfig)
* const session = await response.session()
*
* if (!session) {
* return new Response("Not authenticated", { status: 401 })
* }
*
* console.log(session.user) // Do something with the session
* return response // or return whatever you want.
* }
* ```
*/
async session(): Promise<Session | null> {
try {
const data = await this.clone().json()
if (!this.ok || !data || !Object.keys(data).length) {
return null
}
return data
} catch {
return null
}
}
}

/**
* Extends the standard {@link Request} to add a `providers()` method on the response
* for retrieving a list of client-safe provider configuration. Useful for
* rendering a list of sign-in options.
*/
export class ProvidersRequest extends AuthRequest {
action = "providers" as const
constructor(req: Request) {
super(req.url, req)
}
}

export class ProvidersResponse extends Response {
action = "providers" as const

/**
* Returns the list of providers from the response, or `null`
* if the providers are unavailable (config error, etc.).
* @example
* ```ts
* export default async function handle(req: Request) {
* const response = await Auth(new ProvidersRequest(req), authConfig)
* const providers = await response.providers()
* if (!providers) {
* return new Response("Providers unavailable", { status: 500 })
*
*
* console.log(providers) // Do something with the providers
* return response // or return whatever you want.
* }
* ```
*/
async providers(): Promise<PublicProvider[]> {
try {
if (!this.ok) return []
return Object.values(await this.clone().json())
} catch {
return []
}
}
}
6 changes: 5 additions & 1 deletion packages/core/src/lib/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { parse as parseCookie, serialize } from "cookie"
import { AuthError, UnknownAction } from "../errors.js"

import type { AuthAction, RequestInternal, ResponseInternal } from "../types.js"
import { ProvidersRequest, SessionRequest } from "./web-extension.js"

async function getBody(req: Request): Promise<Record<string, any> | undefined> {
if (!("body" in req) || !req.body || req.method !== "POST") return
Expand Down Expand Up @@ -34,8 +35,11 @@ export async function toInternalRequest(
// see init.ts
const url = new URL(req.url.replace(/\/$/, ""))
const { pathname } = url
let action: AuthAction | undefined
if (req instanceof SessionRequest || req instanceof ProvidersRequest) {
action = req.action
} else action = actions.find((a) => pathname.includes(a))

const action = actions.find((a) => pathname.includes(a))
if (!action) {
throw new UnknownAction("Cannot detect action.")
}
Expand Down