Restructuring the Project

Updating dependencies
This commit is contained in:
Fabian Stamm
2023-04-07 19:54:47 +02:00
parent 532107c479
commit 0453e461c9
121 changed files with 16380 additions and 6701 deletions

View File

@ -0,0 +1,249 @@
import Stacker from "../middlewares/stacker";
import { GetUserMiddleware } from "../middlewares/user";
import { Request, Response } from "express";
import Client from "../../models/client";
import Logging from "@hibas123/nodelogging";
import Permission, { IPermission } from "../../models/permissions";
import ClientCode from "../../models/client_code";
import moment = require("moment");
import { randomBytes } from "crypto";
// import { ObjectID } from "bson";
import Grant, { IGrant } from "../../models/grants";
import GetAuthPage from "../../views/authorize";
import { ObjectID } from "mongodb";
// const AuthRoute = Stacker(GetUserMiddleware(true), async (req: Request, res: Response) => {
// let { response_type, client_id, redirect_uri, scope, state, nored } = req.query;
// const sendError = (type) => {
// if (redirect_uri === "$local")
// redirect_uri = "/code";
// res.redirect(redirect_uri += `?error=${type}&state=${state}`);
// }
// /**
// * error
// REQUIRED. A single ASCII [USASCII] error code from the
// following:
// invalid_request
// The request is missing a required parameter, includes an
// invalid parameter value, includes a parameter more than
// once, or is otherwise malformed.
// unauthorized_client
// The client is not authorized to request an authorization
// code using this method.
// access_denied
// The resource owner or authorization server denied the
// request.
// */
// try {
// if (response_type !== "code") {
// return sendError("unsupported_response_type");
// } else {
// let client = await Client.findOne({ client_id: client_id })
// if (!client) {
// return sendError("unauthorized_client")
// }
// if (redirect_uri && client.redirect_url !== redirect_uri) {
// Logging.log(redirect_uri, client.redirect_url);
// return res.send("Invalid redirect_uri. Please check the integrity of the site requesting and contact the administrator of the page, you want to authorize!");
// }
// let permissions: IPermission[] = [];
// if (scope) {
// let perms = (<string>scope).split(";").filter(e => e !== "read_user").map(p => new ObjectID(p));
// permissions = await Permission.find({ _id: { $in: perms } })
// if (permissions.length != perms.length) {
// return sendError("invalid_scope");
// }
// }
// let code = ClientCode.new({
// user: req.user._id,
// client: client._id,
// permissions: permissions.map(p => p._id),
// validTill: moment().add(30, "minutes").toDate(),
// code: randomBytes(16).toString("hex")
// });
// await ClientCode.save(code);
// let redir = client.redirect_url === "$local" ? "/code" : client.redirect_url;
// let ruri = redir + `?code=${code.code}&state=${state}`;
// if (nored === "true") {
// res.json({
// redirect_uri: ruri
// })
// } else {
// res.redirect(ruri);
// }
// }
// } catch (err) {
// Logging.error(err);
// sendError("server_error")
// }
// })
const GetAuthRoute = (view = false) =>
Stacker(GetUserMiddleware(false), async (req: Request, res: Response) => {
let {
response_type,
client_id,
redirect_uri,
scope = "",
state,
nored,
} = req.query as { [key: string]: string };
const sendError = (type) => {
if (redirect_uri === "$local") redirect_uri = "/code";
res.redirect(
(redirect_uri += `?error=${type}${state ? "&state=" + state : ""}`)
);
};
const scopes = scope.split(";").filter((e: string) => e !== "");
Logging.debug("Scopes:", scope);
try {
if (response_type !== "code") {
return sendError("unsupported_response_type");
} else {
let client = await Client.findOne({ client_id: client_id });
if (!client) {
return sendError("unauthorized_client");
}
if (redirect_uri && client.redirect_url !== redirect_uri) {
Logging.log(redirect_uri, client.redirect_url);
return res.send(
"Invalid redirect_uri. Please check the integrity of the site requesting and contact the administrator of the page, you want to authorize!"
);
}
let permissions: IPermission[] = [];
let proms: PromiseLike<void>[] = [];
if (scopes) {
for (let perm of scopes.filter((e) => e !== "read_user")) {
let oid = undefined;
try {
oid = new ObjectID(perm);
} catch (err) {
Logging.error(err);
continue;
}
proms.push(
Permission.findById(oid).then((p) => {
if (!p) return Promise.reject(new Error());
permissions.push(p);
})
);
}
}
let err = undefined;
await Promise.all(proms).catch((e) => {
err = e;
});
if (err) {
Logging.error(err);
return sendError("invalid_scope");
}
let grant: IGrant | undefined = await Grant.findOne({
client: client._id,
user: req.user._id,
});
Logging.debug("Grant", grant, permissions);
let missing_permissions: IPermission[] = [];
if (grant) {
missing_permissions = grant.permissions
.map((perm) => permissions.find((p) => p._id.equals(perm)))
.filter((e) => !!e);
} else {
missing_permissions = permissions;
}
let client_granted_perm = missing_permissions.filter(
(e) => e.grant_type == "client"
);
if (client_granted_perm.length > 0) {
return sendError("no_permission");
}
if (!grant && missing_permissions.length > 0) {
await new Promise<void>((yes, no) =>
GetUserMiddleware(false, true)(
req,
res,
(err?: Error | string) => (err ? no(err) : yes())
)
); // Maybe unresolved when redirect is happening
if (view) {
res.send(
GetAuthPage(
req.__,
client.name,
permissions.map((perm) => {
return {
name: perm.name,
description: perm.description,
logo: client.logo,
};
})
)
);
return;
} else {
if ((req.body.allow = "true")) {
if (!grant)
grant = Grant.new({
client: client._id,
user: req.user._id,
permissions: [],
});
grant.permissions.push(
...missing_permissions.map((e) => e._id)
);
await Grant.save(grant);
} else {
return sendError("access_denied");
}
}
}
let code = ClientCode.new({
user: req.user._id,
client: client._id,
permissions: permissions.map((p) => p._id),
validTill: moment().add(30, "minutes").toDate(),
code: randomBytes(16).toString("hex"),
});
await ClientCode.save(code);
let redir =
client.redirect_url === "$local" ? "/code" : client.redirect_url;
let ruri =
redir + `?code=${code.code}${state ? "&state=" + state : ""}`;
if (nored === "true") {
res.json({
redirect_uri: ruri,
});
} else {
res.redirect(ruri);
}
}
} catch (err) {
Logging.error(err);
sendError("server_error");
}
});
export default GetAuthRoute;

View File

@ -0,0 +1,63 @@
import { Router } from "express";
import GetAuthRoute from "./auth";
import JWTRoute from "./jwt";
import Public from "./public";
import RefreshTokenRoute from "./refresh";
const OAuthRoue: Router = Router();
/**
* @api {post} /oauth/auth
* @apiName OAuthAuth
*
* @apiGroup oauth
* @apiPermission user Special required
*
* @apiParam {String} response_type must be "code" others are not supported
* @apiParam {String} client_id ClientID
* @apiParam {String} redirect_uri The URI to redirect with code
* @apiParam {String} scope Scope that contains the requested permissions (comma seperated list of permissions)
* @apiParam {String} state State, that will be passed to redirect_uri for client
* @apiParam {String} nored Deactivates the Redirect response from server and instead returns the redirect URI in JSON response
*/
OAuthRoue.post("/auth", GetAuthRoute(false));
/**
* @api {get} /oauth/jwt
* @apiName OAuthJwt
*
* @apiGroup oauth
* @apiPermission none
*
* @apiParam {String} refreshtoken
*
* @apiSuccess {String} token The JWT that allowes the application to access the recources granted for refresh token
*/
OAuthRoue.get("/jwt", JWTRoute);
/**
* @api {get} /oauth/public
* @apiName OAuthPublic
*
* @apiGroup oauth
* @apiPermission none
*
* @apiSuccess {String} public_key The applications public_key. Used to verify JWT.
*/
OAuthRoue.get("/public", Public);
/**
* @api {get} /oauth/refresh
* @apiName OAuthRefreshGet
*
* @apiGroup oauth
*/
OAuthRoue.get("/refresh", RefreshTokenRoute);
/**
* @api {post} /oauth/refresh
* @apiName OAuthRefreshPost
*
* @apiGroup oauth
*/
OAuthRoue.post("/refresh", RefreshTokenRoute);
export default OAuthRoue;

View File

@ -0,0 +1,43 @@
import { Request, Response } from "express";
import promiseMiddleware from "../../helper/promiseMiddleware";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
import RefreshToken from "../../models/refresh_token";
import User from "../../models/user";
import Client from "../../models/client";
import { getAccessTokenJWT } from "../../helper/jwt";
const JWTRoute = promiseMiddleware(async (req: Request, res: Response) => {
let { refreshtoken } = req.query as { [key: string]: string };
if (!refreshtoken)
throw new RequestError(
req.__("Refresh token not set"),
HttpStatusCode.BAD_REQUEST
);
let token = await RefreshToken.findOne({ token: refreshtoken });
if (!token)
throw new RequestError(
req.__("Invalid token"),
HttpStatusCode.BAD_REQUEST
);
let user = await User.findById(token.user);
if (!user) {
token.valid = false;
await RefreshToken.save(token);
throw new RequestError(
req.__("Invalid token"),
HttpStatusCode.BAD_REQUEST
);
}
let client = await Client.findById(token.client);
let jwt = await getAccessTokenJWT({
user,
permissions: token.permissions,
client,
});
res.json({ token: jwt });
});
export default JWTRoute;

View File

@ -0,0 +1,6 @@
import { Request, Response } from "express";
import { public_key } from "../../keys";
export default function Public(req: Request, res: Response) {
res.json({ public_key: public_key });
}

View File

@ -0,0 +1,122 @@
import { Request, Response } from "express";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
import User from "../../models/user";
import Client from "../../models/client";
import {
getAccessTokenJWT,
getIDToken,
AccessTokenJWTExp,
} from "../../helper/jwt";
import Stacker from "../middlewares/stacker";
import { GetClientAuthMiddleware } from "../middlewares/client";
import ClientCode from "../../models/client_code";
import Mail from "../../models/mail";
import { randomBytes } from "crypto";
import moment = require("moment");
// import { JWTExpDur } from "../../keys";
import RefreshToken from "../../models/refresh_token";
import { getEncryptionKey } from "../../helper/user_key";
import { refreshTokenValidTime } from "../../config";
// TODO:
/*
For example, the authorization server could employ refresh token
rotation in which a new refresh token is issued with every access
token refresh response. The previous refresh token is invalidated but retained by the authorization server. If a refresh token is
compromised and subsequently used by both the attacker and the
legitimate client, one of them will present an invalidated refresh
token, which will inform the authorization server of the breach.
*/
const RefreshTokenRoute = Stacker(
GetClientAuthMiddleware(false, false, true),
async (req: Request, res: Response) => {
let grant_type = req.query.grant_type || req.body.grant_type;
if (!grant_type || grant_type === "authorization_code") {
let code = req.query.code || req.body.code;
let nonce = req.query.nonce || req.body.nonce;
let c = await ClientCode.findOne({ code: code });
if (!c || moment(c.validTill).isBefore()) {
throw new RequestError(
req.__("Invalid code"),
HttpStatusCode.BAD_REQUEST
);
}
let client = await Client.findById(c.client);
let user = await User.findById(c.user);
let mails = await Promise.all(user.mails.map((m) => Mail.findOne(m)));
let token = RefreshToken.new({
user: c.user,
client: c.client,
permissions: c.permissions,
token: randomBytes(16).toString("hex"),
valid: true,
validTill: moment().add(refreshTokenValidTime).toDate(),
});
await RefreshToken.save(token);
await ClientCode.delete(c);
let mail = mails.find((e) => e.primary);
if (!mail) mail = mails[0];
res.json({
refresh_token: token.token,
token: token.token,
access_token: await getAccessTokenJWT({
client: client,
user: user,
permissions: c.permissions,
}),
token_type: "bearer",
expires_in: AccessTokenJWTExp.asSeconds(),
profile: {
uid: user.uid,
email: mail ? mail.mail : "",
name: user.name,
enc_key: getEncryptionKey(user, client),
},
id_token: getIDToken(user, client.client_id, nonce),
});
} else if (grant_type === "refresh_token") {
let refresh_token = req.query.refresh_token || req.body.refresh_token;
if (!refresh_token)
throw new RequestError(
req.__("refresh_token not set"),
HttpStatusCode.BAD_REQUEST
);
let token = await RefreshToken.findOne({ token: refresh_token });
if (!token || !token.valid || moment(token.validTill).isBefore())
throw new RequestError(
req.__("Invalid token"),
HttpStatusCode.BAD_REQUEST
);
token.validTill = moment().add(refreshTokenValidTime).toDate();
await RefreshToken.save(token);
let user = await User.findById(token.user);
let client = await Client.findById(token.client);
let jwt = await getAccessTokenJWT({
user,
client,
permissions: token.permissions,
});
res.json({
access_token: jwt,
expires_in: AccessTokenJWTExp.asSeconds(),
});
} else {
throw new RequestError(
"invalid grant_type",
HttpStatusCode.BAD_REQUEST
);
}
}
);
export default RefreshTokenRoute;