First version of OpenAuth remake

This commit is contained in:
Fabian Stamm
2018-11-06 20:48:50 +01:00
commit ac69e73344
89 changed files with 14355 additions and 0 deletions

View File

@ -0,0 +1,76 @@
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 && 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 || req.headers.authorization;
if (!token)
throw invalid_err;
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;
}
}
}

View File

@ -0,0 +1,24 @@
import { Request, Response, NextFunction, RequestHandler } from "express";
import promiseMiddleware from "../../helper/promiseMiddleware";
function call(handler: RequestHandler, req: Request, res: Response) {
return new Promise((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: RequestHandler[]) => {
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;

View File

@ -0,0 +1,83 @@
import { NextFunction, Request, Response } from "express";
import LoginToken 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 Checks if requests wants an json or html for returning errors
* @param redirect_uri Sets the uri to redirect, if json is not set and user not logged in
*/
export function GetUserMiddleware(json = false, special_token: boolean = false, redirect_uri?: string) {
return promiseMiddleware(async function (req: Request, res: Response, next?: NextFunction) {
const invalid = () => {
throw new Invalid();
}
try {
let { login, special } = req.cookies
if (!login) invalid()
let token = await LoginToken.findOne({ token: login, valid: true })
if (!token) invalid()
let user = await User.findById(token.user);
if (!user) {
token.valid = false;
await LoginToken.save(token);
invalid();
}
if (token.validTill.getTime() < new Date().getTime()) { //Token expired
token.valid = false;
await LoginToken.save(token);
invalid()
}
if (special) {
Logging.debug("Special found")
let st = await LoginToken.findOne({ token: special, special: true, valid: true })
if (st && st.valid && st.user.toHexString() === token.user.toHexString()) {
if (st.validTill.getTime() < new Date().getTime()) { //Token expired
Logging.debug("Special expired")
st.valid = false;
await LoginToken.save(st);
} else {
Logging.debug("Special valid")
req.special = true;
}
}
}
if (special_token && !req.special) invalid();
req.user = user
req.isAdmin = user.admin;
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=" + new Buffer(redirect_uri ? redirect_uri : req.originalUrl).toString("base64"))
} else {
throw new RequestError(req.__("You are not logged in or your login is expired"), HttpStatusCode.UNAUTHORIZED)
}
} else {
if (next) next(e);
else throw e;
}
return false;
}
});
}
export const UserMiddleware = GetUserMiddleware();

View File

@ -0,0 +1,125 @@
import { Request, Response, NextFunction } from "express"
import { Logging } from "@hibas123/nodelogging";
import { isBoolean, isString, isNumber, isObject, isDate, isArray, isSymbol } 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 (isNumber(data)) return;
break;
case Types.EMAIL:
if (isEmail(data)) return;
break;
case Types.BOOLEAN:
if (isBoolean(data)) return;
break;
case Types.OBJECT:
if (isObject(data)) return;
break;
case Types.ARRAY:
if (isArray(data)) return;
break;
case Types.DATE:
if (isDate(data)) return;
break;
case Types.ENUM:
if (isString(data)) {
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()
}
}