userdefined api-keys

This commit is contained in:
Felix Kaspar 2024-06-01 15:11:32 +02:00
parent 28862d70ca
commit a884268d97
11 changed files with 92 additions and 47 deletions

View File

@ -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<APIKey> {
const apikey = crypto.randomUUID(); // TODO: Is this secure enough?
const apikeyEntry = await APIKey.create({ apikey: apikey })
await user?.addAPIKey(apikeyEntry);
return apikeyEntry;
}

View File

@ -0,0 +1,15 @@
import { User } from '../user/user-model';
import {
Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ForeignKey,
} from 'sequelize';
export class APIKey extends Model<InferAttributes<APIKey>, InferCreationAttributes<APIKey>> {
declare id: CreationOptional<number>;
declare apikey: string;
declare ownerId: ForeignKey<User['id']>;
declare owner?: NonAttribute<User>;
declare createdAt: CreationOptional<Date>;
declare updatedAt: CreationOptional<Date>;
}

View File

@ -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."});
}

View File

@ -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."});
}

View File

@ -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);
});
});

View File

@ -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<T, F extends keyof T> = Pick<T, F> & { [K in keyof Omit<T, F>]?: 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()
}

View File

@ -10,6 +10,8 @@ import {
HasOneGetAssociationMixin, HasOneSetAssociationMixin, HasOneCreateAssociationMixin,
} from 'sequelize';
import { APIKey } from '../apikey/apikey-model';
export class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
declare id: CreationOptional<number>;
declare username: string;
@ -64,17 +66,6 @@ export class AccessRule extends Model<InferAttributes<AccessRule>, InferCreation
declare ownerId: ForeignKey<User['id']>;
declare owner?: NonAttribute<User>;
declare createdAt: CreationOptional<Date>;
declare updatedAt: CreationOptional<Date>;
}
export class APIKey extends Model<InferAttributes<APIKey>, InferCreationAttributes<APIKey>> {
declare id: CreationOptional<number>;
declare apikey: string;
declare ownerId: ForeignKey<User['id']>;
declare owner?: NonAttribute<User>;
declare createdAt: CreationOptional<Date>;
declare updatedAt: CreationOptional<Date>;
}

View File

@ -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(
{

View File

@ -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

View File

@ -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;

View File

@ -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});
});