First version of OpenAuth remake
This commit is contained in:
12
src/api/admin.ts
Normal file
12
src/api/admin.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Request, Router } from "express";
|
||||
import ClientRoute from "./admin/client";
|
||||
import UserRoute from "./admin/user";
|
||||
import RegCodeRoute from "./admin/regcode";
|
||||
import PermissionRoute from "./admin/permission";
|
||||
|
||||
const AdminRoute: Router = Router();
|
||||
AdminRoute.use("/client", ClientRoute);
|
||||
AdminRoute.use("/regcode", RegCodeRoute)
|
||||
AdminRoute.use("/user", UserRoute)
|
||||
AdminRoute.use("/permission", PermissionRoute);
|
||||
export default AdminRoute;
|
88
src/api/admin/client.ts
Normal file
88
src/api/admin/client.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { Router, Request } from "express";
|
||||
import { GetUserMiddleware } from "../middlewares/user";
|
||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
||||
import Client from "../../models/client";
|
||||
import User from "../../models/user";
|
||||
import verify, { Types } from "../middlewares/verify";
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
|
||||
const ClientRouter: Router = Router();
|
||||
ClientRouter.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
|
||||
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN)
|
||||
else next()
|
||||
});
|
||||
ClientRouter.route("/")
|
||||
.get(promiseMiddleware(async (req, res) => {
|
||||
let clients = await Client.find({});
|
||||
//ToDo check if user is required!
|
||||
res.json(clients);
|
||||
}))
|
||||
.delete(promiseMiddleware(async (req, res) => {
|
||||
let { id } = req.query;
|
||||
await Client.delete(id);
|
||||
res.json({ success: true });
|
||||
}))
|
||||
.post(verify({
|
||||
internal: {
|
||||
type: Types.BOOLEAN,
|
||||
optional: true
|
||||
},
|
||||
name: {
|
||||
type: Types.STRING
|
||||
},
|
||||
redirect_url: {
|
||||
type: Types.STRING
|
||||
},
|
||||
website: {
|
||||
type: Types.STRING
|
||||
},
|
||||
logo: {
|
||||
type: Types.STRING,
|
||||
optional: true
|
||||
}
|
||||
}, true), promiseMiddleware(async (req, res) => {
|
||||
req.body.client_secret = randomBytes(32).toString("hex");
|
||||
let client = Client.new(req.body);
|
||||
client.maintainer = req.user._id;
|
||||
await Client.save(client)
|
||||
res.json(client);
|
||||
}))
|
||||
.put(verify({
|
||||
id: {
|
||||
type: Types.STRING,
|
||||
query: true
|
||||
},
|
||||
internal: {
|
||||
type: Types.BOOLEAN,
|
||||
optional: true
|
||||
},
|
||||
name: {
|
||||
type: Types.STRING,
|
||||
optional: true
|
||||
},
|
||||
redirect_url: {
|
||||
type: Types.STRING,
|
||||
optional: true
|
||||
},
|
||||
website: {
|
||||
type: Types.STRING,
|
||||
optional: true
|
||||
},
|
||||
logo: {
|
||||
type: Types.STRING,
|
||||
optional: true
|
||||
}
|
||||
}, true), promiseMiddleware(async (req, res) => {
|
||||
let { id } = req.query;
|
||||
let client = await Client.findById(id);
|
||||
if (!client) throw new RequestError(req.__("Client not found"), HttpStatusCode.BAD_REQUEST);
|
||||
for (let key in req.body) {
|
||||
client[key] = req.body[key];
|
||||
}
|
||||
await Client.save(client);
|
||||
res.json(client);
|
||||
}))
|
||||
|
||||
export default ClientRouter;
|
45
src/api/admin/permission.ts
Normal file
45
src/api/admin/permission.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { Request, Router } from "express";
|
||||
import { GetUserMiddleware } from "../middlewares/user";
|
||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
||||
import Permission from "../../models/permissions";
|
||||
import verify, { Types } from "../middlewares/verify";
|
||||
import Client from "../../models/client";
|
||||
|
||||
const PermissionRoute: Router = Router();
|
||||
PermissionRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
|
||||
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN)
|
||||
else next()
|
||||
});
|
||||
|
||||
|
||||
PermissionRoute.route("/")
|
||||
.get(promiseMiddleware(async (req, res) => {
|
||||
let permission = await Permission.find({});
|
||||
res.json(permission);
|
||||
}))
|
||||
.post(verify({
|
||||
clientId: {
|
||||
type: Types.NUMBER
|
||||
},
|
||||
name: {
|
||||
type: Types.STRING
|
||||
},
|
||||
description: {
|
||||
type: Types.STRING
|
||||
}
|
||||
}, true), promiseMiddleware(async (req, res) => {
|
||||
let client = await Client.findById(req.body.clientId);
|
||||
if (!client) {
|
||||
throw new RequestError("Client not found", HttpStatusCode.BAD_REQUEST);
|
||||
}
|
||||
let permission = Permission.new({
|
||||
description: req.body.description,
|
||||
name: req.body.name,
|
||||
client: client._id
|
||||
});
|
||||
await Permission.save(permission);
|
||||
res.json(permission);
|
||||
}))
|
||||
|
||||
export default PermissionRoute;
|
34
src/api/admin/regcode.ts
Normal file
34
src/api/admin/regcode.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Request, Router } from "express";
|
||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
||||
import RegCode from "../../models/regcodes";
|
||||
import { randomBytes } from "crypto";
|
||||
import moment = require("moment");
|
||||
import { GetUserMiddleware } from "../middlewares/user";
|
||||
import { HttpStatusCode } from "../../helper/request_error";
|
||||
|
||||
const RegCodeRoute: Router = Router();
|
||||
RegCodeRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
|
||||
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN)
|
||||
else next()
|
||||
});
|
||||
RegCodeRoute.route("/")
|
||||
.get(promiseMiddleware(async (req, res) => {
|
||||
let regcodes = await RegCode.find({});
|
||||
res.json(regcodes);
|
||||
}))
|
||||
.delete(promiseMiddleware(async (req, res) => {
|
||||
let { id } = req.query;
|
||||
await RegCode.delete(id);
|
||||
res.json({ success: true });
|
||||
}))
|
||||
.post(promiseMiddleware(async (req, res) => {
|
||||
let regcode = RegCode.new({
|
||||
token: randomBytes(10).toString("hex"),
|
||||
valid: true,
|
||||
validTill: moment().add("1", "month").toDate()
|
||||
})
|
||||
await RegCode.save(regcode);
|
||||
res.json({ code: regcode.token });
|
||||
}))
|
||||
|
||||
export default RegCodeRoute;
|
42
src/api/admin/user.ts
Normal file
42
src/api/admin/user.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { Request, Router } from "express";
|
||||
import { GetUserMiddleware } from "../middlewares/user";
|
||||
import { HttpStatusCode } from "../../helper/request_error";
|
||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
||||
import User from "../../models/user";
|
||||
import Mail from "../../models/mail";
|
||||
import RefreshToken from "../../models/refresh_token";
|
||||
import LoginToken from "../../models/login_token";
|
||||
|
||||
const UserRoute: Router = Router();
|
||||
UserRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
|
||||
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN)
|
||||
else next()
|
||||
})
|
||||
|
||||
UserRoute.route("/")
|
||||
.get(promiseMiddleware(async (req, res) => {
|
||||
let users = await User.find({});
|
||||
res.json(users);
|
||||
}))
|
||||
.delete(promiseMiddleware(async (req, res) => {
|
||||
let { id } = req.query;
|
||||
let user = await User.findById(id);
|
||||
|
||||
await Promise.all([
|
||||
user.mails.map(mail => Mail.delete(mail)),
|
||||
[
|
||||
RefreshToken.deleteFilter({ user: user._id }),
|
||||
LoginToken.deleteFilter({ user: user._id })
|
||||
]
|
||||
])
|
||||
|
||||
await User.delete(user);
|
||||
res.json({ success: true });
|
||||
})).put(promiseMiddleware(async (req, res) => {
|
||||
let { id } = req.query;
|
||||
let user = await User.findById(id);
|
||||
user.admin = !user.admin;
|
||||
await User.save(user);
|
||||
res.json({ success: true })
|
||||
}))
|
||||
export default UserRoute;
|
18
src/api/api.ts
Normal file
18
src/api/api.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import * as express from "express"
|
||||
import AdminRoute from "./admin";
|
||||
import UserRoute from "./user";
|
||||
import InternalRoute from "./internal";
|
||||
import Login from "./user/login";
|
||||
import { AuthGetUser } from "./client/user";
|
||||
|
||||
const ApiRouter: express.IRouter<void> = express.Router();
|
||||
ApiRouter.use("/admin", AdminRoute);
|
||||
ApiRouter.use("/user", UserRoute);
|
||||
ApiRouter.use("/internal", InternalRoute);
|
||||
|
||||
ApiRouter.use("/user", AuthGetUser);
|
||||
|
||||
// Legacy reasons (deprecated)
|
||||
ApiRouter.post("/login", Login);
|
||||
|
||||
export default ApiRouter;
|
0
src/api/client.ts
Normal file
0
src/api/client.ts
Normal file
14
src/api/client/user.ts
Normal file
14
src/api/client/user.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Request, Response } from "express"
|
||||
import Stacker from "../middlewares/stacker";
|
||||
import { GetClientAuthMiddleware } from "../middlewares/client";
|
||||
import { GetUserMiddleware } from "../middlewares/user";
|
||||
import { createJWT } from "../../keys";
|
||||
|
||||
export const AuthGetUser = Stacker(GetClientAuthMiddleware(false), GetUserMiddleware(true, false), async (req: Request, res: Response) => {
|
||||
let jwt = await createJWT({
|
||||
client: req.client.client_id,
|
||||
uid: req.user.uid,
|
||||
username: req.user.username
|
||||
}, 30); //after 30 seconds this token is invalid
|
||||
res.redirect(req.query.redirect_uri + "?jwt=" + jwt)
|
||||
});
|
8
src/api/internal.ts
Normal file
8
src/api/internal.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Router } from "express";
|
||||
import { OAuthInternalApp } from "./internal/oauth";
|
||||
import PasswordAuth from "./internal/password";
|
||||
|
||||
const InternalRoute: Router = Router();
|
||||
InternalRoute.get("/oauth", OAuthInternalApp);
|
||||
InternalRoute.post("/password", PasswordAuth)
|
||||
export default InternalRoute;
|
29
src/api/internal/oauth.ts
Normal file
29
src/api/internal/oauth.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import Stacker from "../middlewares/stacker";
|
||||
import { GetClientAuthMiddleware } from "../middlewares/client";
|
||||
import { UserMiddleware } from "../middlewares/user";
|
||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||
import ClientCode from "../../models/client_code";
|
||||
import moment = require("moment");
|
||||
import { randomBytes } from "crypto";
|
||||
export const OAuthInternalApp = Stacker(GetClientAuthMiddleware(false, true), UserMiddleware,
|
||||
async (req: Request, res: Response) => {
|
||||
let { redirect_uri, state } = req.query
|
||||
if (!redirect_uri) {
|
||||
throw new RequestError("No redirect url set!", HttpStatusCode.BAD_REQUEST);
|
||||
}
|
||||
|
||||
let sep = redirect_uri.indexOf("?") < 0 ? "?" : "&";
|
||||
|
||||
let code = ClientCode.new({
|
||||
user: req.user._id,
|
||||
client: req.client._id,
|
||||
validTill: moment().add(30, "minutes").toDate(),
|
||||
code: randomBytes(16).toString("hex"),
|
||||
permissions: []
|
||||
});
|
||||
await ClientCode.save(code);
|
||||
|
||||
res.redirect(redirect_uri + sep + "code=" + code.code + (state ? "&state=" + state : ""));
|
||||
res.end();
|
||||
});
|
25
src/api/internal/password.ts
Normal file
25
src/api/internal/password.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { GetClientAuthMiddleware } from "../middlewares/client";
|
||||
import Stacker from "../middlewares/stacker";
|
||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||
import User from "../../models/user";
|
||||
|
||||
const PasswordAuth = Stacker(GetClientAuthMiddleware(true, true), async (req: Request, res: Response) => {
|
||||
let { username, password, uid }: { username: string, password: string, uid: string } = req.body;
|
||||
let query: any = { password: password };
|
||||
if (username) {
|
||||
query.username = username.toLowerCase()
|
||||
} else if (uid) {
|
||||
query.uid = uid;
|
||||
} else {
|
||||
throw new RequestError(req.__("No username or uid set"), HttpStatusCode.BAD_REQUEST);
|
||||
}
|
||||
|
||||
let user = await User.findOne(query);
|
||||
if (!user) {
|
||||
res.json({ error: req.__("Password or username wrong") })
|
||||
} else {
|
||||
res.json({ success: true, uid: user.uid });
|
||||
}
|
||||
});
|
||||
export default PasswordAuth;
|
76
src/api/middlewares/client.ts
Normal file
76
src/api/middlewares/client.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
24
src/api/middlewares/stacker.ts
Normal file
24
src/api/middlewares/stacker.ts
Normal 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;
|
83
src/api/middlewares/user.ts
Normal file
83
src/api/middlewares/user.ts
Normal 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();
|
125
src/api/middlewares/verify.ts
Normal file
125
src/api/middlewares/verify.ts
Normal 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()
|
||||
}
|
||||
}
|
13
src/api/oauth.ts
Normal file
13
src/api/oauth.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Router } from "express";
|
||||
import AuthRoute from "./oauth/auth";
|
||||
import JWTRoute from "./oauth/jwt";
|
||||
import Public from "./oauth/public";
|
||||
import RefreshTokenRoute from "./oauth/refresh";
|
||||
|
||||
const OAuthRoue: Router = Router();
|
||||
OAuthRoue.post("/auth", AuthRoute);
|
||||
OAuthRoue.get("/jwt", JWTRoute)
|
||||
OAuthRoue.get("/public", Public)
|
||||
OAuthRoue.get("/refresh", RefreshTokenRoute);
|
||||
OAuthRoue.post("/refresh", RefreshTokenRoute);
|
||||
export default OAuthRoue;
|
81
src/api/oauth/auth.ts
Normal file
81
src/api/oauth/auth.ts
Normal file
@ -0,0 +1,81 @@
|
||||
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 { Sequelize } from "sequelize-typescript";
|
||||
import ClientCode from "../../models/client_code";
|
||||
import moment = require("moment");
|
||||
import { randomBytes } from "crypto";
|
||||
import { ObjectID } from "bson";
|
||||
|
||||
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) => {
|
||||
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(";").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 ruri = client.redirect_url + `?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")
|
||||
}
|
||||
})
|
||||
export default AuthRoute;
|
27
src/api/oauth/jwt.ts
Normal file
27
src/api/oauth/jwt.ts
Normal file
@ -0,0 +1,27 @@
|
||||
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 Permission from "../../models/permissions";
|
||||
import Client from "../../models/client";
|
||||
import getOAuthJWT from "../../helper/jwt";
|
||||
|
||||
const JWTRoute = promiseMiddleware(async (req: Request, res: Response) => {
|
||||
let { refreshtoken } = req.query;
|
||||
if (!refreshtoken) throw new RequestError(req.__("Refresh token not set"), HttpStatusCode.BAD_REQUEST);
|
||||
|
||||
let token = await RefreshToken.findOne({ where: { token: refreshtoken }, include: [User, Permission, Client] });
|
||||
if (!token) throw new RequestError(req.__("Invalid token"), HttpStatusCode.BAD_REQUEST);
|
||||
|
||||
let user = await User.findById(token.user);
|
||||
if (!user) {
|
||||
//TODO handle error!
|
||||
}
|
||||
|
||||
let client = await Client.findById(token.client);
|
||||
|
||||
let jwt = await getOAuthJWT({ user, permissions: token.permissions, client });
|
||||
res.json({ token: jwt });
|
||||
})
|
||||
export default JWTRoute;
|
6
src/api/oauth/public.ts
Normal file
6
src/api/oauth/public.ts
Normal 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 })
|
||||
}
|
79
src/api/oauth/refresh.ts
Normal file
79
src/api/oauth/refresh.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { Request, Response } from "express";
|
||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||
import User from "../../models/user";
|
||||
import Permission from "../../models/permissions";
|
||||
import Client from "../../models/client";
|
||||
import getOAuthJWT 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 { Promise } from "bluebird";
|
||||
|
||||
const RefreshTokenRoute = Stacker(GetClientAuthMiddleware(false, false, true), async (req: Request, res: Response) => {
|
||||
let code = req.query.code || req.body.code;
|
||||
let redirect_uri = req.query.redirect_uri || req.body.redirect_uri;
|
||||
let grant_type = req.query.grant_type || req.body.grant_type;
|
||||
if (!grant_type || grant_type === "authorization_code") {
|
||||
let c = await ClientCode.findOne({ where: { code: code }, include: [{ model: User, include: [Mail] }, Client, Permission] })
|
||||
if (!c) {
|
||||
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(6, "months").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 getOAuthJWT({
|
||||
client: client,
|
||||
user: user,
|
||||
permissions: c.permissions
|
||||
}),
|
||||
token_type: "bearer",
|
||||
expires_in: JWTExpDur.asSeconds(),
|
||||
profile: {
|
||||
uid: user.uid,
|
||||
email: mail ? mail.mail : "",
|
||||
name: user.name,
|
||||
}
|
||||
});
|
||||
} 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) throw new RequestError(req.__("Invalid token"), HttpStatusCode.BAD_REQUEST);
|
||||
|
||||
let user = await User.findById(token.user);
|
||||
let client = await Client.findById(token.client)
|
||||
let jwt = await getOAuthJWT({ user, client, permissions: token.permissions });
|
||||
res.json({ access_token: jwt, expires_in: JWTExpDur.asSeconds() });
|
||||
} else {
|
||||
throw new RequestError("invalid grant_type", HttpStatusCode.BAD_REQUEST);
|
||||
}
|
||||
})
|
||||
|
||||
export default RefreshTokenRoute;
|
8
src/api/user.ts
Normal file
8
src/api/user.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Request, Router } from "express";
|
||||
import Register from "./user/register";
|
||||
import Login from "./user/login";
|
||||
|
||||
const UserRoute: Router = Router();
|
||||
UserRoute.post("/register", Register);
|
||||
UserRoute.post("/login", Login)
|
||||
export default UserRoute;
|
81
src/api/user/login.ts
Normal file
81
src/api/user/login.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { Request, Response } from "express"
|
||||
import User, { IUser } from "../../models/user";
|
||||
import { randomBytes } from "crypto";
|
||||
import moment = require("moment");
|
||||
import LoginToken from "../../models/login_token";
|
||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
||||
|
||||
const Login = promiseMiddleware(async (req: Request, res: Response) => {
|
||||
let type = req.query.type;
|
||||
if (type === "username") {
|
||||
let { username, uid } = req.query;
|
||||
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid });
|
||||
if (!user) {
|
||||
res.json({ error: req.__("User not found") })
|
||||
} else {
|
||||
res.json({ salt: user.salt, uid: user.uid });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const sendToken = async (user: IUser) => {
|
||||
let token_str = randomBytes(16).toString("hex");
|
||||
let token_exp = moment().add(6, "months").toDate()
|
||||
let token = LoginToken.new({
|
||||
token: token_str,
|
||||
valid: true,
|
||||
validTill: token_exp,
|
||||
user: user._id
|
||||
});
|
||||
await LoginToken.save(token);
|
||||
|
||||
let special_str = randomBytes(24).toString("hex");
|
||||
let special_exp = moment().add(30, "minutes").toDate()
|
||||
let special = LoginToken.new({
|
||||
token: special_str,
|
||||
valid: true,
|
||||
validTill: special_exp,
|
||||
special: true,
|
||||
user: user._id
|
||||
});
|
||||
await LoginToken.save(special);
|
||||
|
||||
res.json({
|
||||
login: { token: token_str, expires: token_exp.toUTCString() },
|
||||
special: { token: special_str, expires: special_exp.toUTCString() }
|
||||
});
|
||||
}
|
||||
|
||||
if (type === "password" || type === "twofactor") {
|
||||
let { username, password, uid } = req.body;
|
||||
|
||||
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid })
|
||||
if (!user) {
|
||||
res.json({ error: req.__("User not found") })
|
||||
} else {
|
||||
if (user.password !== password) {
|
||||
res.json({ error: req.__("Password or username wrong") })
|
||||
} else {
|
||||
if (type === "twofactor") {
|
||||
|
||||
} else {
|
||||
if (user.twofactor && user.twofactor.length > 0) {
|
||||
let types = user.twofactor.map(f => {
|
||||
return { type: f.type };
|
||||
})
|
||||
res.json({
|
||||
types: types
|
||||
});
|
||||
} else {
|
||||
await sendToken(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new RequestError("Invalid type!", HttpStatusCode.BAD_REQUEST);
|
||||
}
|
||||
});
|
||||
|
||||
export default Login;
|
142
src/api/user/register.ts
Normal file
142
src/api/user/register.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import { Request, Response, Router } from "express"
|
||||
import Stacker from "../middlewares/stacker";
|
||||
import verify, { Types } from "../middlewares/verify";
|
||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
||||
import User, { Gender } from "../../models/user";
|
||||
import { HttpStatusCode } from "../../helper/request_error";
|
||||
import Mail from "../../models/mail";
|
||||
import RegCode from "../../models/regcodes";
|
||||
|
||||
const Register = Stacker(verify({
|
||||
mail: {
|
||||
type: Types.EMAIL,
|
||||
notempty: true
|
||||
},
|
||||
username: {
|
||||
type: Types.STRING,
|
||||
notempty: true
|
||||
},
|
||||
password: {
|
||||
type: Types.STRING,
|
||||
notempty: true
|
||||
},
|
||||
salt: {
|
||||
type: Types.STRING,
|
||||
notempty: true
|
||||
},
|
||||
regcode: {
|
||||
type: Types.STRING,
|
||||
notempty: true
|
||||
},
|
||||
gender: {
|
||||
type: Types.STRING,
|
||||
notempty: true
|
||||
},
|
||||
name: {
|
||||
type: Types.STRING,
|
||||
notempty: true
|
||||
},
|
||||
// birthday: {
|
||||
// type: Types.DATE
|
||||
// }
|
||||
}), promiseMiddleware(async (req: Request, res: Response) => {
|
||||
let { username, password, salt, mail, gender, name, birthday, regcode } = req.body;
|
||||
let u = await User.findOne({ username: username.toLowerCase() })
|
||||
if (u) {
|
||||
let err = {
|
||||
message: [
|
||||
{
|
||||
message: req.__("Username taken"),
|
||||
field: "username"
|
||||
}
|
||||
],
|
||||
status: HttpStatusCode.BAD_REQUEST,
|
||||
nolog: true
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
|
||||
let m = await Mail.findOne({ mail: mail })
|
||||
if (m) {
|
||||
let err = {
|
||||
message: [
|
||||
{
|
||||
message: req.__("Mail linked with other account"),
|
||||
field: "mail"
|
||||
}
|
||||
],
|
||||
status: HttpStatusCode.BAD_REQUEST,
|
||||
nolog: true
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
let regc = await RegCode.findOne({ token: regcode })
|
||||
if (!regc) {
|
||||
let err = {
|
||||
message: [
|
||||
{
|
||||
message: req.__("Invalid registration code"),
|
||||
field: "regcode"
|
||||
}
|
||||
],
|
||||
status: HttpStatusCode.BAD_REQUEST,
|
||||
nolog: true
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!regc.valid) {
|
||||
let err = {
|
||||
message: [
|
||||
{
|
||||
message: req.__("Registration code already used"),
|
||||
field: "regcode"
|
||||
}
|
||||
],
|
||||
status: HttpStatusCode.BAD_REQUEST,
|
||||
nolog: true
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
let g = -1;
|
||||
switch (gender) {
|
||||
case "male":
|
||||
g = Gender.male
|
||||
break;
|
||||
case "female":
|
||||
g = Gender.female
|
||||
break;
|
||||
case "other":
|
||||
g = Gender.other
|
||||
break;
|
||||
default:
|
||||
g = Gender.none
|
||||
break;
|
||||
}
|
||||
|
||||
let user = User.new({
|
||||
username: username.toLowerCase(),
|
||||
password: password,
|
||||
salt: salt,
|
||||
gender: g,
|
||||
name: name,
|
||||
// birthday: birthday,
|
||||
admin: false
|
||||
})
|
||||
|
||||
regc.valid = false;
|
||||
await RegCode.save(regc);
|
||||
|
||||
let ml = Mail.new({
|
||||
mail: mail,
|
||||
primary: true
|
||||
})
|
||||
|
||||
user.mails.push(ml._id);
|
||||
await User.save(user)
|
||||
res.json({ success: true });
|
||||
}))
|
||||
export default Register;
|
Reference in New Issue
Block a user