Skip to content

Commit

Permalink
feat: Add APIContext object to Auth Config
Browse files Browse the repository at this point in the history
Adds an option to the Auth Config file where users can define
a function which accept the Astro global object (for Astro
pages/components) or the APIContext object (for API Routes).

This allows Cloudflare D1 users to use the environment provided by
the cloudflare bindings, rather the the vite environment variables.

Adds getSessionByContext(), which allows fetching the session
when using a lazy auth config.

Fixes #60, #52, #50.
  • Loading branch information
JordanSekky committed Aug 20, 2024
1 parent e6da1df commit 7be3b83
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 21 deletions.
50 changes: 42 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,48 @@ AUTH_TRUST_HOST=true
#### Deploying to Vercel?
Setting `AUTH_TRUST_HOST` is not needed, as we also check for an active Vercel environment.

### Requirements
- Node version `>= 17.4`
- Astro config set to output mode `server`
- [SSR](https://docs.astro.build/en/guides/server-side-rendering/) enabled in your Astro project

Resources:
- [Enabling SSR in Your Project](https://docs.astro.build/en/guides/server-side-rendering/#enabling-ssr-in-your-project)
- [Adding an Adapter](https://docs.astro.build/en/guides/server-side-rendering/#adding-an-adapter)
### Using API Context and Runtime Environment in your Configuration

Some database providers like Cloudflare D1 provide bindings to your databases in the runtime
environment, which isn't accessible statically, but is provided on each request. You can define
your configuration as a function which accepts the APIContext (for API Routes) or the Astro
global value (for Astro pages/components).

```ts title="auth.config.ts"
// auth.config.ts
import Patreon from "@auth/core/providers/patreon";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { defineConfig } from "auth-astro";
import type { UserAuthConfig } from "auth-astro/src/config";
import { drizzle } from "drizzle-orm/d1";

export default {
config: (ctx) => {
const { env } = ctx.locals.runtime;
const db = env.DB;
return defineConfig({
secret: env.AUTH_SECRET,
trustHost: env.AUTH_TRUST_HOST === "true",
adapter: DrizzleAdapter(drizzle(db)),
providers: [
Patreon({
clientId: env.AUTH_PATREON_ID,
clientSecret: env.AUTH_PATREON_SECRET,
}),
],
});
},
} satisfies UserAuthConfig;
```

### requirements
- node version `>= 17.4`
- astro config set to output mode `server`
- [ssr](https://docs.astro.build/en/guides/server-side-rendering/) enabled in your astro project

resources:
- [enabling ssr in your project](https://docs.astro.build/en/guides/server-side-rendering/#enabling-ssr-in-your-project)
- [adding an adapter](https://docs.astro.build/en/guides/server-side-rendering/#adding-an-adapter)

# Usage

Expand Down
2 changes: 1 addition & 1 deletion module.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
declare module 'auth:config' {
const config: import('./src/config').FullAuthConfig
const config: import('./src/config').UserAuthConfig
export default config
}

Expand Down
50 changes: 40 additions & 10 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
*/
import { Auth } from '@auth/core'
import type { AuthAction, Session } from '@auth/core/types'
import type { APIContext } from 'astro'
import type { APIContext, AstroGlobal } from 'astro'
import { parseString } from 'set-cookie-parser'
import authConfig from 'auth:config'
import type { UserAuthConfig } from './src/config'

const actions: AuthAction[] = [
'providers',
Expand All @@ -41,13 +42,15 @@ const actions: AuthAction[] = [
]

function AstroAuthHandler(prefix: string, options = authConfig) {
return async ({ cookies, request }: APIContext) => {
return async (ctx: APIContext) => {
const { cookies, request } = ctx
const url = new URL(request.url)
const action = url.pathname.slice(prefix.length + 1).split('/')[0] as AuthAction

if (!actions.includes(action) || !url.pathname.startsWith(prefix + '/')) return

const res = await Auth(request, options)
const config = isUserConfigLazy(options) ? await options.config(ctx) : options
const res = await Auth(request, config)
if (['callback', 'signin', 'signout'].includes(action)) {
// Properly handle multiple Set-Cookie headers (they can't be concatenated in one)
const getSetCookie = res.headers.getSetCookie()
Expand Down Expand Up @@ -86,17 +89,23 @@ export function AstroAuth(options = authConfig) {
// @ts-ignore
const { AUTH_SECRET, AUTH_TRUST_HOST, VERCEL, NODE_ENV } = import.meta.env

options.secret ??= AUTH_SECRET
options.trustHost ??= !!(AUTH_TRUST_HOST ?? VERCEL ?? NODE_ENV !== 'production')

const { prefix = '/api/auth', ...authOptions } = options

const handler = AstroAuthHandler(prefix, authOptions)
return {
async GET(context: APIContext) {
const config = isUserConfigLazy(options) ? await options.config(context) : options
config.secret ??= AUTH_SECRET
config.trustHost ??= !!(AUTH_TRUST_HOST ?? VERCEL ?? NODE_ENV !== 'production')

const { prefix = '/api/auth', ...authOptions } = config
const handler = AstroAuthHandler(prefix, authOptions)
return await handler(context)
},
async POST(context: APIContext) {
const config = isUserConfigLazy(options) ? await options.config(context) : options
config.secret ??= AUTH_SECRET
config.trustHost ??= !!(AUTH_TRUST_HOST ?? VERCEL ?? NODE_ENV !== 'production')

const { prefix = '/api/auth', ...authOptions } = config
const handler = AstroAuthHandler(prefix, authOptions)
return await handler(context)
},
}
Expand All @@ -108,10 +117,14 @@ export function AstroAuth(options = authConfig) {
* @returns The current session, or `null` if there is no session.
*/
export async function getSession(req: Request, options = authConfig): Promise<Session | null> {
if (isUserConfigLazy(options)) {
throw new Error(
'User Auth Configuration is Lazy. Fetch the session using getSessionByContext().'
)
}
// @ts-ignore
options.secret ??= import.meta.env.AUTH_SECRET
options.trustHost ??= true

const url = new URL(`${options.prefix}/session`, req.url)
const response = await Auth(new Request(url, { headers: req.headers }), options)
const { status = 200 } = response
Expand All @@ -122,3 +135,20 @@ export async function getSession(req: Request, options = authConfig): Promise<Se
if (status === 200) return data
throw new Error(data.message)
}

/**
* Fetches the current session when using a lazy auth config.
* @param ctx The Astro global object, or APIContext.
* @returns The current session, or `null` if there is no session.
*/
export async function getSessionByContext(
ctx: AstroGlobal | APIContext,
options = authConfig
): Promise<Session | null> {
const config = isUserConfigLazy(options) ? await options.config(ctx) : options
return await getSession(ctx.request, config)
}

export function isUserConfigLazy(config: UserAuthConfig) {
return 'config' in config
}
10 changes: 9 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { PluginOption } from 'vite'
import type { AuthConfig } from '@auth/core/types'
import type { APIContext, AstroGlobal } from 'astro'
import type { ActionAPIContext } from 'astro/dist/actions/runtime/store'

export const virtualConfigModule = (configFile: string = './auth.config'): PluginOption => {
const virtualModuleId = 'auth:config'
Expand Down Expand Up @@ -27,7 +29,7 @@ export interface AstroAuthConfig {
*/
prefix?: string
/**
* Defineds wether or not you want the integration to handle the API routes
* Defines whether or not you want the integration to handle the API routes
* @default true
*/
injectEndpoints?: boolean
Expand All @@ -43,3 +45,9 @@ export const defineConfig = (config: FullAuthConfig) => {
config.basePath = config.prefix
return config
}

export type UserAuthConfig =
| {
config: (ctx: APIContext | AstroGlobal | ActionAPIContext) => FullAuthConfig | Promise<FullAuthConfig>
}
| FullAuthConfig
2 changes: 1 addition & 1 deletion src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default (config: AstroAuthConfig = {}): AstroIntegration => ({
injectRoute({
pattern: config.prefix + '/[...auth]',
entrypoint: entrypoint,
entryPoint: entrypoint
entryPoint: entrypoint,
})
}

Expand Down

0 comments on commit 7be3b83

Please sign in to comment.