diff --git a/express/middleware/session_middleware.js b/express/middleware/session_middleware.js index 97ed1af4..2b72df31 100644 --- a/express/middleware/session_middleware.js +++ b/express/middleware/session_middleware.js @@ -1,14 +1,28 @@ 'use strict'; const { SESSION_COOKIE_NAME, ADMIN_SESSION_COOKIE_NAME } = require('./../constants'); const SessionStorage = require("../session/session_storage"); +const jwt = require('jsonwebtoken'); +const { extension } = require('../extension'); +const logger = require('../logger'); + function sessionMiddleware(strict) { return async (req, res, next) => { try { const companyId = req.headers['x-company-id'] || req.query['company_id']; - const compCookieName = `${SESSION_COOKIE_NAME}_${companyId}` - let sessionId = req.signedCookies[compCookieName]; - req.fdkSession = await SessionStorage.getSession(sessionId); + const compCookieName = `${SESSION_COOKIE_NAME}_${companyId}`; + const token = req.cookies[compCookieName]; + + req.fdkSession = null; + if (token) { + try { + const decoded = jwt.verify(token, extension.api_secret); + const sessionId = decoded.id; + req.fdkSession = await SessionStorage.getSession(sessionId); + } catch (err) { + logger.debug(`Error verifying JWT: ${err.message}`); + } + } if(strict && !req.fdkSession) { return res.status(401).json({ "message": "unauthorized" }); diff --git a/express/routes.js b/express/routes.js index da708673..fb3b174c 100644 --- a/express/routes.js +++ b/express/routes.js @@ -8,6 +8,7 @@ const { SESSION_COOKIE_NAME, ADMIN_SESSION_COOKIE_NAME } = require('./constants' const { sessionMiddleware, partnerSessionMiddleware } = require('./middleware/session_middleware'); const logger = require('./logger'); const urljoin = require('url-join'); +const jwt = require('jsonwebtoken'); const FdkRoutes = express.Router(); @@ -20,10 +21,51 @@ function setupRoutes(ext) { // ?company_id=1&client_id=123313112122 try { let companyId = parseInt(req.query.company_id); - let platformConfig = await ext.getPlatformConfig(companyId); - let session; + const compCookieName = `${SESSION_COOKIE_NAME}_${companyId}`; + const token = req.cookies[compCookieName]; + let session = null; + + if (token) { + try { + const decoded = jwt.verify(token, ext.api_secret); + const sessionId = decoded.id; + session = await SessionStorage.getSession(sessionId); + } catch (err) { + logger.debug(`Error verifying JWT during install: ${err.message}`); + } + } + let redirectPath = req.query.redirect_path; + if (session && session.expires && new Date() < new Date(session.expires)) { + if (redirectPath) { + session.redirect_path = redirectPath; + } + req.fdkSession = session; + req.extension = ext; + await ext.getPlatformClient(companyId, session); + await SessionStorage.saveSession(session); + const expiresIn = Math.floor((new Date(session.expires) - new Date()) / 1000); + const jwtToken = jwt.sign({ id: session.id }, ext.api_secret, { expiresIn: expiresIn > 0 ? expiresIn : 0 }); + res.cookie(compCookieName, jwtToken, { + secure: true, + httpOnly: true, + expires: new Date(session.expires), + signed: false, + sameSite: "None", + partitioned: true + }); + res.header['x-company-id'] = companyId; + let redirectUrl = await ext.callbacks.auth(req); + if (req.fdkSession.redirect_path) { + redirectUrl = req.fdkSession.redirect_path; + } + logger.debug(`Redirecting with existing session to url: ${redirectUrl}`); + return res.redirect(redirectUrl); + } + + + let platformConfig = await ext.getPlatformConfig(companyId); session = new Session(Session.generateSessionId(true)); let sessionExpires = new Date(Date.now() + 900000); // 15 min @@ -44,13 +86,13 @@ function setupRoutes(ext) { req.fdkSession = session; req.extension = ext; - const compCookieName = `${SESSION_COOKIE_NAME}_${companyId}` res.header['x-company-id'] = companyId; - res.cookie(compCookieName, session.id, { + const tempJwt = jwt.sign({ id: session.id }, ext.api_secret, { expiresIn: '15m' }); + res.cookie(compCookieName, tempJwt, { secure: true, httpOnly: true, expires: session.expires, - signed: true, + signed: false, sameSite: "None", partitioned: true }); @@ -135,12 +177,13 @@ function setupRoutes(ext) { } - const compCookieName = `${SESSION_COOKIE_NAME}_${companyId}` - res.cookie(compCookieName, req.fdkSession.id, { + const compCookieName = `${SESSION_COOKIE_NAME}_${companyId}`; + const jwtToken = jwt.sign({ id: req.fdkSession.id }, ext.api_secret, { expiresIn: req.fdkSession.expires_in }); + res.cookie(compCookieName, jwtToken, { secure: true, httpOnly: true, expires: sessionExpires, - signed: true, + signed: false, sameSite: "None", partitioned: true }); diff --git a/express/session/session.js b/express/session/session.js index c12cffa5..dc45c332 100644 --- a/express/session/session.js +++ b/express/session/session.js @@ -24,6 +24,9 @@ class Session { static cloneSession(id, session, isNew=true) { let newSession = new Session(id, isNew); Object.assign(newSession, session); + if (newSession.expires) { + newSession.expires = new Date(newSession.expires); + } return newSession; } diff --git a/package-lock.json b/package-lock.json index 28423167..cfb5e1c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "axios": "^1.6.4", "crypto-js": "^4.2.0", + "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", "querystring": "^0.2.1", "url-join": "^4.0.1", @@ -897,6 +898,12 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", @@ -1504,6 +1511,15 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2666,6 +2682,46 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -2687,6 +2743,27 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -2733,12 +2810,54 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, "node_modules/lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", "dev": true }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -5301,6 +5420,11 @@ "update-browserslist-db": "^1.0.13" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", @@ -5767,6 +5891,14 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6631,6 +6763,35 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==" + } + } + }, "jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -6649,6 +6810,25 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, + "jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "requires": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -6692,12 +6872,47 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", "dev": true }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", diff --git a/package.json b/package.json index 29fb26d6..bc6cf625 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "dependencies": { "axios": "^1.6.4", "crypto-js": "^4.2.0", + "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", "querystring": "^0.2.1", "url-join": "^4.0.1", diff --git a/spec/tests/auth.spec.js b/spec/tests/auth.spec.js new file mode 100644 index 00000000..7013ea46 --- /dev/null +++ b/spec/tests/auth.spec.js @@ -0,0 +1,81 @@ +'use strict'; + +const fdkHelper = require("../helpers/fdk"); +const { clearData, getOfflineSession, saveOfflineSession } = require("../helpers/setup_db"); +const request = require("../helpers/server")(5073); +const axiosMock = require("./../mocks/axios.mock.js"); +const { SESSION_COOKIE_NAME } = require("../../express/constants"); +const { extension } = require("../../express/extension"); +const Session = require("../../express/session/session"); +const SessionStorage = require("../../express/session/session_storage"); +const jwt = require('jsonwebtoken'); + +describe("Auth Integrations", () => { + let fdk_instance; + let cookie = ""; + + beforeEach(async () => { + fdk_instance = await fdkHelper({ + access_mode: "online", + callbacks: { + auth: async (req) => { + return "http://localdev.fyndx0.de/test-page"; + }, + uninstall: async (req) => {}, + } + }); + request.app.restApp.use(fdk_instance.fdkHandler); + }); + + afterEach(async () => { + await clearData(); + fdk_instance.extension._isInitialized = false; + }); + + afterAll(() => { + axiosMock.reset(); + request.app.shutdown(); + }); + + it("should redirect to auth url for new install", async () => { + const response = await request.get('/fp/install?company_id=1'); + expect(response.status).toBe(302); + + const location = response.headers['location']; + expect(location).toContain("/service/panel/authentication/v1.0/company/1/oauth/authorize"); + + const cookieHeader = response.headers['set-cookie'][0]; + const cookieValue = cookieHeader.split(';')[0].split('=')[1]; + + const decoded = jwt.verify(cookieValue, 'API_SECRET'); + expect(decoded.id).toBeDefined(); + }); + + it("should reuse existing session and redirect to auth callback", async () => { + // 1. Create a valid session + const companyId = 1; + const sessionId = "test_session_id"; + const session = new Session(sessionId, false); + session.company_id = companyId; + session.scope = ["company/products"]; + session.expires = new Date(Date.now() + 1000 * 60 * 15); // 15 minutes from now + session.access_token = "test_access_token"; + session.access_mode = "online"; + session.extension_id = "API_KEY"; + + await SessionStorage.saveSession(session); + + // 2. Create a JWT for the session + const jwtToken = jwt.sign({ id: sessionId }, 'API_SECRET', { expiresIn: '15m' }); + + // 3. Make a request with the JWT cookie + const compCookieName = `${SESSION_COOKIE_NAME}_${companyId}`; + const response = await request.get(`/fp/install?company_id=${companyId}`) + .set('Cookie', `${compCookieName}=${jwtToken}`); + + // 4. Assert the redirect + expect(response.status).toBe(302); + const location = response.headers['location']; + expect(location).toBe("http://localdev.fyndx0.de/test-page"); // Should redirect to the auth callback directly + }); +});