Skip to content
This repository has been archived by the owner on Dec 27, 2024. It is now read-only.

Commit

Permalink
refactor: reorganize api route handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ZerNico committed May 10, 2023
1 parent 9ffe460 commit dc92a1a
Show file tree
Hide file tree
Showing 13 changed files with 231 additions and 210 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export default defineNuxtConfig({
baseUrl: '<your-nextjs-app-base-url>', // E.g. http://localhost:3000
cookieSecret: 'complex_password_at_least_32_characters_long',
cookieSecure: process.env.NODE_ENV === 'production',
cookieHttpOnly: true,
resources: ['<your-resource-id>'], // optionally add a resource
},
})
Expand Down
1 change: 0 additions & 1 deletion playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export default defineNuxtConfig({
baseUrl: '<your-nextjs-app-base-url>', // E.g. http://localhost:3000
cookieSecret: 'complex_password_at_least_32_characters_long',
cookieSecure: process.env.NODE_ENV === 'production',
cookieHttpOnly: true,
resources: ['<your-resource-id>'], // optionally add a resource
},
})
5 changes: 2 additions & 3 deletions src/module.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { defineNuxtModule, addPlugin, createResolver, addTemplate, addImports } from '@nuxt/kit'
import { defu } from 'defu'
import { ModuleOptions } from './runtime/types'
import { LogtoNuxtConfig } from './runtime/types'

export default defineNuxtModule<ModuleOptions>({
export default defineNuxtModule<LogtoNuxtConfig>({
meta: {
name: '@zernico/nuxt-logto',
configKey: 'logto',
Expand All @@ -17,7 +17,6 @@ export default defineNuxtModule<ModuleOptions>({
},
{
coookieSecure: true,
cookieHttpOnly: true,
}
)

Expand Down
45 changes: 0 additions & 45 deletions src/runtime/client.ts

This file was deleted.

10 changes: 5 additions & 5 deletions src/runtime/composables/useLogto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { ComputedRef } from 'nuxt/dist/app/compat/vue-demi'

export interface UseLogtoOptions {
/**
* The URL to fetch the user info from.
* The URL to fetch the user context from.
*
* @default '/api/logto/user'
* @default '/api/logto/context'
*/
userUrl?: string
contextUrl?: string
}

export interface UseLogtoReturn {
Expand All @@ -31,9 +31,9 @@ export interface UseLogtoReturn {
* @returns An object containing various properties and methods related to Logto user info.
*/
export const useLogto = (options?: UseLogtoOptions): UseLogtoReturn => {
const { userUrl = '/api/logto/user' } = options || {}
const { contextUrl = '/api/logto/context' } = options || {}

const { data, refresh, pending } = useFetch<LogtoContext>(userUrl)
const { data, refresh, pending } = useFetch<LogtoContext>(contextUrl)

/**
* Whether the user is authenticated.
Expand Down
124 changes: 1 addition & 123 deletions src/runtime/index.ts
Original file line number Diff line number Diff line change
@@ -1,123 +1 @@
import { eventHandler, sendRedirect, createError, H3Event } from 'h3'
import LogtoNuxtBaseClient from './client'
import NodeClient, { GetContextParameters, type InteractionMode } from '@logto/node'
import { useRuntimeConfig } from '#imports'

export class LogtoClient extends LogtoNuxtBaseClient {
constructor() {
const config = useRuntimeConfig().logto
super(config, { NodeClient })
}

/**
* Api route handler to handle sign in.
* @param redirectUri - The uri to redirect to after sign in. Should be pointed to a handleSignInCallback route.
* @param interactionMode - The interaction mode to use for sign in.
* - `signIn`: Sign in the user.
* - `signUp`: Sign up the user.
*/
handleSignIn = (
redirectUri = `${this.config.baseUrl}/api/logto/sign-in-callback`,
interactionMode?: InteractionMode
) =>
eventHandler(async (event) => {
const session = await this.getIronSession(event.node.req, event.node.res)
const nodeClient = this.createNodeClient(session)
await nodeClient.signIn(redirectUri, interactionMode)
await this.storage?.save()

if (this.navigateUrl) {
return sendRedirect(event, this.navigateUrl)
}
})

/**
* Api route handler to handle sign in callback.
* @param redirectTo - The uri to redirect to after sign in.
*/
handleSignInCallback = (redirectTo = this.config.baseUrl) =>
eventHandler(async (event) => {
const session = await this.getIronSession(event.node.req, event.node.res)
const nodeClient = this.createNodeClient(session)

const url = event.node.req.url

if (url) {
await nodeClient.handleSignInCallback(`${this.config.baseUrl}${url}`)
await this.storage?.save()
return sendRedirect(event, redirectTo)
}
})

/**
* Api route handler to handle sign out.
* @param redirectUri - The uri to redirect to after sign out.
*/
handleSignOut = (redirectUri = this.config.baseUrl) =>
eventHandler(async (event) => {
const session = await this.getIronSession(event.node.req, event.node.res)
const nodeClient = this.createNodeClient(session)
await nodeClient.signOut(redirectUri)

session.destroy()
await this.storage?.save()

if (this.navigateUrl) {
return sendRedirect(event, this.navigateUrl)
}
})

/**
* Api route handler to get the user info.
* @param config - The config to use for getting the user info.
* @returns The auth state, claims, user info and access token.
*/
handleUser = (config: GetContextParameters = {}) =>
eventHandler(async (event) => {
const user = this.getUser(event, config)
return user
})

/**
* Api route handler to handle all auth routes.
* @param config - The config to use for getting the user info.
*/
handleAuthRoutes = (config?: GetContextParameters) =>
eventHandler(async (event) => {
const action = event.context.params?.action

if (action === 'sign-in') {
return this.handleSignIn()(event)
}

if (action === 'sign-up') {
return this.handleSignIn(undefined, 'signUp')(event)
}

if (action === 'sign-in-callback') {
return this.handleSignInCallback()(event)
}

if (action === 'sign-out') {
return this.handleSignOut()(event)
}

if (action === 'user') {
return this.handleUser(config)(event)
}

throw createError({ statusCode: 404, statusMessage: 'Not Found' })
})

/**
* Get the user info.
* @param event - The api handler event.
* @param config - The config to use for getting the user info.
* @returns The auth state, claims, user info and access token.
*/
getUser = async (event: H3Event, config: GetContextParameters = {}) => {
const session = await this.getIronSession(event.node.req, event.node.res)
const user = await this.getLogtoUserFromRequest(session, config)
return user
}
}
export * from './logto/client'
59 changes: 29 additions & 30 deletions src/runtime/index.test.ts → src/runtime/logto/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
import { afterEach, describe, vi, it, expect, beforeEach } from 'vitest'
import { LogtoClient } from './index'
import type { ModuleOptions } from './types.js'
import { App, EventHandler, Router, createApp, createRouter, toNodeListener } from 'h3'
import supertest, { SuperTest, Test } from 'supertest'
import { type InteractionMode } from '@logto/node'
import { LogtoClient } from './client'
import { LogtoNuxtConfig } from "../types"

const signInUrl = 'http://mock-logto-server.com/sign-in'

const config: ModuleOptions = {
const config: LogtoNuxtConfig = {
appId: 'app_id_value',
endpoint: 'https://logto.dev',
baseUrl: 'http://localhost:3000',
cookieHttpOnly: true,
cookieSecret: 'complex_password_at_least_32_characters_long',
cookieSecure: process.env.NODE_ENV === 'production',
}

const setItem = vi.fn()
const getItem = vi.fn()
const save = vi.fn()
const signIn = vi.fn()
const handleSignInCallback = vi.fn()
const signOut = vi.fn()
const getContext = vi.fn()
const signInUrl = 'http://mock-logto-server.com/sign-in'

vi.mock('#imports', () => ({
useRuntimeConfig: () => {
return {
logto: config,
}
},
}))

vi.mock('./storage', () => ({
default: class Storage {
Expand All @@ -33,18 +32,18 @@ vi.mock('./storage', () => ({
},
}))

vi.mock('#imports', () => ({
useRuntimeConfig: () => {
return {
logto: config,
}
},
}))

type Adapter = {
navigate: (url: string) => void
}

const setItem = vi.fn()
const getItem = vi.fn()
const save = vi.fn()
const signIn = vi.fn()
const handleSignInCallback = vi.fn()
const signOut = vi.fn()
const getContext = vi.fn()

vi.mock('@logto/node', () => ({
default: class NodeClient {
navigate: (url: string) => void
Expand Down Expand Up @@ -76,7 +75,7 @@ describe('Nuxt LogtoClient', () => {
afterEach(() => {
vi.resetAllMocks()
})

it('creates an instance without crash', () => {
expect(() => new LogtoClient()).not.toThrow()
})
Expand Down Expand Up @@ -131,11 +130,11 @@ describe('Nuxt LogtoClient', () => {
})
})

describe('handleUser', () => {
describe('handleContext', () => {
it('should call getContext', async () => {
const client = new LogtoClient()
app.use('/api/logto/user', client.handleUser())
await request.get('/api/logto/user')
app.use('/api/logto/context', client.handleContext())
await request.get('/api/logto/context')

expect(getContext).toHaveBeenCalled()
})
Expand Down Expand Up @@ -190,18 +189,18 @@ describe('Nuxt LogtoClient', () => {
expect(client.handleSignOut).toHaveBeenCalled()
})

it('should call handleUser', async () => {
it('should call handleContext', async () => {
const client = new LogtoClient()

const mockEventHandler: EventHandler = async () => {
return 'mock response'
}
vi.spyOn(client, 'handleUser').mockImplementation(() => mockEventHandler)
vi.spyOn(client, 'handleContext').mockImplementation(() => mockEventHandler)
router.get('/api/logto/:action', client.handleAuthRoutes())
app.use(router)
await request.get('/api/logto/user')
await request.get('/api/logto/context')

expect(client.handleUser).toHaveBeenCalled()
expect(client.handleContext).toHaveBeenCalled()
})
})
})
})
Loading

0 comments on commit dc92a1a

Please sign in to comment.