Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#854): Add getServerSession and getToken to the local provider #855

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
20 changes: 17 additions & 3 deletions playground-local/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,23 @@ const password = ref('')
refresh session (required: true)
</button>
<br>
<NuxtLink to="/login">
navigate to Login Page
</NuxtLink>
<h2>Navigation</h2>
<p>Navigate to different pages below to test out different things:</p>
<div>
<nuxt-link to="/api/protected/inline" external>
-> API endpoint protected inline
</nuxt-link>
<br>
<nuxt-link to="/api/protected/middleware" external>
-> API endpoint protected middleware
</nuxt-link>
<br>
<NuxtLink to="/login">
-> navigate to Login Page
</NuxtLink>
<br>
</div>
<br>
<NuxtPage />
</div>
</template>
10 changes: 10 additions & 0 deletions playground-local/server/api/protected/inline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { eventHandler } from 'h3'
import { getServerSession } from '#auth'

export default eventHandler(async (event) => {
const session = await getServerSession(event)
if (!session) {
return { status: 'unauthenticated!' }
}
return { status: 'authenticated!', text: 'im protected by an in-endpoint check', session }
})
3 changes: 3 additions & 0 deletions playground-local/server/api/protected/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { eventHandler } from 'h3'

export default eventHandler(() => ({ status: 'authenticated', text: 'you only see me if you are logged in, as a server-middleware protects me' }))
4 changes: 4 additions & 0 deletions playground-local/server/api/session.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { defineEventHandler } from 'h3'
import { getServerSession } from '#auth'

export default defineEventHandler(event => getServerSession(event))
4 changes: 4 additions & 0 deletions playground-local/server/api/token.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { defineEventHandler } from 'h3'
import { getToken } from '#auth'

export default defineEventHandler(event => getToken(event))
14 changes: 14 additions & 0 deletions playground-local/server/middleware/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createError, eventHandler } from 'h3'
import { getServerSession } from '#auth'

export default eventHandler(async (event) => {
// Only protect a certain backend route
if (!event.node.req.url?.startsWith('/api/protected/middleware')) {
return
}

const session = await getServerSession(event)
if (!session) {
throw createError({ statusMessage: 'Unauthenticated', statusCode: 403 })
}
})
26 changes: 8 additions & 18 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
} from '@nuxt/kit'
import { defu } from 'defu'
import { joinURL } from 'ufo'
import { genInterface } from 'knitwork'
import type { DeepRequired } from 'ts-essentials'
import type { NuxtModule } from 'nuxt/schema'
import { getOriginAndPathnameFromURL, isProduction } from './runtime/helpers'
Expand All @@ -23,6 +22,7 @@ import type {
RefreshHandler,
SupportedAuthProviders
} from './runtime/types'
import { generateModuleTypes } from './runtime/utils/generateTypes'

const topLevelDefaults = {
isEnabled: true,
Expand Down Expand Up @@ -101,7 +101,10 @@ const PACKAGE_NAME = 'sidebase-auth'
export default defineNuxtModule<ModuleOptions>({
meta: {
name: PACKAGE_NAME,
configKey: 'auth'
configKey: 'auth',
compatibility: {
nuxt: '>=3.0.0'
}
},
setup(userOptions, nuxt) {
const logger = useLogger(PACKAGE_NAME)
Expand Down Expand Up @@ -178,26 +181,13 @@ export default defineNuxtModule<ModuleOptions>({
inline: [resolve('./runtime')]
}
)
nitroConfig.alias['#auth'] = resolve('./runtime/server/services')

nitroConfig.alias['#auth'] = resolve(`./runtime/server/services/${options.provider.type}`)
})

addTypeTemplate({
filename: 'types/auth.d.ts',
getContents: () =>
[
'// AUTO-GENERATED BY @sidebase/nuxt-auth',
'declare module \'#auth\' {',
` const { getServerSession, getToken, NuxtAuthHandler }: typeof import('${resolve('./runtime/server/services')}')`,
...(options.provider.type === 'local'
? [genInterface(
'SessionData',
(options.provider as any).session.dataType
)]
: []
),
'}',
''
].join('\n')
getContents: () => generateModuleTypes(options.provider)
})

addTypeTemplate({
Expand Down
1 change: 1 addition & 0 deletions src/runtime/server/services/authjs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NuxtAuthHandler, getServerSession, getToken } from './nuxtAuthHandler'
1 change: 0 additions & 1 deletion src/runtime/server/services/index.ts

This file was deleted.

51 changes: 51 additions & 0 deletions src/runtime/server/services/local/getServerSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { type H3Event, createError } from 'h3'
import getURL from 'requrl'
import { joinURL } from 'ufo'
import { jsonPointerGet, useTypedBackendConfig } from '../../../helpers'
import { getToken } from './getToken'

// @ts-expect-error - #auth not defined
import type { SessionData } from '#auth'
import { useRuntimeConfig } from '#imports'

function joinPathToApiURL(event: H3Event, path: string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would prefer if this was down the file as it is a helper (consumers are more interested in reading getServerSession

const { origin, pathname, fullBaseUrl } = useRuntimeConfig().public.auth.computed

let baseURL
if (origin) {
// Case 1: An origin was supplied by the developer in the runtime-config. Use it by returning the already assembled full base url that contains it
baseURL = fullBaseUrl
}
else {
// Case 2: An origin was not supplied, we determine it from the request
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably needs to happen only when trustHost is set (look at authjs implementation)

const determinedOrigin = getURL(event.node.req, false)
baseURL = joinURL(determinedOrigin, pathname)
}

const base = path.startsWith('/') ? pathname : baseURL
return joinURL(base, path)
}

export async function getServerSession(event: H3Event): Promise<SessionData | null> {
const token = getToken(event)
if (!token) {
return null
}

const config = useTypedBackendConfig(useRuntimeConfig(), 'local')
const { path, method } = config.endpoints.getSession

// Compose heads to request the session
const headers = new Headers({ [config.token.headerName]: token } as HeadersInit)

try {
const url = joinPathToApiURL(event, path)
const result = await $fetch<any>(url, { method, headers })
const { dataResponsePointer: sessionDataResponsePointer } = config.session
return jsonPointerGet<SessionData>(result, sessionDataResponsePointer)
}
catch (err) {
console.error(err)
throw createError({ statusCode: 401, statusMessage: 'Session could not be retrieved.' })
}
}
15 changes: 15 additions & 0 deletions src/runtime/server/services/local/getToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type H3Event, getCookie } from 'h3'
import { useTypedBackendConfig } from '../../../helpers'
import { formatToken } from '../../../utils/local'
import { useRuntimeConfig } from '#imports'

export function getToken(event: H3Event) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export function getToken(event: H3Event) {
export function getToken(event: H3Event): string | null {

const config = useTypedBackendConfig(useRuntimeConfig(), 'local')
const rawToken = getCookie(event, config.token.cookieName)
const token = formatToken(rawToken, config)

if (!token) {
return null
}
return token
Comment on lines +11 to +14
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatToken already returns string | null

Suggested change
if (!token) {
return null
}
return token
return token

}
2 changes: 2 additions & 0 deletions src/runtime/server/services/local/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { getToken } from './getToken'
export { getServerSession } from './getServerSession'
26 changes: 26 additions & 0 deletions src/runtime/utils/generateTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { genInterface } from 'knitwork'
import { createResolver } from '@nuxt/kit'
import type { AuthProviders } from '../types'

export function generateModuleTypes(provider: AuthProviders) {
const { resolve } = createResolver(import.meta.url)

const providerSpecificTypes: string[] = []

if (provider.type === 'authjs') {
providerSpecificTypes.push(` const { getServerSession, getToken, NuxtAuthHandler }: typeof import('${resolve('./runtime/server/services/authjs')}')`)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question - does this resolve relative to current path or from root? From the docs, it looks like the resolution will happen from the current path which is not correct:
https://nuxt.com/docs/api/advanced/import-meta#builder-properties

}

if (provider.type === 'local') {
providerSpecificTypes.push(` const { getServerSession, getToken }: typeof import('${resolve('./runtime/server/services/local')}')`)
providerSpecificTypes.push(genInterface('SessionData', (provider as any).session.dataType))
}

return [
'// AUTO-GENERATED BY @sidebase/nuxt-auth',
`declare module '#auth' {`,
...providerSpecificTypes,
'}',
''
].join('\n')
}
Loading