From 5da93f318aad10b1c67032a467191e4dbb99f411 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 28 Apr 2022 06:44:25 +0500 Subject: [PATCH 1/5] feat: enabled session based authentication for web --- api/.env.example | 4 + api/package-lock.json | 262 +++++++++++++++++++++- api/package.json | 3 + api/public/swagger.yaml | 59 +++++ api/src/app.ts | 22 +- api/src/controllers/session.ts | 2 +- api/src/controllers/web.ts | 75 +++++++ api/src/middlewares/authenticateToken.ts | 8 +- api/src/routes/api/auth.ts | 3 +- api/src/routes/web/web.ts | 55 ++--- api/src/types/Process.d.ts | 8 - api/src/types/Request.ts | 17 -- api/src/types/index.ts | 1 - api/src/types/system/express-session.d.ts | 14 ++ api/src/types/system/global.d.ts | 1 + api/src/types/system/process.d.ts | 8 + api/src/utils/connectDB.ts | 15 +- api/src/utils/validation.ts | 6 + api/tsoa.json | 4 + web/.env.example | 3 +- web/src/App.tsx | 2 +- web/src/components/login.tsx | 76 +++---- web/src/components/useTokens.ts | 94 -------- web/src/context/appContext.tsx | 128 +++-------- web/src/index.tsx | 12 + 25 files changed, 582 insertions(+), 300 deletions(-) create mode 100644 api/src/controllers/web.ts delete mode 100644 api/src/types/Process.d.ts delete mode 100644 api/src/types/Request.ts create mode 100644 api/src/types/system/express-session.d.ts create mode 100644 api/src/types/system/global.d.ts create mode 100644 api/src/types/system/process.d.ts delete mode 100644 web/src/components/useTokens.ts diff --git a/api/.env.example b/api/.env.example index f36cf34..0a4fecc 100644 --- a/api/.env.example +++ b/api/.env.example @@ -1,13 +1,17 @@ MODE=[desktop|server] default considered as desktop CORS=[disable|enable] default considered as disable for server MODE & enable for desktop MODE WHITELIST= + PROTOCOL=[http|https] default considered as http PRIVATE_KEY=privkey.pem FULL_CHAIN=fullchain.pem + PORT=[5000] default value is 5000 + ACCESS_TOKEN_SECRET= REFRESH_TOKEN_SECRET= AUTH_CODE_SECRET= +SESSION_SECRET= DB_CONNECT=mongodb+srv://:@/?retryWrites=true&w=majority SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas diff --git a/api/package-lock.json b/api/package-lock.json index 9f8fafe..d5d3bcb 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -11,11 +11,14 @@ "@sasjs/core": "^4.19.0", "@sasjs/utils": "2.42.1", "bcryptjs": "^2.4.3", + "connect-mongo": "^4.6.0", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.17.1", + "express-session": "^1.17.2", "joi": "^17.4.2", "jsonwebtoken": "^8.5.1", + "mongodb": "^4.1.4", "mongoose": "^6.0.12", "mongoose-sequence": "^5.3.1", "morgan": "^1.10.0", @@ -30,6 +33,7 @@ "@types/cookie-parser": "^1.4.2", "@types/cors": "^2.8.12", "@types/express": "^4.17.12", + "@types/express-session": "^1.17.4", "@types/jest": "^26.0.24", "@types/jsonwebtoken": "^8.5.5", "@types/mongoose-sequence": "^3.0.6", @@ -1856,6 +1860,15 @@ "@types/range-parser": "*" } }, + "node_modules/@types/express-session": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz", + "integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -2447,6 +2460,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -2674,6 +2698,11 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "node_modules/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -3238,6 +3267,42 @@ "node": ">=8" } }, + "node_modules/connect-mongo": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz", + "integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==", + "dependencies": { + "debug": "^4.3.1", + "kruptein": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "mongodb": "^4.1.0" + } + }, + "node_modules/connect-mongo/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/connect-mongo/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/consola": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz", @@ -4027,6 +4092,59 @@ "node": ">= 0.10.0" } }, + "node_modules/express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express-session/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -6828,6 +6946,17 @@ "node": ">=6" } }, + "node_modules/kruptein": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.4.tgz", + "integrity": "sha512-614v+4fgOkcw98lI7rMO9HZ+Y2cK6MGYcR/NSVhRXcClUb72LTAf2NibAh8CKSjalY81rfrrjLQgb8TW9RP03Q==", + "dependencies": { + "asn1.js": "^5.4.1" + }, + "engines": { + "node": ">8" + } + }, "node_modules/latest-version": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", @@ -7095,6 +7224,11 @@ "node": ">=4" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -7133,7 +7267,6 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz", "integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==", - "dev": true, "dependencies": { "bson": "^4.5.4", "denque": "^2.0.1", @@ -8367,6 +8500,14 @@ } ] }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -9626,6 +9767,17 @@ "node": ">=0.8.0" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -11548,6 +11700,15 @@ "@types/range-parser": "*" } }, + "@types/express-session": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz", + "integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -12059,6 +12220,17 @@ "is-string": "^1.0.7" } }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -12234,6 +12406,11 @@ } } }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -12681,6 +12858,30 @@ "xdg-basedir": "^4.0.0" } }, + "connect-mongo": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz", + "integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==", + "requires": { + "debug": "^4.3.1", + "kruptein": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "consola": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz", @@ -13303,6 +13504,38 @@ "vary": "~1.1.2" } }, + "express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, "fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -15409,6 +15642,14 @@ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" }, + "kruptein": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.4.tgz", + "integrity": "sha512-614v+4fgOkcw98lI7rMO9HZ+Y2cK6MGYcR/NSVhRXcClUb72LTAf2NibAh8CKSjalY81rfrrjLQgb8TW9RP03Q==", + "requires": { + "asn1.js": "^5.4.1" + } + }, "latest-version": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", @@ -15615,6 +15856,11 @@ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -15644,7 +15890,6 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz", "integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==", - "dev": true, "requires": { "bson": "^4.5.4", "denque": "^2.0.1", @@ -16555,6 +16800,11 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -17495,6 +17745,14 @@ "dev": true, "optional": true }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", diff --git a/api/package.json b/api/package.json index d835e4c..aff24b2 100644 --- a/api/package.json +++ b/api/package.json @@ -50,9 +50,11 @@ "@sasjs/core": "^4.19.0", "@sasjs/utils": "2.42.1", "bcryptjs": "^2.4.3", + "connect-mongo": "^4.6.0", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.17.1", + "express-session": "^1.17.2", "joi": "^17.4.2", "jsonwebtoken": "^8.5.1", "mongoose": "^6.0.12", @@ -66,6 +68,7 @@ "@types/cookie-parser": "^1.4.2", "@types/cors": "^2.8.12", "@types/express": "^4.17.12", + "@types/express-session": "^1.17.4", "@types/jest": "^26.0.24", "@types/jsonwebtoken": "^8.5.5", "@types/mongoose-sequence": "^3.0.6", diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index 6a7eafb..0cee9b9 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -5,6 +5,21 @@ components: requestBodies: {} responses: {} schemas: + LoginPayload: + properties: + username: + type: string + description: 'Username for user' + example: secretuser + password: + type: string + description: 'Password for user' + example: secretpassword + required: + - username + - password + type: object + additionalProperties: false AuthorizeResponse: properties: code: @@ -450,6 +465,47 @@ info: name: '4GL Ltd' openapi: 3.0.0 paths: + /login: + post: + operationId: Login + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + user: {properties: {displayName: {type: string}, username: {type: string}}, required: [displayName, username], type: object} + loggedIn: {type: boolean} + required: + - user + - loggedIn + type: object + summary: 'Accept a valid username/password' + tags: + - Web + security: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginPayload' + /logout: + get: + operationId: Logout + responses: + '200': + description: Ok + content: + application/json: + schema: {} + summary: 'Accept a valid username/password' + tags: + - Web + security: [] + parameters: [] /SASjsApi/auth/authorize: post: operationId: Authorize @@ -1308,3 +1364,6 @@ tags: - name: CODE description: 'Operations on SAS code' + - + name: Web + description: 'Operations on Web' diff --git a/api/src/app.ts b/api/src/app.ts index cb0734d..6632e18 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -1,5 +1,7 @@ import path from 'path' import express, { ErrorRequestHandler } from 'express' +import session from 'express-session' +import MongoStore from 'connect-mongo' import morgan from 'morgan' import cookieParser from 'cookie-parser' import dotenv from 'dotenv' @@ -34,6 +36,25 @@ if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') { app.use(cors({ credentials: true, origin: whiteList })) } +if (MODE?.trim() === 'server') { + const clientPromise = connectDB().then((conn) => conn!.getClient() as any) + + const { PROTOCOL } = process.env + + app.use( + session({ + secret: process.env.SESSION_SECRET as string, + saveUninitialized: false, // don't create session until something stored + resave: false, //don't save session if unmodified + store: MongoStore.create({ clientPromise, collectionName: 'sessions' }), + cookie: { + secure: PROTOCOL === 'https', + maxAge: 24 * 60 * 60 * 1000 // 24 hours + } + }) + ) +} + app.use(cookieParser()) app.use(morgan('tiny')) app.use(express.json({ limit: '100mb' })) @@ -61,6 +82,5 @@ export default setProcessVariables().then(async () => { app.use(onError) - await connectDB() return app }) diff --git a/api/src/controllers/session.ts b/api/src/controllers/session.ts index 1e3dd59..92a977a 100644 --- a/api/src/controllers/session.ts +++ b/api/src/controllers/session.ts @@ -24,7 +24,7 @@ export class SessionController { } const session = (req: any) => ({ - id: req.user.id, + id: req.user.userId, username: req.user.username, displayName: req.user.displayName }) diff --git a/api/src/controllers/web.ts b/api/src/controllers/web.ts new file mode 100644 index 0000000..605d8e8 --- /dev/null +++ b/api/src/controllers/web.ts @@ -0,0 +1,75 @@ +import express from 'express' +import { Request, Route, Tags, Post, Body, Get } from 'tsoa' +import User from '../model/User' + +@Route('/') +@Tags('Web') +export class WebController { + /** + * @summary Accept a valid username/password + * + */ + @Post('/login') + public async login( + @Request() req: express.Request, + @Body() body: LoginPayload + ) { + return login(req, body) + } + + /** + * @summary Accept a valid username/password + * + */ + @Get('/logout') + public async logout(@Request() req: express.Request) { + return new Promise((resolve) => { + req.session.destroy(() => { + resolve(true) + }) + }) + } +} + +const login = async ( + req: express.Request, + { username, password }: LoginPayload +) => { + // Authenticate User + const user = await User.findOne({ username }) + if (!user) throw new Error('Username is not found.') + + const validPass = user.comparePassword(password) + if (!validPass) throw new Error('Invalid password.') + + req.session.loggedIn = true + req.session.user = { + userId: user.id, + clientId: 'web_app', + username: user.username, + displayName: user.displayName, + isAdmin: user.isAdmin, + isActive: user.isActive + } + + return { + loggedIn: true, + user: { + username: user.username, + displayName: user.displayName + } + } +} + +interface LoginPayload { + /** + * Username for user + * @example "secretuser" + */ + username: string + /** + * Password for user + * @example "secretpassword" + */ + password: string +} diff --git a/api/src/middlewares/authenticateToken.ts b/api/src/middlewares/authenticateToken.ts index d61473f..4d97583 100644 --- a/api/src/middlewares/authenticateToken.ts +++ b/api/src/middlewares/authenticateToken.ts @@ -2,6 +2,10 @@ import jwt from 'jsonwebtoken' import { verifyTokenInDB } from '../utils' export const authenticateAccessToken = (req: any, res: any, next: any) => { + if (req.session?.loggedIn) { + req.user = req.session.user + return next() + } authenticateToken( req, res, @@ -43,9 +47,7 @@ const authenticateToken = ( } const authHeader = req.headers['authorization'] - const token = - authHeader?.split(' ')[1] ?? - (tokenType === 'accessToken' ? req.cookies.accessToken : '') + const token = authHeader?.split(' ')[1] if (!token) return res.sendStatus(401) jwt.verify(token, key, async (err: any, data: any) => { diff --git a/api/src/routes/api/auth.ts b/api/src/routes/api/auth.ts index 08664cb..8965052 100644 --- a/api/src/routes/api/auth.ts +++ b/api/src/routes/api/auth.ts @@ -32,9 +32,8 @@ authRouter.post('/token', async (req, res) => { try { const response = await controller.token(body) - const { accessToken } = response - res.cookie('accessToken', accessToken).send(response) + res.send(response) } catch (err: any) { res.status(403).send(err.toString()) } diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index a2daa01..f9889b0 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -1,37 +1,40 @@ -import { readFile } from '@sasjs/utils' -import express from 'express' import path from 'path' -import { getWebBuildFolderPath } from '../../utils' +import express from 'express' +import { fileExists } from '@sasjs/utils' +import { WebController } from '../../controllers/web' +import { getWebBuildFolderPath, loginWebValidation } from '../../utils' const webRouter = express.Router() -const jsCodeForDesktopMode = ` -` - -const jsCodeForServerMode = ` -` - webRouter.get('/', async (_, res) => { - let content: string + const indexHtmlPath = path.join(getWebBuildFolderPath(), 'index.html') + + if (await fileExists(indexHtmlPath)) return res.sendFile(indexHtmlPath) + + return res.send('Web Build is not present') +}) + +webRouter.post('/login', async (req, res) => { + const { error, value: body } = loginWebValidation(req.body) + if (error) return res.status(400).send(error.details[0].message) + + const controller = new WebController() try { - const indexHtmlPath = path.join(getWebBuildFolderPath(), 'index.html') - content = await readFile(indexHtmlPath) - } catch (_) { - return res.send('Web Build is not present') + const response = await controller.login(req, body) + res.send(response) + } catch (err: any) { + res.status(400).send(err.toString()) } +}) - const { MODE } = process.env - const codeToInject = - MODE?.trim() === 'server' ? jsCodeForServerMode : jsCodeForDesktopMode - const injectedContent = content.replace('', `${codeToInject}`) - - res.setHeader('Content-Type', 'text/html') - return res.send(injectedContent) +webRouter.get('/logout', async (req, res) => { + const controller = new WebController() + try { + await controller.logout(req) + res.status(200).send() + } catch (err: any) { + res.status(400).send(err.toString()) + } }) export default webRouter diff --git a/api/src/types/Process.d.ts b/api/src/types/Process.d.ts deleted file mode 100644 index 72e1c85..0000000 --- a/api/src/types/Process.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -declare namespace NodeJS { - export interface Process { - sasLoc: string - driveLoc: string - sessionController?: import('../controllers/internal').SessionController - appStreamConfig: import('./').AppStreamConfig - } -} diff --git a/api/src/types/Request.ts b/api/src/types/Request.ts deleted file mode 100644 index bebb033..0000000 --- a/api/src/types/Request.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { MacroVars } from '@sasjs/utils' - -export interface ExecutionQuery { - _program: string - macroVars?: MacroVars - _debug?: number -} - -export interface FileQuery { - filePath: string -} - -export const isExecutionQuery = (arg: any): arg is ExecutionQuery => - arg && !Array.isArray(arg) && typeof arg._program === 'string' - -export const isFileQuery = (arg: any): arg is FileQuery => - arg && !Array.isArray(arg) && typeof arg.filePath === 'string' diff --git a/api/src/types/index.ts b/api/src/types/index.ts index 682aee8..9040575 100644 --- a/api/src/types/index.ts +++ b/api/src/types/index.ts @@ -3,6 +3,5 @@ export * from './AppStreamConfig' export * from './Execution' export * from './InfoJWT' export * from './PreProgramVars' -export * from './Request' export * from './Session' export * from './TreeNode' diff --git a/api/src/types/system/express-session.d.ts b/api/src/types/system/express-session.d.ts new file mode 100644 index 0000000..e55a990 --- /dev/null +++ b/api/src/types/system/express-session.d.ts @@ -0,0 +1,14 @@ +import express from 'express' +declare module 'express-session' { + interface SessionData { + loggedIn: boolean + user: { + userId: number + clientId: string + username: string + displayName: string + isAdmin: boolean + isActive: boolean + } + } +} diff --git a/api/src/types/system/global.d.ts b/api/src/types/system/global.d.ts new file mode 100644 index 0000000..b68ae07 --- /dev/null +++ b/api/src/types/system/global.d.ts @@ -0,0 +1 @@ +import 'jest-extended' diff --git a/api/src/types/system/process.d.ts b/api/src/types/system/process.d.ts new file mode 100644 index 0000000..9b03ec6 --- /dev/null +++ b/api/src/types/system/process.d.ts @@ -0,0 +1,8 @@ +declare namespace NodeJS { + export interface Process { + sasLoc: string + driveLoc: string + sessionController?: import('../../controllers/internal').SessionController + appStreamConfig: import('../').AppStreamConfig + } +} diff --git a/api/src/utils/connectDB.ts b/api/src/utils/connectDB.ts index 0fea107..6f7ef6e 100644 --- a/api/src/utils/connectDB.ts +++ b/api/src/utils/connectDB.ts @@ -11,15 +11,18 @@ export const connectDB = async () => { const { MODE } = process.env if (MODE?.trim() !== 'server') { - console.log('Running in Destop Mode, no DB to connect.') + console.log('Running in Desktop Mode, no DB to connect.') return } - mongoose.connect(process.env.DB_CONNECT as string, async (err) => { - if (err) throw err + try { + await mongoose.connect(process.env.DB_CONNECT as string) + } catch (err) { + throw new Error('Unable to connect to DB!') + } - console.log('Connected to db!') + console.log('Connected to DB!') + await seedDB() - await seedDB() - }) + return mongoose.connection } diff --git a/api/src/utils/validation.ts b/api/src/utils/validation.ts index cede0de..a18cef9 100644 --- a/api/src/utils/validation.ts +++ b/api/src/utils/validation.ts @@ -5,6 +5,12 @@ const passwordSchema = Joi.string().min(6).max(1024) export const blockFileRegex = /\.(exe|sh|htaccess)$/i +export const loginWebValidation = (data: any): Joi.ValidationResult => + Joi.object({ + username: usernameSchema.required(), + password: passwordSchema.required() + }).validate(data) + export const authorizeValidation = (data: any): Joi.ValidationResult => Joi.object({ username: usernameSchema.required(), diff --git a/api/tsoa.json b/api/tsoa.json index 61c1390..f353150 100644 --- a/api/tsoa.json +++ b/api/tsoa.json @@ -46,6 +46,10 @@ { "name": "CODE", "description": "Operations on SAS code" + }, + { + "name": "Web", + "description": "Operations on Web" } ], "yaml": true, diff --git a/web/.env.example b/web/.env.example index 2e9dbe4..e91dab4 100644 --- a/web/.env.example +++ b/web/.env.example @@ -1,2 +1 @@ -PORT_API=[place sasjs server port] default value is 5000 -CLIENT_ID= \ No newline at end of file +PORT_API=[place sasjs server port] default value is 5000 \ No newline at end of file diff --git a/web/src/App.tsx b/web/src/App.tsx index 02a493b..1f13e31 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -14,7 +14,7 @@ import { AppContext } from './context/appContext' function App() { const appContext = useContext(AppContext) - if (!appContext.tokens) { + if (!appContext.loggedIn) { return ( diff --git a/web/src/components/login.tsx b/web/src/components/login.tsx index acccd35..1964006 100644 --- a/web/src/components/login.tsx +++ b/web/src/components/login.tsx @@ -1,3 +1,4 @@ +import axios from 'axios' import React, { useState, useContext } from 'react' import { useLocation } from 'react-router-dom' import PropTypes from 'prop-types' @@ -5,34 +6,11 @@ import PropTypes from 'prop-types' import { CssBaseline, Box, TextField, Button, Typography } from '@mui/material' import { AppContext } from '../context/appContext' -const headers = { - Accept: 'application/json', - 'Content-Type': 'application/json' -} -const NODE_ENV = process.env.NODE_ENV -const PORT_API = process.env.PORT_API -const baseUrl = - NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : '' +const getAuthCode = async (credentials: any) => + axios.post('/SASjsApi/auth/authorize', credentials).then((res) => res.data) -const getAuthCode = async (credentials: any) => { - return fetch(`${baseUrl}/SASjsApi/auth/authorize`, { - method: 'POST', - headers, - body: JSON.stringify(credentials) - }).then(async (response) => { - const resText = await response.text() - if (response.status !== 200) throw resText - - return JSON.parse(resText) - }) -} -const getTokens = async (payload: any) => { - return fetch(`${baseUrl}/SASjsApi/auth/token`, { - method: 'POST', - headers, - body: JSON.stringify(payload) - }).then((data) => data.json()) -} +const login = async (payload: { username: string; password: string }) => + axios.post('/login', payload).then((res) => res.data) const Login = ({ getCodeOnly }: any) => { const location = useLocation() @@ -47,34 +25,40 @@ const Login = ({ getCodeOnly }: any) => { error = false setErrorMessage('') e.preventDefault() - let clientId = process.env.CLIENT_ID ?? localStorage.getItem('CLIENT_ID') if (getCodeOnly) { const params = new URLSearchParams(location.search) const responseType = params.get('response_type') - if (responseType === 'code') clientId = params.get('client_id') + if (responseType === 'code') { + const clientId = params.get('client_id') + + const { code } = await getAuthCode({ + clientId, + username, + password + }).catch((err: any) => { + error = true + setErrorMessage(err.response.data) + return {} + }) + if (!error) return setDisplayCode(code) + return + } } - const { code } = await getAuthCode({ - clientId, + const { loggedIn, user } = await login({ username, password - }).catch((err: string) => { + }).catch((err: any) => { error = true - setErrorMessage(err) + setErrorMessage(err.response.data) return {} }) - if (!error) { - if (getCodeOnly) return setDisplayCode(code) - - const { accessToken, refreshToken } = await getTokens({ - clientId, - code - }) - - if (appContext.setTokens) appContext.setTokens(accessToken, refreshToken) - if (appContext.setUserName) appContext.setUserName(username) + if (loggedIn) { + appContext.setLoggedIn?.(loggedIn) + appContext.setUserName?.(user.username) + appContext.setDisplayName?.(user.displayname) } } @@ -129,7 +113,11 @@ const Login = ({ getCodeOnly }: any) => { required /> {errorMessage && {errorMessage}} - diff --git a/web/src/components/useTokens.ts b/web/src/components/useTokens.ts deleted file mode 100644 index 0c111c0..0000000 --- a/web/src/components/useTokens.ts +++ /dev/null @@ -1,94 +0,0 @@ -import axios from 'axios' -import { useEffect, useState } from 'react' - -export default function useTokens() { - const getTokens = () => { - const accessToken = localStorage.getItem('accessToken') - const refreshToken = localStorage.getItem('refreshToken') - - if (accessToken && refreshToken) { - setAxiosRequestHeader(accessToken) - return { accessToken, refreshToken } - } - return undefined - } - - const [tokens, setTokens] = useState(getTokens()) - - useEffect(() => { - if (tokens === undefined) { - localStorage.removeItem('accessToken') - localStorage.removeItem('refreshToken') - } - }, [tokens]) - setAxiosResponse(setTokens) - - const saveTokens = (accessToken: string, refreshToken: string) => { - localStorage.setItem('accessToken', accessToken) - localStorage.setItem('refreshToken', refreshToken) - setAxiosRequestHeader(accessToken) - setTokens({ accessToken, refreshToken }) - } - - return { - setTokens: saveTokens, - tokens - } -} - -const NODE_ENV = process.env.NODE_ENV -const PORT_API = process.env.PORT_API -const baseUrl = - NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : '' - -const isAbsoluteURLRegex = /^(?:\w+:)\/\// - -const setAxiosRequestHeader = (accessToken: string) => { - axios.interceptors.request.use(function (config) { - if (baseUrl && !isAbsoluteURLRegex.test(config.url as string)) { - config.url = baseUrl + config.url - } - config.headers!['Authorization'] = `Bearer ${accessToken}` - config.withCredentials = true - - return config - }) -} - -const setAxiosResponse = (setTokens: Function) => { - // Add a response interceptor - axios.interceptors.response.use( - function (response) { - // Any status code that lie within the range of 2xx cause this function to trigger - return response - }, - async function (error) { - if (error.response?.status === 401) { - // refresh token - // const { accessToken, refreshToken: newRefresh } = await refreshMyToken( - // refreshToken - // ) - - // if (accessToken && newRefresh) { - // setTokens(accessToken, newRefresh) - // error.config.headers['Authorization'] = 'Bearer ' + accessToken - // error.config.baseURL = undefined - - // return axios.request(error.config) - // } - setTokens(undefined) - } - - return Promise.reject(error) - } - ) -} - -// const refreshMyToken = async (refreshToken: string) => { -// return fetch('http://localhost:5000/SASjsApi/auth/refresh', { -// method: 'POST', -// headers: { -// Authorization: `Bearer ${refreshToken}` -// } -// }).then((data) => data.json()) -// } diff --git a/web/src/context/appContext.tsx b/web/src/context/appContext.tsx index a46352a..cac6cbd 100644 --- a/web/src/context/appContext.tsx +++ b/web/src/context/appContext.tsx @@ -7,127 +7,71 @@ import React, { useCallback, ReactNode } from 'react' - import axios from 'axios' -const NODE_ENV = process.env.NODE_ENV -const PORT_API = process.env.PORT_API -const baseUrl = - NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : '' - -const isAbsoluteURLRegex = /^(?:\w+:)\/\// - -const setAxiosRequestHeader = (accessToken: string) => { - axios.interceptors.request.use(function (config) { - if (baseUrl && !isAbsoluteURLRegex.test(config.url as string)) { - config.url = baseUrl + config.url - } - console.log('axios.interceptors.request.use', accessToken) - config.headers!['Authorization'] = `Bearer ${accessToken}` - config.withCredentials = true - - return config - }) -} - -const setAxiosResponse = (setTokens: Function) => { - // Add a response interceptor - axios.interceptors.response.use( - function (response) { - // Any status code that lie within the range of 2xx cause this function to trigger - return response - }, - async function (error) { - if (error.response?.status === 401) { - // refresh token - // const { accessToken, refreshToken: newRefresh } = await refreshMyToken( - // refreshToken - // ) - - // if (accessToken && newRefresh) { - // setTokens(accessToken, newRefresh) - // error.config.headers['Authorization'] = 'Bearer ' + accessToken - // error.config.baseURL = undefined - - // return axios.request(error.config) - // } - console.log(53) - setTokens(undefined) - } - - return Promise.reject(error) - } - ) -} - -const getTokens = () => { - const accessToken = localStorage.getItem('accessToken') - const refreshToken = localStorage.getItem('refreshToken') - - if (accessToken && refreshToken) { - setAxiosRequestHeader(accessToken) - return { accessToken, refreshToken } - } - return undefined -} - interface AppContextProps { + checkingSession: boolean + loggedIn: boolean + setLoggedIn: Dispatch> | null userName: string setUserName: Dispatch> | null - tokens?: { accessToken: string; refreshToken: string } - setTokens: ((accessToken: string, refreshToken: string) => void) | null + displayName: string + setDisplayName: Dispatch> | null logout: (() => void) | null } export const AppContext = createContext({ + checkingSession: false, + loggedIn: false, + setLoggedIn: null, userName: '', - tokens: getTokens(), setUserName: null, - setTokens: null, + displayName: '', + setDisplayName: null, logout: null }) const AppContextProvider = (props: { children: ReactNode }) => { const { children } = props + const [checkingSession, setCheckingSession] = useState(false) + const [loggedIn, setLoggedIn] = useState(false) const [userName, setUserName] = useState('') - const [tokens, setTokens] = useState(getTokens()) + const [displayName, setDisplayName] = useState('') useEffect(() => { - setAxiosResponse(setTokens) + setCheckingSession(true) + + axios + .get('/SASjsApi/session') + .then((response: any) => { + setCheckingSession(false) + setLoggedIn(true) + setUserName(response.userName) + setDisplayName(response.displayName) + }) + .catch(() => { + setLoggedIn(false) + }) }, []) - useEffect(() => { - console.log(97) - if (tokens === undefined) { - console.log(99) - localStorage.removeItem('accessToken') - localStorage.removeItem('refreshToken') - } - }, [tokens]) - - const saveTokens = useCallback( - (accessToken: string, refreshToken: string) => { - localStorage.setItem('accessToken', accessToken) - localStorage.setItem('refreshToken', refreshToken) - console.log(accessToken) - setAxiosRequestHeader(accessToken) - setTokens({ accessToken, refreshToken }) - }, - [] - ) - const logout = useCallback(() => { - setUserName('') - setTokens(undefined) + axios.get('/logout').then(() => { + setLoggedIn(false) + setUserName('') + setDisplayName('') + }) }, []) return ( diff --git a/web/src/index.tsx b/web/src/index.tsx index 23b1a75..4939003 100644 --- a/web/src/index.tsx +++ b/web/src/index.tsx @@ -4,6 +4,18 @@ import './index.css' import App from './App' import AppContextProvider from './context/appContext' +import axios from 'axios' + +const NODE_ENV = process.env.NODE_ENV +const PORT_API = process.env.PORT_API +const baseUrl = + NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : '' + +axios.defaults = Object.assign(axios.defaults, { + withCredentials: true, + baseURL: baseUrl +}) + ReactDOM.render( From e57443f1ed662a022494bb93d79c3d2f10a2d082 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 28 Apr 2022 07:00:49 +0500 Subject: [PATCH 2/5] fix(web): show display name instead of username --- web/src/components/header.tsx | 6 +++--- web/src/components/login.tsx | 8 ++++---- web/src/components/userName.tsx | 6 +++--- web/src/context/appContext.tsx | 23 ++++++++++++----------- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/web/src/components/header.tsx b/web/src/components/header.tsx index f0654cc..c499ae3 100644 --- a/web/src/components/header.tsx +++ b/web/src/components/header.tsx @@ -12,7 +12,7 @@ import { } from '@mui/material' import OpenInNewIcon from '@mui/icons-material/OpenInNew' -import UserName from './userName' +import Username from './username' import { AppContext } from '../context/appContext' const NODE_ENV = process.env.NODE_ENV @@ -113,8 +113,8 @@ const Header = (props: any) => { justifyContent: 'flex-end' }} > - const Login = ({ getCodeOnly }: any) => { const location = useLocation() const appContext = useContext(AppContext) - const [username, setUserName] = useState('') + const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [errorMessage, setErrorMessage] = useState('') let error: boolean @@ -57,8 +57,8 @@ const Login = ({ getCodeOnly }: any) => { if (loggedIn) { appContext.setLoggedIn?.(loggedIn) - appContext.setUserName?.(user.username) - appContext.setDisplayName?.(user.displayname) + appContext.setUsername?.(user.username) + appContext.setDisplayName?.(user.displayName) } } @@ -101,7 +101,7 @@ const Login = ({ getCodeOnly }: any) => { label="Username" type="text" variant="outlined" - onChange={(e: any) => setUserName(e.target.value)} + onChange={(e: any) => setUsername(e.target.value)} required /> { +const Username = (props: any) => { return ( { )} - {props.userName} + {props.username} ) } -export default UserName +export default Username diff --git a/web/src/context/appContext.tsx b/web/src/context/appContext.tsx index cac6cbd..dea218e 100644 --- a/web/src/context/appContext.tsx +++ b/web/src/context/appContext.tsx @@ -13,8 +13,8 @@ interface AppContextProps { checkingSession: boolean loggedIn: boolean setLoggedIn: Dispatch> | null - userName: string - setUserName: Dispatch> | null + username: string + setUsername: Dispatch> | null displayName: string setDisplayName: Dispatch> | null logout: (() => void) | null @@ -24,8 +24,8 @@ export const AppContext = createContext({ checkingSession: false, loggedIn: false, setLoggedIn: null, - userName: '', - setUserName: null, + username: '', + setUsername: null, displayName: '', setDisplayName: null, logout: null @@ -35,7 +35,7 @@ const AppContextProvider = (props: { children: ReactNode }) => { const { children } = props const [checkingSession, setCheckingSession] = useState(false) const [loggedIn, setLoggedIn] = useState(false) - const [userName, setUserName] = useState('') + const [username, setUsername] = useState('') const [displayName, setDisplayName] = useState('') useEffect(() => { @@ -43,11 +43,12 @@ const AppContextProvider = (props: { children: ReactNode }) => { axios .get('/SASjsApi/session') - .then((response: any) => { + .then((res) => res.data) + .then((data: any) => { setCheckingSession(false) setLoggedIn(true) - setUserName(response.userName) - setDisplayName(response.displayName) + setUsername(data.username) + setDisplayName(data.displayName) }) .catch(() => { setLoggedIn(false) @@ -57,7 +58,7 @@ const AppContextProvider = (props: { children: ReactNode }) => { const logout = useCallback(() => { axios.get('/logout').then(() => { setLoggedIn(false) - setUserName('') + setUsername('') setDisplayName('') }) }, []) @@ -68,8 +69,8 @@ const AppContextProvider = (props: { children: ReactNode }) => { checkingSession, loggedIn, setLoggedIn, - userName, - setUserName, + username, + setUsername, displayName, setDisplayName, logout From 4c7ad5632681b5c51b69413bcfbb2d4d413e2b46 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 28 Apr 2022 07:07:56 +0500 Subject: [PATCH 3/5] test: fixed specs --- api/src/app.ts | 32 ++++++++++++++++++-------------- api/src/utils/connectDB.ts | 13 ------------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/api/src/app.ts b/api/src/app.ts index 6632e18..d588bea 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -37,22 +37,26 @@ if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') { } if (MODE?.trim() === 'server') { - const clientPromise = connectDB().then((conn) => conn!.getClient() as any) + // NOTE: when exporting app.js as agent for supertest + // we should exclude connecting to the real database + if (process.env.NODE_ENV !== 'test') { + const clientPromise = connectDB().then((conn) => conn!.getClient() as any) - const { PROTOCOL } = process.env + const { PROTOCOL } = process.env - app.use( - session({ - secret: process.env.SESSION_SECRET as string, - saveUninitialized: false, // don't create session until something stored - resave: false, //don't save session if unmodified - store: MongoStore.create({ clientPromise, collectionName: 'sessions' }), - cookie: { - secure: PROTOCOL === 'https', - maxAge: 24 * 60 * 60 * 1000 // 24 hours - } - }) - ) + app.use( + session({ + secret: process.env.SESSION_SECRET as string, + saveUninitialized: false, // don't create session until something stored + resave: false, //don't save session if unmodified + store: MongoStore.create({ clientPromise, collectionName: 'sessions' }), + cookie: { + secure: PROTOCOL === 'https', + maxAge: 24 * 60 * 60 * 1000 // 24 hours + } + }) + ) + } } app.use(cookieParser()) diff --git a/api/src/utils/connectDB.ts b/api/src/utils/connectDB.ts index 6f7ef6e..b6dd383 100644 --- a/api/src/utils/connectDB.ts +++ b/api/src/utils/connectDB.ts @@ -2,19 +2,6 @@ import mongoose from 'mongoose' import { seedDB } from './seedDB' export const connectDB = async () => { - // NOTE: when exporting app.js as agent for supertest - // we should exclude connecting to the real database - if (process.env.NODE_ENV === 'test') { - return - } - - const { MODE } = process.env - - if (MODE?.trim() !== 'server') { - console.log('Running in Desktop Mode, no DB to connect.') - return - } - try { await mongoose.connect(process.env.DB_CONNECT as string) } catch (err) { From 1d8acc36eb13d9643178e4f0576e1ce3324caf25 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 28 Apr 2022 07:15:09 +0500 Subject: [PATCH 4/5] chore: temp --- web/src/components/{userName.tsx => usernameTEMP.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename web/src/components/{userName.tsx => usernameTEMP.tsx} (100%) diff --git a/web/src/components/userName.tsx b/web/src/components/usernameTEMP.tsx similarity index 100% rename from web/src/components/userName.tsx rename to web/src/components/usernameTEMP.tsx From c3c2048e7564577b8bc82a718f2dc1d93355d182 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Thu, 28 Apr 2022 07:15:36 +0500 Subject: [PATCH 5/5] chore: temp --- web/src/components/{usernameTEMP.tsx => username.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename web/src/components/{usernameTEMP.tsx => username.tsx} (100%) diff --git a/web/src/components/usernameTEMP.tsx b/web/src/components/username.tsx similarity index 100% rename from web/src/components/usernameTEMP.tsx rename to web/src/components/username.tsx