Skip to content

Commit 915e4f1

Browse files
committed
feat: allow users to define custom session factory + types
1 parent 74f452c commit 915e4f1

File tree

11 files changed

+58
-36
lines changed

11 files changed

+58
-36
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ jobs:
6565
- name: Run test suite
6666
run: pnpm test
6767

68-
# - name: Test types
69-
# run: pnpm test:types
68+
- name: Test types
69+
run: pnpm test:types
7070

7171
# - name: Test playground types
7272
# run: pnpm test:types:playground

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ const session = await requireUserSession(event)
111111

112112
All helpers are exposed from the `oauth` global variable and can be used in your server routes or API routes.
113113

114-
The pattern is `oauth.<provider>EventHandler({ onSuccess, config?, onError? })`, example: `oauth.githubEventHandler`.
114+
The pattern is `oauth.<provider>EventHandler({ onSuccess?, config?, onError? })`, example: `oauth.githubEventHandler`.
115115

116116
The helper returns an event handler that automatically redirects to the provider authorization page and then call `onSuccess` or `onError` depending on the result.
117117

playground/app/auth-utils-session.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// This is only used if an oauth provider doesn't provide an `onSuccess` callback
2+
export default defineSession((_event, { provider, user }) => ({
3+
user: {
4+
[provider]: user
5+
},
6+
}))
Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1 @@
1-
export default oauth.githubEventHandler({
2-
async onSuccess(event, { user }) {
3-
await setUserSession(event, {
4-
user: {
5-
github: user,
6-
}
7-
})
8-
9-
return sendRedirect(event, '/')
10-
}
11-
})
1+
export default oauth.githubEventHandler()
Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1 @@
1-
export default oauth.spotifyEventHandler({
2-
async onSuccess(event, { user }) {
3-
await setUserSession(event, {
4-
user: {
5-
spotify: user,
6-
}
7-
})
8-
9-
return sendRedirect(event, '/')
10-
}
11-
})
1+
export default oauth.spotifyEventHandler()

src/module.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { defineNuxtModule, addPlugin, createResolver, addImportsDir, addServerHandler } from '@nuxt/kit'
1+
import { defineNuxtModule, addPlugin, createResolver, addImportsDir, addServerHandler, findPath } from '@nuxt/kit'
22
import { sha256 } from 'ohash'
33
import { defu } from 'defu'
4+
import { resolve } from 'pathe'
45

56
// Module options TypeScript interface definition
67
export interface ModuleOptions {}
@@ -12,7 +13,7 @@ export default defineNuxtModule<ModuleOptions>({
1213
},
1314
// Default configuration options of the Nuxt module
1415
defaults: {},
15-
setup (options, nuxt) {
16+
async setup (options, nuxt) {
1617
const resolver = createResolver(import.meta.url)
1718

1819
if (!process.env.NUXT_SESSION_PASSWORD) {
@@ -22,6 +23,11 @@ export default defineNuxtModule<ModuleOptions>({
2223
process.env.NUXT_SESSION_PASSWORD = randomPassword
2324
}
2425

26+
// Allow user to define custom session/user
27+
nuxt.options.watch.push('app/auth-utils-session.ts', 'app/auth-utils-session.js', 'app/auth-utils-session.mjs')
28+
29+
nuxt.options.alias['#auth-utils-session'] = await findFirstExisting(nuxt.options._layers.map(layer => resolve(layer.config.srcDir || layer.cwd, 'app/auth-utils-session'))) || resolver.resolve('./runtime/app/auth-utils-session')
30+
2531
// App
2632
addImportsDir(resolver.resolve('./runtime/composables'))
2733
addPlugin(resolver.resolve('./runtime/plugins/session.server'))
@@ -62,3 +68,10 @@ export default defineNuxtModule<ModuleOptions>({
6268
})
6369
}
6470
})
71+
72+
async function findFirstExisting (paths: string[]) {
73+
for (const path of paths) {
74+
const resolvedPath = await findPath(path)
75+
if (resolvedPath) { return resolvedPath }
76+
}
77+
}

src/runtime/app/auth-utils-session.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { defineSession } from '../server/utils/session'
2+
3+
// Default session provider (only used when user does not provide their own provider or `onSuccess` handler)
4+
export default defineSession((event, { provider, user }) => ({
5+
user: {
6+
[provider]: user
7+
},
8+
loggedInAt: new Date()
9+
}))

src/runtime/composables/session.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useState, computed, useRequestFetch } from '#imports'
2-
// import { UserSession } from '../server/utils/session'
3-
interface UserSession {}
2+
import type { default as UserSessionFactory } from '#auth-utils-session'
3+
type UserSession = ReturnType<typeof UserSessionFactory>
44

5-
const useSessionState = () => useState<UserSession>('nuxt-session', () => ({}))
5+
const useSessionState = () => useState<UserSession | Record<string, unknown>>('nuxt-session', () => ({}))
66

77
export const useUserSession = () => {
88
const sessionState = useSessionState()

src/runtime/server/lib/oauth/github.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from
33
import { ofetch } from 'ofetch'
44
import { withQuery } from 'ufo'
55
import { defu } from 'defu'
6+
import { default as createUserSession } from '#auth-utils-session'
7+
import { setUserSession } from '../../utils/session'
68

79
export interface OAuthGitHubConfig {
810
/**
@@ -43,7 +45,7 @@ export interface OAuthGitHubConfig {
4345

4446
interface OAuthConfig {
4547
config?: OAuthGitHubConfig
46-
onSuccess: (event: H3Event, result: { user: any, tokens: any }) => Promise<void> | void
48+
onSuccess?: (event: H3Event, result: { user: any, tokens: any }) => Promise<void> | void
4749
onError?: (event: H3Event, error: H3Error) => Promise<void> | void
4850
}
4951

@@ -127,6 +129,12 @@ export function githubEventHandler({ config, onSuccess, onError }: OAuthConfig)
127129
user.email = primaryEmail.email
128130
}
129131

132+
if (!onSuccess) {
133+
const session = await createUserSession(event, { provider: 'github', user, tokens })
134+
await setUserSession(event, session)
135+
return sendRedirect(event, '/')
136+
}
137+
130138
return onSuccess(event, {
131139
user,
132140
tokens,

src/runtime/server/lib/oauth/spotify.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from
33
import { withQuery, parsePath } from 'ufo'
44
import { ofetch } from 'ofetch'
55
import { defu } from 'defu'
6+
import { default as createUserSession } from '#auth-utils-session'
67

78
export interface OAuthSpotifyConfig {
89
/**
@@ -43,7 +44,7 @@ export interface OAuthSpotifyConfig {
4344

4445
interface OAuthConfig {
4546
config?: OAuthSpotifyConfig
46-
onSuccess: (event: H3Event, result: { user: any, tokens: any }) => Promise<void> | void
47+
onSuccess?: (event: H3Event, result: { user: any, tokens: any }) => Promise<void> | void
4748
onError?: (event: H3Event, error: H3Error) => Promise<void> | void
4849
}
4950

@@ -118,6 +119,12 @@ export function spotifyEventHandler({ config, onSuccess, onError }: OAuthConfig)
118119
}
119120
})
120121

122+
if (!onSuccess) {
123+
const session = await createUserSession(event, { provider: 'spotify', user, tokens })
124+
await setUserSession(event, session)
125+
return sendRedirect(event, '/')
126+
}
127+
121128
return onSuccess(event, {
122129
tokens,
123130
user

src/runtime/server/utils/session.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import type { H3Event } from 'h3'
22
import { useSession, createError } from 'h3'
33
import { defu } from 'defu'
44
import { useRuntimeConfig } from '#imports'
5+
import type { default as UserSessionFactory } from '#auth-utils-session'
6+
type UserSession = ReturnType<typeof UserSessionFactory>
57

6-
export interface UserSession {
7-
user?: any
8-
[key: string]: any
9-
}
8+
export const defineSession = <T extends Record<string, unknown> & { user?: unknown }>(definition: (event: H3Event, result: { provider: string, user: any, tokens: any }) => T) => definition
109

1110
export async function getUserSession (event: H3Event) {
1211
return (await _useSession(event)).data as UserSession

0 commit comments

Comments
 (0)