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 LOCALE_CODES = ['ru', 'en'] as const function resolveLocalizedPath(pathname: string) { const trimmed = pathname === '/' ? '/' : pathname.replace(/\/+$/, '') || '/' const segments = trimmed.split('/').filter(Boolean) const firstSegment = segments[0] if (firstSegment && LOCALE_CODES.includes(firstSegment as (typeof LOCALE_CODES)[number])) { const normalized = `/${segments.slice(1).join('/')}` || '/' const normalizedPath = normalized === '//' ? '/' : normalized return { localePrefix: `/${firstSegment}`, normalizedPath: normalizedPath || '/', } } return { localePrefix: '', normalizedPath: trimmed, } } 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 || process.env.LOGTO_ENDPOINT || 'https://auth.optovia.ru' const appId = process.env.NUXT_LOGTO_APP_ID || process.env.LOGTO_APP_ID || process.env.LOGTO_CLIENT_ID || '' const appSecret = process.env.NUXT_LOGTO_APP_SECRET || process.env.LOGTO_APP_SECRET || process.env.LOGTO_CLIENT_SECRET || '' const url = getRequestURL(event) const { localePrefix, normalizedPath } = resolveLocalizedPath(url.pathname) if (!appId || !appSecret) { if (normalizedPath === '/sign-in' || normalizedPath === '/sign-out' || normalizedPath === '/callback') { await sendRedirect(event, localePrefix || '/', 302) } return } 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 (normalizedPath === '/sign-in') { const callbackPath = `${localePrefix}/callback` await logto.signIn({ redirectUri: new URL(callbackPath || '/callback', url).href }) return } if (normalizedPath === '/sign-out') { const homePath = localePrefix || '/' await logto.signOut(new URL(homePath, url).href) return } if (normalizedPath === '/callback') { await logto.handleSignInCallback(url.href) const clientareaPath = `${localePrefix}/clientarea` await sendRedirect(event, clientareaPath || '/clientarea', 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 } })