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: add a json pointer for the session object #392

Closed
Show file tree
Hide file tree
Changes from 4 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
12 changes: 12 additions & 0 deletions docs/content/v0.6/2.configuration/2.nuxt-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,18 @@ type ProviderLocal = {
* @advanced_array_example { id: 'string', email: 'string', name: 'string', role: 'admin | guest | account', subscriptions: "{ id: number, status: 'ACTIVE' | 'INACTIVE' }[]" }
*/
sessionDataType?: SessionDataObject,
/**
* How to extract the authentication-token from the sign-in response.
*
* E.g., setting this to `/data/user` and returning an object like `{ data: { user: { id:number, name: string } }, status: 'ok' }` from the `getSession` endpoint will
* storing the 'User' object typed as the type created via the 'sessionDataType' prop.
*
* This follows the JSON Pointer standard, see it's RFC6901 here: https://www.rfc-editor.org/rfc/rfc6901
*
* @default / Access the root of the session response object
* @example /data/user Access the `data/user` property of the session response object
*/
sessionDataResponseTokenPointer?: string
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can move this and the sessionDataType into the SessionConfig and then use it for all providers, right?

Defaults would just need to be slightly different per provider:

Tghis would be nice as it would unify behavior of all providers + make it way easier to type the return of the authjs provider (which was a big pain before). What do you think?

}
```
```ts [SessionConfig]
Expand Down
3 changes: 2 additions & 1 deletion playground-local/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export default defineNuxtConfig({
token: {
signInResponseTokenPointer: '/token/accessToken'
},
sessionDataType: { id: 'string', email: 'string', name: 'string', role: 'admin | guest | account', subscriptions: "{ id: number, status: 'ACTIVE' | 'INACTIVE' }[]" }
sessionDataType: { id: 'string', email: 'string', name: 'string', role: 'admin | guest | account', subscriptions: "{ id: number, status: 'ACTIVE' | 'INACTIVE' }[]" },
sessionDataResponseTokenPointer: '/data/user'
},
session: {
// Whether to refresh the session every time the browser window is refocused.
Expand Down
4 changes: 2 additions & 2 deletions playground-local/server/api/auth/login.post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export default eventHandler(async (event) => {
name: 'User ' + username
}

const accessToken = sign({ ...user, scope: ['test', 'user'] }, SECRET, { expiresIn })
const accessToken = sign({ data: { user }, scope: ['test', 'user'] }, SECRET, { expiresIn })
refreshTokens[refreshToken] = {
accessToken,
user
data: { user }
}

return {
Expand Down
3 changes: 2 additions & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const defaultsByBackend: { [key in SupportedAuthProviders]: DeepRequired<Extract
headerName: 'Authorization',
maxAgeInSeconds: 30 * 60
},
sessionDataType: { id: 'string | number' }
sessionDataType: { id: 'string | number' },
sessionDataResponseTokenPointer: '/'
},
authjs: {
type: 'authjs',
Expand Down
7 changes: 4 additions & 3 deletions src/runtime/composables/local/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { readonly, Ref } from 'vue'
import { callWithNuxt } from '#app'
import { CommonUseAuthReturn, SignOutFunc, SignInFunc, GetSessionFunc, SecondarySignInOptions } from '../../types'
import { _fetch } from '../../utils/fetch'
import { jsonPointerGet, useTypedBackendConfig } from '../../helpers'
import { extractObjectWithJsonPointer, useTypedBackendConfig } from '../../helpers'
import { getRequestURLWN } from '../../utils/callWithNuxt'
import { useAuthState } from './useAuthState'
// @ts-expect-error - #auth not defined
Expand All @@ -25,7 +25,7 @@ const signIn: SignInFunc<Credentials, any> = async (credentials, signInOptions,
params: signInParams ?? {}
})

const extractedToken = jsonPointerGet(response, config.token.signInResponseTokenPointer)
const extractedToken = extractObjectWithJsonPointer<string>(response, config.token.signInResponseTokenPointer)
if (typeof extractedToken !== 'string') {
console.error(`Auth: string token expected, received instead: ${JSON.stringify(extractedToken)}. Tried to find token at ${config.token.signInResponseTokenPointer} in ${JSON.stringify(response)}`)
return
Expand Down Expand Up @@ -80,7 +80,8 @@ const getSession: GetSessionFunc<SessionData | null | void> = async (getSessionO

loading.value = true
try {
data.value = await _fetch<SessionData>(nuxt, path, { method, headers })
const result = await _fetch<any>(nuxt, path, { method, headers })
data.value = extractObjectWithJsonPointer<SessionData>(result, config.sessionDataResponseTokenPointer)
} catch {
// Clear all data: Request failed so we must not be authenticated
data.value = null
Expand Down
13 changes: 9 additions & 4 deletions src/runtime/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ export const useTypedBackendConfig = <T extends SupportedAuthProviders>(runtimeC
* @param obj
* @param pointer
*/
export const jsonPointerGet = (obj: Record<string, any>, pointer: string): string | Record<string, any> => {
export const extractObjectWithJsonPointer = <TResult, T extends object = object>(obj: T, pointer?: string): TResult => {
let result: TResult = obj as unknown as TResult
if (!pointer || pointer === '/') {
return result
}
const unescape = (str: string) => str.replace(/~1/g, '/').replace(/~0/g, '~')
const parse = (pointer: string) => {
if (pointer === '') { return [] }
Expand All @@ -56,10 +60,11 @@ export const jsonPointerGet = (obj: Record<string, any>, pointer: string): strin

for (let i = 0; i < refTokens.length; ++i) {
const tok = refTokens[i]
if (!(typeof obj === 'object' && tok in obj)) {
if (!(typeof result === 'object' && result && tok in result)) {
throw new Error('Invalid reference token: ' + tok)
}
obj = obj[tok]
result = (result as any)[tok]
}
return obj

return result
}
17 changes: 14 additions & 3 deletions src/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface GlobalMiddlewareOptions {
isEnabled: boolean
/**
* Whether to enforce authentication if the target-route does not exist. Per default the middleware redirects
* to Nuxts' default 404 page instead of forcing a sign-in if the target does not exist. This is to avoid a
* to Nuxtjs default 404 page instead of forcing a sign-in if the target does not exist. This is to avoid a
* user-experience and developer-experience of having to sign-in only to see a 404 page afterwards.
*
* Note: Setting this to `false` this may lead to `vue-router` + node related warnings like: "Error [ERR_HTTP_HEADERS_SENT] ...",
Expand Down Expand Up @@ -133,7 +133,7 @@ type ProviderLocal = {
* Header name to be used in requests that need to be authenticated, e.g., to be used in the `getSession` request.
*
* @default Authorization
* @exmaple Auth
* @example Auth
*/
headerName?: string,
/**
Expand All @@ -151,8 +151,19 @@ type ProviderLocal = {
* @advanced_array_example { id: 'string', email: 'string', name: 'string', role: 'admin | guest | account', subscriptions: "{ id: number, status: 'ACTIVE' | 'INACTIVE' }[]" }
*/
sessionDataType?: SessionDataObject,
/**
* How to extract the authentication-token from the sign-in response.
*
* E.g., setting this to `/data/user` and returning an object like `{ data: { user: { id:number, name: string } }, status: 'ok' }` from the `getSession` endpoint will
* storing the 'User' object typed as the type created via the 'sessionDataType' prop.
*
* This follows the JSON Pointer standard, see it's RFC6901 here: https://www.rfc-editor.org/rfc/rfc6901
*
* @default / Access the root of the session response object
* @example /data/user Access the `data/user` property of the session response object
*/
sessionDataResponseTokenPointer?: string
}

/**
* Configuration for the `authjs`-provider.
*/
Expand Down