import LogtoClient, { CookieStorage } from '@logto/node' import { getRequestURL, getCookie, setCookie, sendRedirect, type H3Event } from 'h3' import { randomUUID } from 'crypto' const SESSION_COOKIE_NAME = 'logtoSession' const LEGACY_COOKIE_NAME = 'logtoCookies' const SCOPES = [ 'openid', 'profile', 'email', 'offline_access', 'urn:logto:scope:organizations', 'urn:logto:scope:organization_token', 'teams:member' ] const RESOURCES = [ 'https://teams.optovia.ru', 'https://orders.optovia.ru', 'https://kyc.optovia.ru', 'https://exchange.optovia.ru', 'https://billing.optovia.ru' ] const createSessionWrapper = (event: H3Event) => { const storage = useStorage('logto') let currentSessionId = '' return { wrap: async (data: Record) => { if (!data || Object.keys(data).length === 0) { if (currentSessionId) { await storage.removeItem(`session:${currentSessionId}`) } currentSessionId = '' return '' } if (!currentSessionId) { currentSessionId = randomUUID() } await storage.setItem(`session:${currentSessionId}`, data) return currentSessionId }, unwrap: async (value: string) => { currentSessionId = value || '' if (!currentSessionId) return {} const stored = await storage.getItem>(`session:${currentSessionId}`) if (!stored) { // Session ID exists in cookie but data not found in storage - clear the stale cookie setCookie(event, SESSION_COOKIE_NAME, '', { path: '/', maxAge: 0 }) currentSessionId = '' return {} } return stored } } } export default defineEventHandler(async (event) => { const endpoint = process.env.NUXT_LOGTO_ENDPOINT || 'https://auth.optovia.ru' const appId = process.env.NUXT_LOGTO_APP_ID || '' const appSecret = process.env.NUXT_LOGTO_APP_SECRET || '' if (!appId || !appSecret) { return } const url = getRequestURL(event) if (getCookie(event, LEGACY_COOKIE_NAME)) { setCookie(event, LEGACY_COOKIE_NAME, '', { path: '/', maxAge: 0 }) } // Check for stale session cookie BEFORE initializing CookieStorage const existingSessionId = getCookie(event, SESSION_COOKIE_NAME) if (existingSessionId) { const nitroStorage = useStorage('logto') const sessionData = await nitroStorage.getItem(`session:${existingSessionId}`) if (!sessionData) { // Session cookie exists but no data in storage - clear it setCookie(event, SESSION_COOKIE_NAME, '', { path: '/', maxAge: 0 }) } } const storage = new CookieStorage({ cookieKey: SESSION_COOKIE_NAME, isSecure: process.env.NODE_ENV === 'production', sessionWrapper: createSessionWrapper(event), getCookie: async (name) => getCookie(event, name), setCookie: async (name, value, options) => { setCookie(event, name, value, options) } }) await storage.init() const logto = new LogtoClient( { endpoint, appId, appSecret, resources: RESOURCES, scopes: SCOPES }, { navigate: async (target) => { await sendRedirect(event, target, 302) }, storage } ) if (url.pathname === '/sign-in') { await logto.signIn({ redirectUri: new URL('/callback', url).href }) return } if (url.pathname === '/sign-out') { await logto.signOut(new URL('/', url).href) return } if (url.pathname === '/callback') { await logto.handleSignInCallback(url.href) await sendRedirect(event, '/', 302) return } event.context.logtoClient = logto if (await logto.isAuthenticated()) { try { event.context.logtoUser = await logto.fetchUserInfo() } catch { try { event.context.logtoUser = await logto.getIdTokenClaims() } catch { event.context.logtoUser = undefined } } } const orgId = (event.context.logtoUser as { organizations?: string[] } | undefined)?.organizations?.[0] if (orgId) { event.context.logtoOrgId = orgId } })