diff --git a/server-node/src/auth/apikey/apikey-controller.ts b/server-node/src/auth/apikey/apikey-controller.ts new file mode 100644 index 000000000..b54de0765 --- /dev/null +++ b/server-node/src/auth/apikey/apikey-controller.ts @@ -0,0 +1,35 @@ +import { Error as SequelizeError } from "sequelize"; +import { APIKey } from "./apikey-model"; +import { User } from "../user/user-model"; + +export function findOne(params: {apikey?: string}, cb: (err: Error | null, apikey?: APIKey | undefined, info?: Object | undefined) => void): undefined { + const query: any = params; + + for (let key in query) { + if (query[key] === undefined) { + delete query[key]; + } + } + + if(Object.keys(query).length == 0) { + cb(new Error("You need to provide at least one argument."), undefined) + } + + APIKey.findOne({ + where: query + }).then(apikey => { + if(apikey) + cb(null, apikey); + else + cb(null, undefined, { message: "The requested apikey was not found."}); + }).catch(e => + cb(e, undefined) + ); +} + +export async function createAPIKey(user: User | undefined): Promise { + const apikey = crypto.randomUUID(); // TODO: Is this secure enough? + const apikeyEntry = await APIKey.create({ apikey: apikey }) + await user?.addAPIKey(apikeyEntry); + return apikeyEntry; +} \ No newline at end of file diff --git a/server-node/src/auth/apikey/apikey-model.ts b/server-node/src/auth/apikey/apikey-model.ts new file mode 100644 index 000000000..e09d20fe4 --- /dev/null +++ b/server-node/src/auth/apikey/apikey-model.ts @@ -0,0 +1,15 @@ +import { User } from '../user/user-model'; +import { + Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ForeignKey, +} from 'sequelize'; + +export class APIKey extends Model, InferCreationAttributes> { + declare id: CreationOptional; + declare apikey: string; + + declare ownerId: ForeignKey; + declare owner?: NonAttribute; + + declare createdAt: CreationOptional; + declare updatedAt: CreationOptional; +} \ No newline at end of file diff --git a/server-node/src/auth/authenticationMiddleware.ts b/server-node/src/auth/authenticationMiddleware.ts new file mode 100644 index 000000000..8f48cf4eb --- /dev/null +++ b/server-node/src/auth/authenticationMiddleware.ts @@ -0,0 +1,15 @@ +import { Request, Response, NextFunction } from "express"; + +export function isAuthorized(req: Request, res: Response, next: NextFunction) { + if(import.meta.env.VITE_AUTH_ENABLED === "False" || req.user) { + return next(); + } + return res.status(403).json({"Error": "Authentication failed."}); +} + +export function whenAuthIsEnabled(req: Request, res: Response, next: NextFunction) { + if(import.meta.env.VITE_AUTH_ENABLED === "True") { + return next(); + } + return res.status(403).json({"Error": "Authentication is not enabled."}); +} \ No newline at end of file diff --git a/server-node/src/auth/checkAuthorizedMiddleware.ts b/server-node/src/auth/checkAuthorizedMiddleware.ts deleted file mode 100644 index 844b401ba..000000000 --- a/server-node/src/auth/checkAuthorizedMiddleware.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Request, Response, NextFunction } from "express"; - -export function checkAuthorized(req: Request, res: Response, next: NextFunction) { - if(import.meta.env.VITE_AUTH_ENABLED === "False" || req.user) { - return next(); - } - return res.status(403).json({"Error": "Authentication failed."}); -} \ No newline at end of file diff --git a/server-node/src/auth/passport-config.ts b/server-node/src/auth/passport-config.ts index b23225e9d..63f0f77f7 100644 --- a/server-node/src/auth/passport-config.ts +++ b/server-node/src/auth/passport-config.ts @@ -1,12 +1,13 @@ -import * as User from "./user/user-controller"; - import { Strategy as LocalStrategy} from "passport-local"; import { HeaderAPIKeyStrategy as HeaderAPIKeyStrategy } from "passport-headerapikey"; +import * as User from "./user/user-controller"; +import * as APIKey from "./apikey/apikey-controller"; + export function initialize(passport: typeof import("passport")) { passport.use("local", new LocalStrategy( function(username, password, done) { - User.findOne({ username: username }, function (err, user) { + User.findOne({username: username}, function (err, user) { if (err) { return done(err, false); } @@ -29,14 +30,14 @@ export function initialize(passport: typeof import("passport")) { { header: 'Authorization', prefix: 'Bearer ' }, false, function(apikey, done) { - User.findOne({ apikey: apikey }, function (err, user) { + APIKey.findOne({ apikey: apikey }, function (err, apikey, info) { if (err) { - return done(err); + return done(err, false); } - if (!user) { - return done(null, false); + if (!apikey) { + return done(null, false, info); } - return done(null, user); + return done(null, apikey.owner); }); } )); @@ -46,7 +47,7 @@ export function initialize(passport: typeof import("passport")) { }); passport.deserializeUser((id: number, done) => { - User.findOne({id: id}, function (err, user) { + User.findOne({ id: id }, function (err, user) { done(err, user); }); }); diff --git a/server-node/src/auth/user/user-controller.ts b/server-node/src/auth/user/user-controller.ts index ae922d82c..9cc12af09 100644 --- a/server-node/src/auth/user/user-controller.ts +++ b/server-node/src/auth/user/user-controller.ts @@ -1,10 +1,10 @@ import { Error as SequelizeError, Op } from "sequelize"; -import { APIKey, Password, User } from "./user-model"; +import { Password, User } from "./user-model"; import crypto from "crypto"; type PickOne = Pick & { [K in keyof Omit]?: never }; -export function findOne(params: {id?: number, username?: string, apikey?: string}, cb: (err: Error | null, user: User | null) => void): undefined { +export function findOne(params: {id?: number, username?: string}, cb: (err: Error | null, apikey?: User | undefined, info?: Object | undefined) => void): undefined { const query: any = params; for (let key in query) { @@ -14,7 +14,7 @@ export function findOne(params: {id?: number, username?: string, apikey?: string } if(Object.keys(query).length == 0) { - cb(new Error("You need to provide at least one argument."), null) + cb(new Error("You need to provide at least one argument."), undefined) } User.findOne({ @@ -23,9 +23,9 @@ export function findOne(params: {id?: number, username?: string, apikey?: string if(user) cb(null, user); else - cb(new Error("The requested user was not found."), null); + cb(null, undefined, { message: "The requested user was not found."}); }).catch(e => - cb(e, null) + cb(e, undefined) ); } @@ -70,8 +70,4 @@ function hashPassword(password: string, salt: string, cb: (err: Error | null, de if (err) return cb(err, null); cb(null, derivedKey.toString('hex')); }); -} - -export function createAPIKey(user: User, cb: (err: SequelizeError | null, apikey: APIKey | null) => void ) { - user.addAPIKey() } \ 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 index 61542f395..28ab0dc21 100644 --- a/server-node/src/auth/user/user-model.ts +++ b/server-node/src/auth/user/user-model.ts @@ -10,6 +10,8 @@ import { HasOneGetAssociationMixin, HasOneSetAssociationMixin, HasOneCreateAssociationMixin, } from 'sequelize'; +import { APIKey } from '../apikey/apikey-model'; + export class User extends Model, InferCreationAttributes> { declare id: CreationOptional; declare username: string; @@ -64,17 +66,6 @@ export class AccessRule extends Model, InferCreation declare ownerId: ForeignKey; declare owner?: NonAttribute; - declare createdAt: CreationOptional; - declare updatedAt: CreationOptional; -} - -export class APIKey extends Model, InferCreationAttributes> { - declare id: CreationOptional; - declare apikey: string; - - declare ownerId: ForeignKey; - declare owner?: NonAttribute; - declare createdAt: CreationOptional; declare updatedAt: CreationOptional; } \ No newline at end of file diff --git a/server-node/src/data/sequelize-relations.ts b/server-node/src/data/sequelize-relations.ts index 8c4fd6159..086b65155 100644 --- a/server-node/src/data/sequelize-relations.ts +++ b/server-node/src/data/sequelize-relations.ts @@ -5,7 +5,8 @@ const sequelize = new Sequelize("sqlite::memory:", { logging: import.meta.env.VITE_SEQUELIZE_LOGGING === "True" ? console.log : false }); -import { User, AccessRule, APIKey, Password } from "../auth/user/user-model"; +import { User, AccessRule, Password } from "../auth/user/user-model"; +import { APIKey } from "../auth/apikey/apikey-model"; User.init( { diff --git a/server-node/src/routes/api/api-controller.ts b/server-node/src/routes/api/api-controller.ts index bf4c2d3a1..280969e6f 100644 --- a/server-node/src/routes/api/api-controller.ts +++ b/server-node/src/routes/api/api-controller.ts @@ -1,13 +1,13 @@ import express, { Request, Response } from "express"; -import { checkAuthorized } from "../../auth/checkAuthorizedMiddleware"; +import { isAuthorized } from "../../auth/authenticationMiddleware"; import workflow from "./workflow-controller"; import dynamicOperations from "./dynamic-operations-controller"; const router = express.Router(); -router.use(checkAuthorized); +router.use(isAuthorized); router.get("/", (req: Request, res: Response) => { // TODO: Implement root api endpoint diff --git a/server-node/src/routes/auth/create-api-key-controller.ts b/server-node/src/routes/auth/create-api-key-controller.ts index 356766702..247e03c53 100644 --- a/server-node/src/routes/auth/create-api-key-controller.ts +++ b/server-node/src/routes/auth/create-api-key-controller.ts @@ -1,11 +1,10 @@ -import { checkAuthorized } from "../../auth/checkAuthorizedMiddleware"; -import { APIKey } from "../../auth/user/user-model"; +import * as APIKey from "../../auth/apikey/apikey-controller"; +import { whenAuthIsEnabled, isAuthorized } from "../../auth/authenticationMiddleware"; import express, { Request, Response } from "express"; const router = express.Router(); -router.post('/create-api-key', checkAuthorized, async function(req: Request, res: Response) { - const apikey: APIKey | undefined = await req.user?.createAPIKey({apikey: "test"}); //TODO: Replace with random string - res.json({apikey: apikey}); +router.post('/create-api-key', whenAuthIsEnabled, isAuthorized, async function(req: Request, res: Response) { + res.json({apikey: await APIKey.createAPIKey(req.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 index 692557398..5dbd92e80 100644 --- a/server-node/src/routes/auth/status-controller.ts +++ b/server-node/src/routes/auth/status-controller.ts @@ -1,8 +1,8 @@ -import { checkAuthorized } from "../../auth/checkAuthorizedMiddleware"; +import { isAuthorized } from "../../auth/authenticationMiddleware"; import express, { Request, Response } from "express"; const router = express.Router(); -router.get('/status', checkAuthorized, async function(req: Request, res: Response) { +router.get('/status', isAuthorized, async function(req: Request, res: Response) { res.json({user: req.user}); });