Restructuring the Project
Updating dependencies
This commit is contained in:
110
Backend/src/api/middlewares/client.ts
Normal file
110
Backend/src/api/middlewares/client.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||
import Client from "../../models/client";
|
||||
import { validateJWT } from "../../keys";
|
||||
import User from "../../models/user";
|
||||
import Mail from "../../models/mail";
|
||||
import { OAuthJWT } from "../../helper/jwt";
|
||||
|
||||
export function GetClientAuthMiddleware(
|
||||
checksecret = true,
|
||||
internal = false,
|
||||
checksecret_if_available = false
|
||||
) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
let client_id = req.query.client_id || req.body.client_id;
|
||||
let client_secret = req.query.client_secret || req.body.client_secret;
|
||||
|
||||
if (!client_id && !client_secret && req.headers.authorization) {
|
||||
let header = req.headers.authorization;
|
||||
let [type, val] = header.split(" ");
|
||||
if (val) {
|
||||
let str = Buffer.from(val, "base64").toString("utf-8");
|
||||
let [id, secret] = str.split(":");
|
||||
client_id = id;
|
||||
client_secret = secret;
|
||||
}
|
||||
}
|
||||
|
||||
if (!client_id || (!client_secret && checksecret)) {
|
||||
throw new RequestError(
|
||||
"No client credentials",
|
||||
HttpStatusCode.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
let w = { client_id: client_id, client_secret: client_secret };
|
||||
if (!checksecret && !(checksecret_if_available && client_secret))
|
||||
delete w.client_secret;
|
||||
|
||||
let client = await Client.findOne(w);
|
||||
|
||||
if (!client) {
|
||||
throw new RequestError(
|
||||
"Invalid client_id" + (checksecret ? "or client_secret" : ""),
|
||||
HttpStatusCode.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
if (internal && !client.internal) {
|
||||
throw new RequestError(
|
||||
req.__("Client has no permission for access"),
|
||||
HttpStatusCode.FORBIDDEN
|
||||
);
|
||||
}
|
||||
req.client = client;
|
||||
next();
|
||||
} catch (e) {
|
||||
if (next) next(e);
|
||||
else throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const ClientAuthMiddleware = GetClientAuthMiddleware();
|
||||
|
||||
export function GetClientApiAuthMiddleware(permissions?: string[]) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const invalid_err = new RequestError(
|
||||
req.__("You are not logged in or your login is expired"),
|
||||
HttpStatusCode.UNAUTHORIZED
|
||||
);
|
||||
let token =
|
||||
(req.query.access_token as string) ||
|
||||
(req.headers.authorization as string);
|
||||
if (!token) throw invalid_err;
|
||||
|
||||
if (token.toLowerCase().startsWith("bearer "))
|
||||
token = token.substring(7);
|
||||
|
||||
let data: OAuthJWT;
|
||||
try {
|
||||
data = await validateJWT(token);
|
||||
} catch (err) {
|
||||
throw invalid_err;
|
||||
}
|
||||
|
||||
let user = await User.findOne({ uid: data.user });
|
||||
|
||||
if (!user) throw invalid_err;
|
||||
|
||||
let client = await Client.findOne({ client_id: data.application });
|
||||
if (!client) throw invalid_err;
|
||||
|
||||
if (
|
||||
permissions &&
|
||||
(!data.permissions ||
|
||||
!permissions.every((e) => data.permissions.indexOf(e) >= 0))
|
||||
)
|
||||
throw invalid_err;
|
||||
|
||||
req.user = user;
|
||||
req.client = client;
|
||||
next();
|
||||
} catch (e) {
|
||||
if (next) next(e);
|
||||
else throw e;
|
||||
}
|
||||
};
|
||||
}
|
28
Backend/src/api/middlewares/stacker.ts
Normal file
28
Backend/src/api/middlewares/stacker.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Request, Response, NextFunction, RequestHandler } from "express";
|
||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
||||
|
||||
type RH = (req: Request, res: Response, next?: NextFunction) => any;
|
||||
|
||||
function call(handler: RH, req: Request, res: Response) {
|
||||
return new Promise<void>((yes, no) => {
|
||||
let p = handler(req, res, (err) => {
|
||||
if (err) no(err);
|
||||
else yes();
|
||||
});
|
||||
if (p && p.catch) p.catch((err) => no(err));
|
||||
});
|
||||
}
|
||||
|
||||
const Stacker = (...handler: RH[]) => {
|
||||
return promiseMiddleware(
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
let hc = handler.concat();
|
||||
while (hc.length > 0) {
|
||||
let h = hc.shift();
|
||||
await call(h, req, res);
|
||||
}
|
||||
next();
|
||||
}
|
||||
);
|
||||
};
|
||||
export default Stacker;
|
106
Backend/src/api/middlewares/user.ts
Normal file
106
Backend/src/api/middlewares/user.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import LoginToken, { CheckToken } from "../../models/login_token";
|
||||
import Logging from "@hibas123/nodelogging";
|
||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||
import User from "../../models/user";
|
||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
||||
|
||||
class Invalid extends Error {}
|
||||
|
||||
/**
|
||||
* Returns customized Middleware function, that could also be called directly
|
||||
* by code and will return true or false depending on the token. In the false
|
||||
* case it will also send error and redirect if json is not set
|
||||
* @param json Default false. Checks if requests wants an json or html for returning errors
|
||||
* @param special_required Default false. If true, a special token is required
|
||||
* @param redirect_uri Default current uri. Sets the uri to redirect, if json is not set and user not logged in
|
||||
* @param validated Default true. If false, the token must not be validated
|
||||
*/
|
||||
export function GetUserMiddleware(
|
||||
json = false,
|
||||
special_required: boolean = false,
|
||||
redirect_uri?: string,
|
||||
validated = true
|
||||
) {
|
||||
return promiseMiddleware(async function (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next?: NextFunction
|
||||
) {
|
||||
const invalid = (message: string) => {
|
||||
throw new Invalid(req.__(message));
|
||||
};
|
||||
try {
|
||||
let { login, special } = req.query as { [key: string]: string };
|
||||
if (!login) {
|
||||
login = req.cookies.login;
|
||||
special = req.cookies.special;
|
||||
}
|
||||
if (!login) invalid("No login token");
|
||||
if (!special && special_required) invalid("No special token");
|
||||
|
||||
let token = await LoginToken.findOne({ token: login, valid: true });
|
||||
if (!(await CheckToken(token, validated)))
|
||||
invalid("Login token invalid");
|
||||
|
||||
let user = await User.findById(token.user);
|
||||
if (!user) {
|
||||
token.valid = false;
|
||||
await LoginToken.save(token);
|
||||
invalid("Login token invalid");
|
||||
}
|
||||
|
||||
let special_token;
|
||||
if (special) {
|
||||
Logging.debug("Special found");
|
||||
special_token = await LoginToken.findOne({
|
||||
token: special,
|
||||
special: true,
|
||||
valid: true,
|
||||
user: token.user,
|
||||
});
|
||||
if (!(await CheckToken(special_token, validated)))
|
||||
invalid("Special token invalid");
|
||||
req.special = true;
|
||||
}
|
||||
|
||||
req.user = user;
|
||||
req.isAdmin = user.admin;
|
||||
req.token = {
|
||||
login: token,
|
||||
special: special_token,
|
||||
};
|
||||
|
||||
if (next) next();
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof Invalid) {
|
||||
if (req.method === "GET" && !json) {
|
||||
res.status(HttpStatusCode.UNAUTHORIZED);
|
||||
res.redirect(
|
||||
"/login?base64=true&state=" +
|
||||
Buffer.from(
|
||||
redirect_uri ? redirect_uri : req.originalUrl
|
||||
).toString("base64")
|
||||
);
|
||||
} else {
|
||||
throw new RequestError(
|
||||
req.__(
|
||||
"You are not logged in or your login is expired" +
|
||||
` (${e.message})`
|
||||
),
|
||||
HttpStatusCode.UNAUTHORIZED,
|
||||
undefined,
|
||||
{ auth: true }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (next) next(e);
|
||||
else throw e;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const UserMiddleware = GetUserMiddleware();
|
142
Backend/src/api/middlewares/verify.ts
Normal file
142
Backend/src/api/middlewares/verify.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import Logging from "@hibas123/nodelogging";
|
||||
import {
|
||||
isString,
|
||||
isDate,
|
||||
} from "util";
|
||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||
|
||||
export enum Types {
|
||||
STRING,
|
||||
NUMBER,
|
||||
BOOLEAN,
|
||||
EMAIL,
|
||||
OBJECT,
|
||||
DATE,
|
||||
ARRAY,
|
||||
ENUM,
|
||||
}
|
||||
|
||||
function isEmail(value: any): boolean {
|
||||
return /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
export interface CheckObject {
|
||||
type: Types;
|
||||
query?: boolean;
|
||||
optional?: boolean;
|
||||
|
||||
/**
|
||||
* Only when Type.ENUM
|
||||
*
|
||||
* values to check before
|
||||
*/
|
||||
values?: string[];
|
||||
|
||||
/**
|
||||
* Only when Type.STRING
|
||||
*/
|
||||
notempty?: boolean; // Only STRING
|
||||
}
|
||||
|
||||
export interface Checks {
|
||||
[index: string]: CheckObject; // | Types
|
||||
}
|
||||
|
||||
// req: Request, res: Response, next: NextFunction
|
||||
export default function (fields: Checks, noadditional = false) {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
let errors: { message: string; field: string }[] = [];
|
||||
|
||||
function check(data: any, field_name: string, field: CheckObject) {
|
||||
if (data !== undefined && data !== null) {
|
||||
switch (field.type) {
|
||||
case Types.STRING:
|
||||
if (isString(data)) {
|
||||
if (!field.notempty) return;
|
||||
if (data !== "") return;
|
||||
}
|
||||
break;
|
||||
case Types.NUMBER:
|
||||
if (typeof data == "number") return;
|
||||
break;
|
||||
case Types.EMAIL:
|
||||
if (isEmail(data)) return;
|
||||
break;
|
||||
case Types.BOOLEAN:
|
||||
if (typeof data == "boolean") return;
|
||||
break;
|
||||
case Types.OBJECT:
|
||||
if (typeof data == "object") return;
|
||||
break;
|
||||
case Types.ARRAY:
|
||||
if (Array.isArray(data)) return;
|
||||
break;
|
||||
case Types.DATE:
|
||||
if (isDate(data)) return;
|
||||
break;
|
||||
case Types.ENUM:
|
||||
if (typeof data == "string") {
|
||||
if (field.values.indexOf(data) >= 0) return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Logging.error(
|
||||
`Invalid type to check: ${field.type} ${Types[field.type]}`
|
||||
);
|
||||
}
|
||||
errors.push({
|
||||
message: res.__(
|
||||
"Field {{field}} has wrong type. It should be from type {{type}}",
|
||||
{ field: field_name, type: Types[field.type].toLowerCase() }
|
||||
),
|
||||
field: field_name,
|
||||
});
|
||||
} else {
|
||||
if (!field.optional)
|
||||
errors.push({
|
||||
message: res.__("Field {{field}} is not defined", {
|
||||
field: field_name,
|
||||
}),
|
||||
field: field_name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (let field_name in fields) {
|
||||
let field = fields[field_name];
|
||||
let data = fields[field_name].query
|
||||
? req.query[field_name]
|
||||
: req.body[field_name];
|
||||
check(data, field_name, field);
|
||||
}
|
||||
|
||||
if (noadditional) {
|
||||
//Checks if the data given has additional parameters
|
||||
let should = Object.keys(fields);
|
||||
should = should.filter((e) => !fields[e].query); //Query parameters should not exist on body
|
||||
let has = Object.keys(req.body);
|
||||
|
||||
has.every((e) => {
|
||||
if (should.indexOf(e) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
errors.push({
|
||||
message: res.__("Field {{field}} should not be there", {
|
||||
field: e,
|
||||
}),
|
||||
field: e,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
let err = new RequestError(errors, HttpStatusCode.BAD_REQUEST, true);
|
||||
next(err);
|
||||
} else next();
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user