diff --git a/package-lock.json b/package-lock.json index c2218f7e2..4eaeb719a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2629,6 +2629,15 @@ "@types/send": "*" } }, + "node_modules/@types/express-session": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.0.tgz", + "integrity": "sha512-27JdDRgor6PoYlURY+Y5kCakqp5ulC0kmf7y+QwaY+hv9jEFuQOThgkjyA53RP3jmKuBsH5GR6qEfFmvb8mwOA==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/fs-extra": { "version": "8.1.5", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz", @@ -2697,6 +2706,36 @@ "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz", "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==" }, + "node_modules/@types/passport": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.16.tgz", + "integrity": "sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-local": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.38.tgz", + "integrity": "sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", + "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", @@ -4973,6 +5012,50 @@ "node": ">=12.0.0" } }, + "node_modules/express-session": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz", + "integrity": "sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==", + "dependencies": { + "cookie": "0.6.0", + "cookie-signature": "1.0.7", + "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.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -7161,6 +7244,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7256,6 +7347,42 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -7338,6 +7465,11 @@ "node": ">=6" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -7599,6 +7731,14 @@ "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -9067,6 +9207,17 @@ "node": ">=14.17" } }, + "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/uncontrollable": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", @@ -10094,9 +10245,12 @@ "dotenv": "^16.4.5", "express": "^4.18.2", "express-fileupload": "^1.4.2", + "express-session": "^1.18.0", "joi": "^17.11.0", "jsqr": "^1.4.0", "multer": "^1.4.5-lts.1", + "passport": "^0.7.0", + "passport-local": "^1.0.0", "pdf-lib": "^1.17.1", "rollup-plugin-copy": "^3.5.0", "rollup-plugin-dynamic-import-variables": "^1.1.0", @@ -10111,6 +10265,8 @@ "@rollup/plugin-dynamic-import-vars": "^2.1.2", "@rollup/plugin-run": "^3.0.2", "@rollup/plugin-typescript": "^11.1.6", + "@types/express-session": "^1.18.0", + "@types/passport-local": "^1.0.38", "copyfiles": "^2.4.1", "pkgroll": "^2.0.1", "rimraf": "^5.0.5", diff --git a/server-node/package.json b/server-node/package.json index 2cbb4b093..a6e4a13af 100644 --- a/server-node/package.json +++ b/server-node/package.json @@ -31,9 +31,12 @@ "dotenv": "^16.4.5", "express": "^4.18.2", "express-fileupload": "^1.4.2", + "express-session": "^1.18.0", "joi": "^17.11.0", "jsqr": "^1.4.0", "multer": "^1.4.5-lts.1", + "passport": "^0.7.0", + "passport-local": "^1.0.0", "pdf-lib": "^1.17.1", "rollup-plugin-copy": "^3.5.0", "rollup-plugin-dynamic-import-variables": "^1.1.0", @@ -48,6 +51,8 @@ "@rollup/plugin-dynamic-import-vars": "^2.1.2", "@rollup/plugin-run": "^3.0.2", "@rollup/plugin-typescript": "^11.1.6", + "@types/express-session": "^1.18.0", + "@types/passport-local": "^1.0.38", "copyfiles": "^2.4.1", "pkgroll": "^2.0.1", "rimraf": "^5.0.5", diff --git a/server-node/src/auth/passport-config.ts b/server-node/src/auth/passport-config.ts new file mode 100644 index 000000000..a2ddc291e --- /dev/null +++ b/server-node/src/auth/passport-config.ts @@ -0,0 +1,31 @@ +import LocalStrategy from "passport-local"; +import * as User from "./user/user-controller"; + +export function initialize(passport: typeof import("passport")) { + passport.use("local", new LocalStrategy.Strategy( + function(username, password, done) { + User.findOne({ username: username }, function (err, user) { + if (err) { + return done(err); + } + if (!user) { + return done(null, false); + } + if (!User.verifyPassword(user, password)) { + return done(null, false); + } + return done(null, user); + }); + } + )); + + passport.serializeUser((user, done) => { + done(null, user.id) + }); + + passport.deserializeUser((id: number, done) => { + User.findOne({id: id}, function (err, user) { + done(err, user); + }); + }); +} \ No newline at end of file diff --git a/server-node/src/auth/user/user-controller.ts b/server-node/src/auth/user/user-controller.ts new file mode 100644 index 000000000..3e09650a4 --- /dev/null +++ b/server-node/src/auth/user/user-controller.ts @@ -0,0 +1,16 @@ +import { User } from "./user-model"; + +export function findOne(params: {id?: number, username?: string}, cb: (err: Error | null, user: User) => void): undefined { + //TODO: replace with db connection. + cb(null, { + id: 1, + username: "test", + mail: "test@test.com", + accessControlList: [] + }); +} + +export function verifyPassword(user: User, password: string) { + //TODO: replace with db connection. + return password == "test"; +} \ No newline at end of file diff --git a/server-node/src/auth/user/user-model.ts b/server-node/src/auth/user/user-model.ts new file mode 100644 index 000000000..39a483d5f --- /dev/null +++ b/server-node/src/auth/user/user-model.ts @@ -0,0 +1,6 @@ +export interface User { + id: number, + username: string, + mail: string, + accessControlList: string[], +} \ No newline at end of file diff --git a/server-node/src/index.ts b/server-node/src/index.ts index c3647207b..2b7ac0e04 100644 --- a/server-node/src/index.ts +++ b/server-node/src/index.ts @@ -1,5 +1,5 @@ /* - * Translation + * translation */ import i18next from "i18next"; @@ -7,7 +7,7 @@ import resourcesToBackend from "i18next-resources-to-backend"; i18next.use(resourcesToBackend((language: string, namespace: string) => import(`../../shared-operations/public/locales/${namespace}/${language}.json`))) .init({ - debug: true, + debug: false, ns: ["common"], // Preload this namespace, no need to add the others, they will load once their module is loaded defaultNS: "common", fallbackLng: "en", @@ -25,16 +25,42 @@ console.log("Available Modules: ", listOperatorNames()); * jobs */ -import "./jobs"; +import "./jobs/jobs-controller"; /* - * API + * EXPRESS */ import express from "express"; const app = express(); const PORT = 8000; +/* + * auth +*/ + +import passport from "passport"; +import session from "express-session"; +import { initialize } from "./auth/passport-config"; +import auth from "./routes/auth/auth-controller"; + +app.use(session({ + secret: process.env.SESSION_SECRET || "default-secret", + resave: false, + saveUninitialized: false +})); + +app.use(passport.initialize()); +app.use(passport.session()); + +initialize(passport); + +app.use("/auth", auth); + +/* + * api +*/ + import api from "./routes/api/api-controller"; app.use("/api", api); diff --git a/server-node/src/jobs.ts b/server-node/src/jobs/jobs-controller.ts similarity index 100% rename from server-node/src/jobs.ts rename to server-node/src/jobs/jobs-controller.ts diff --git a/server-node/src/routes/api/dynamic-operations-controller.ts b/server-node/src/routes/api/dynamic-operations-controller.ts index 293934c02..afd79182e 100644 --- a/server-node/src/routes/api/dynamic-operations-controller.ts +++ b/server-node/src/routes/api/dynamic-operations-controller.ts @@ -6,7 +6,7 @@ import { getOperatorByName } from "@stirling-pdf/shared-operations/src/workflow/ import { Operator } from "@stirling-pdf/shared-operations/src/functions"; import { PdfFile } from "@stirling-pdf/shared-operations/src/wrappers/PdfFile"; -import { respondWithPdfFiles } from "../../utils/endpoint-utils"; +import { respondWithPdfFiles } from "../../utils/response-utils"; import { Action } from "@stirling-pdf/shared-operations/declarations/Action"; import { JoiPDFFileSchema } from "@stirling-pdf/shared-operations/src/wrappers/PdfFileJoi"; diff --git a/server-node/src/routes/api/workflow-controller.ts b/server-node/src/routes/api/workflow-controller.ts index 6e34d2e53..82392154d 100644 --- a/server-node/src/routes/api/workflow-controller.ts +++ b/server-node/src/routes/api/workflow-controller.ts @@ -5,7 +5,7 @@ const upload = multer(); import { traverseOperations } from "@stirling-pdf/shared-operations/src/workflow/traverseOperations"; import { PdfFile, RepresentationType } from "@stirling-pdf/shared-operations/src/wrappers/PdfFile"; -import { respondWithPdfFiles } from "../../utils/endpoint-utils"; +import { respondWithPdfFiles } from "../../utils/response-utils"; import { JoiPDFFileSchema } from "@stirling-pdf/shared-operations/src/wrappers/PdfFileJoi"; interface Workflow { diff --git a/server-node/src/routes/auth/auth-controller.ts b/server-node/src/routes/auth/auth-controller.ts new file mode 100644 index 000000000..8bb44d2f2 --- /dev/null +++ b/server-node/src/routes/auth/auth-controller.ts @@ -0,0 +1,12 @@ +import express, { Request, Response } from "express"; + +import login from "./login-controller"; +import logout from "./logout-controller"; +import register from "./register-controller"; +import status from "./status-controller"; + +const router = express.Router(); + +router.use("/", [login, logout, register, status]); + +export default router; \ No newline at end of file diff --git a/server-node/src/routes/auth/login-controller.ts b/server-node/src/routes/auth/login-controller.ts new file mode 100644 index 000000000..720726a0f --- /dev/null +++ b/server-node/src/routes/auth/login-controller.ts @@ -0,0 +1,16 @@ +import express from "express"; +const router = express.Router(); + +import passport from "passport"; + +router.post("/login", passport.authenticate(['local'], { + successRedirect: '/auth/status', + failureRedirect: '/auth/login/failure' +})); + +router.post('/login/password', passport.authenticate('local', { + successRedirect: '/auth/status', + failureRedirect: '/auth/login/failure' +})); + +export default router; \ No newline at end of file diff --git a/server-node/src/routes/auth/logout-controller.ts b/server-node/src/routes/auth/logout-controller.ts new file mode 100644 index 000000000..3aeb96a0c --- /dev/null +++ b/server-node/src/routes/auth/logout-controller.ts @@ -0,0 +1,11 @@ +import express, { Request, Response } from "express"; +const router = express.Router(); + +router.post('/logout', function(req, res, next) { + req.logout(function(err) { + if (err) { return next(err); } + res.redirect('/'); + }); +}); + +export default router; \ No newline at end of file diff --git a/server-node/src/routes/auth/register-controller.ts b/server-node/src/routes/auth/register-controller.ts new file mode 100644 index 000000000..9aa5bda62 --- /dev/null +++ b/server-node/src/routes/auth/register-controller.ts @@ -0,0 +1,8 @@ +import express, { Request, Response } from "express"; +const router = express.Router(); + +router.post('/register', async function(req: Request, res: Response) { + //TODO: Register new user +}); + +export default router; \ No newline at end of file diff --git a/server-node/src/routes/auth/status-controller.ts b/server-node/src/routes/auth/status-controller.ts new file mode 100644 index 000000000..b7b98d4f8 --- /dev/null +++ b/server-node/src/routes/auth/status-controller.ts @@ -0,0 +1,8 @@ +import express, { Request, Response } from "express"; +const router = express.Router(); + +router.get('/status', async function(req: Request, res: Response) { + res.json({user: req.user}) +}); + +export default router; \ No newline at end of file diff --git a/server-node/src/utils/endpoint-utils.ts b/server-node/src/utils/response-utils.ts similarity index 100% rename from server-node/src/utils/endpoint-utils.ts rename to server-node/src/utils/response-utils.ts diff --git a/shared-operations/declarations/Action.d.ts b/shared-operations/declarations/Action.d.ts index 17e694ea9..c0d4bc629 100644 --- a/shared-operations/declarations/Action.d.ts +++ b/shared-operations/declarations/Action.d.ts @@ -1,3 +1,5 @@ +// TODO: This file can probably be removed by now + export interface Action { values: any; type: "wait" | "done" | "impose" | string; @@ -18,4 +20,4 @@ export interface ImposeAction extends Action { export interface WaitAction extends Action { values: { id: number } -} \ No newline at end of file +}