diff --git a/src/session-adapters/cookieAdapter.ts b/src/session-adapters/cookieAdapter.ts new file mode 100644 index 0000000..bb158ad --- /dev/null +++ b/src/session-adapters/cookieAdapter.ts @@ -0,0 +1,38 @@ +import { expireCookie, getCookie, setCookie, verifyData } from '../utils' +import jwt from 'jsonwebtoken' +import { type Adapter } from '../storyblok-auth-api/AuthHandlerParams' + +const clientSecret = process.env['CLIENT_SECRET'] || '' + +export const cookieAdapter: Adapter = { + getItem: ({ req, key }) => { + const cookie = getCookie(req, key) + + if (!cookie) { + return undefined + } + + //TODO: fix typing + return verifyData(clientSecret)(cookie) as object + }, + setItem: ({ req, res, key, value }) => { + const currentSession = cookieAdapter.getItem({ req, res, key }) + + // TODO: Improve readability + // TODO: improve checking as user might select their own key and not sb.auth + const isSession = key === 'sb.auth' + const data = isSession + ? { + sessions: currentSession ? [currentSession, value] : [value], + } + : value + + const signedData = jwt.sign({ data }, clientSecret) + setCookie(res, key, signedData) + }, + hasItem: ({ req, res, key }) => + cookieAdapter.getItem({ req, res, key }) !== undefined, + removeItem: ({ res, key }) => { + expireCookie(res, key) + }, +} diff --git a/src/storyblok-auth-api/session-adapters/createInternalAdapter.ts b/src/session-adapters/createInternalAdapter.ts similarity index 86% rename from src/storyblok-auth-api/session-adapters/createInternalAdapter.ts rename to src/session-adapters/createInternalAdapter.ts index 652aea2..8e5f917 100644 --- a/src/storyblok-auth-api/session-adapters/createInternalAdapter.ts +++ b/src/session-adapters/createInternalAdapter.ts @@ -1,5 +1,8 @@ import http from 'http' -import { Adapter, InternalAdapter } from '../AuthHandlerParams' +import { + Adapter, + InternalAdapter, +} from '../storyblok-auth-api/AuthHandlerParams' export const createInternalAdapter = ({ req, diff --git a/src/session/sessionCookieStore.ts b/src/session/sessionCookieStore.ts index 35e0f62..4446db6 100644 --- a/src/session/sessionCookieStore.ts +++ b/src/session/sessionCookieStore.ts @@ -2,8 +2,8 @@ import { AppSessionCookieStoreFactory, AppSessionStore } from './types' import { getAllSessions, getSession, putSession, removeSession } from './crud' import { refreshStoredAppSession } from './refreshStoredAppSession' import { GetCookie, SetCookie } from '../utils' -import { createInternalAdapter } from '../storyblok-auth-api/session-adapters/createInternalAdapter' -import { cookieAdapter } from '../storyblok-auth-api/session-adapters/cookieAdapter' +import { createInternalAdapter } from '../session-adapters/createInternalAdapter' +import { cookieAdapter } from '../session-adapters/cookieAdapter' export const sessionCookieStore: AppSessionCookieStoreFactory = (params) => diff --git a/src/storyblok-auth-api/auth-handler.ts b/src/storyblok-auth-api/auth-handler.ts index 0a802b5..70bd5b6 100644 --- a/src/storyblok-auth-api/auth-handler.ts +++ b/src/storyblok-auth-api/auth-handler.ts @@ -2,8 +2,8 @@ import http from 'http' import { AuthHandlerParams } from './AuthHandlerParams' import { handleAnyRequest } from './handle-requests' import { reconcileNodeResponse } from './reconcileNodeResponse' -import { createInternalAdapter } from './session-adapters/createInternalAdapter' -import { cookieAdapter } from './session-adapters/cookieAdapter' +import { createInternalAdapter } from '../session-adapters/createInternalAdapter' +import { cookieAdapter } from '../session-adapters/cookieAdapter' /** * Auth handler for Node.js diff --git a/src/storyblok-auth-api/session-adapters/cookieAdapter.ts b/src/storyblok-auth-api/session-adapters/cookieAdapter.ts deleted file mode 100644 index 73ddaa9..0000000 --- a/src/storyblok-auth-api/session-adapters/cookieAdapter.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { ServerResponse } from 'node:http' -import { verifyData } from '../../utils' -import jwt from 'jsonwebtoken' -import { type Adapter } from '../AuthHandlerParams' - -const REGEXP_ESCAPE_CHARS_REGEXP = /[\^$\\.*+?()[\]{}|]/g - -const getPattern = (name: string) => - new RegExp( - '(?:^|;) *' + name.replace(REGEXP_ESCAPE_CHARS_REGEXP, '\\$&') + '=([^;]*)', - ) - -const withCookie = ( - headers: string[], - name: string, - value: string, -): string[] => [ - ...headers.filter((header) => !header.startsWith(`${name}=`)), - changedCookieHeaderValue(name, value), -] - -const cookieHeaders = (res: ServerResponse): string[] => { - const header = res.getHeader('Set-Cookie') - if (typeof header === 'undefined') { - return [] - } - if (typeof header == 'number') { - return [header.toString(10)] - } - if (typeof header == 'string') { - return [header] - } - return header -} - -const changedCookieHeaderValue = (name: string, value: string) => - `${name}=${value}; path=/; samesite=none; secure; httponly; partitioned;` - -const expiredCookieHeaderValue = (name: string) => - `${name}=""; path=/; samesite=none; secure; httponly; expires=Thu, 01 Jan 1970 00:00:00 GMT; partitioned;` - -const withExpiredCookie = (headers: string[], name: string): string[] => [ - ...headers.filter((header) => !header.startsWith(`${name}=`)), - expiredCookieHeaderValue(name), -] - -const clientSecret = process.env['CLIENT_SECRET'] || '' - -export const cookieAdapter: Adapter = { - getItem: ({ req, key }) => { - const header = req.headers['cookie'] - - if (!header) { - return undefined - } - - const match = header.match(getPattern(key)) - if (!match) { - return undefined - } - - const value = match[1] - - // TODO: verifyData correct typing - if (value[0] === '"') { - return verifyData(clientSecret)(value.slice(1, -1)) as object - } - return verifyData(clientSecret)(value) as object - }, - setItem: ({ req, res, key, value }) => { - const currentSession = cookieAdapter.getItem({ req, res, key }) - - //Add session to array - const data = - key === 'sb.auth' - ? { - sessions: currentSession ? [currentSession, value] : [value], - } - : value - - const signedData = withCookie( - cookieHeaders(res), - key, - jwt.sign({ data }, clientSecret), - ) - res.setHeader('Set-Cookie', signedData) - }, - hasItem: ({ req, res, key }) => - cookieAdapter.getItem({ req, res, key }) !== undefined, - removeItem: ({ res, key }) => { - res.setHeader('Set-Cookie', withExpiredCookie(cookieHeaders(res), key)) - }, -}