Browse Source

Running prettier

master
Fabian Stamm 2 years ago
parent
commit
51a8609880
  1. 10
      apidoc.json
  2. 82
      locales/de.json
  3. 32
      locales/en.json
  4. 6
      package-lock.json
  5. 4
      package.json
  6. 178
      src/api/admin/client.ts
  7. 14
      src/api/admin/index.ts
  8. 12
      src/api/admin/permission.ts
  9. 60
      src/api/admin/regcode.ts
  10. 92
      src/api/admin/user.ts
  11. 98
      src/api/client/index.ts
  12. 24
      src/api/client/permissions.ts
  13. 6
      src/api/index.ts
  14. 12
      src/api/internal/index.ts
  15. 24
      src/api/internal/oauth.ts
  16. 44
      src/api/internal/password.ts
  17. 57
      src/api/middlewares/client.ts
  18. 26
      src/api/middlewares/stacker.ts
  19. 6
      src/api/middlewares/user.ts
  20. 90
      src/api/middlewares/verify.ts
  21. 43
      src/api/oauth/auth.ts
  22. 22
      src/api/oauth/index.ts
  23. 27
      src/api/oauth/jwt.ts
  24. 4
      src/api/oauth/public.ts
  25. 147
      src/api/oauth/refresh.ts
  26. 23
      src/api/user/account.ts
  27. 23
      src/api/user/contact.ts
  28. 31
      src/api/user/index.ts
  29. 91
      src/api/user/login.ts
  30. 257
      src/api/user/register.ts
  31. 60
      src/api/user/token.ts
  32. 118
      src/api/user/twofactor/backup/index.ts
  33. 7
      src/api/user/twofactor/helper.ts
  34. 66
      src/api/user/twofactor/index.ts
  35. 190
      src/api/user/twofactor/otc/index.ts
  36. 280
      src/api/user/twofactor/yubikey/index.ts
  37. 90
      src/config.ts
  38. 8
      src/database.ts
  39. 4
      src/express.d.ts
  40. 70
      src/helper/jwt.ts
  41. 10
      src/helper/promiseMiddleware.ts
  42. 2
      src/helper/random.ts
  43. 16
      src/helper/request_error.ts
  44. 16
      src/helper/user_key.ts
  45. 103
      src/index.ts
  46. 42
      src/keys.ts
  47. 30
      src/models/client.ts
  48. 30
      src/models/client_code.ts
  49. 20
      src/models/grants.ts
  50. 91
      src/models/login_token.ts
  51. 22
      src/models/mail.ts
  52. 39
      src/models/permissions.ts
  53. 28
      src/models/refresh_token.ts
  54. 22
      src/models/regcodes.ts
  55. 38
      src/models/twofactor.ts
  56. 197
      src/models/user.ts
  57. 50
      src/testdata.ts
  58. 12
      src/views/admin.ts
  59. 34
      src/views/authorize.ts
  60. 16
      src/views/login.ts
  61. 12
      src/views/register.ts
  62. 30
      tsconfig.json
  63. 128
      views/build.js
  64. 8
      views/shared/cookie.js
  65. 7
      views/shared/event.js
  66. 12
      views/shared/formdata.js
  67. 22
      views/shared/inputs.js
  68. 24
      views/shared/inputs.scss
  69. 2
      views/shared/mat_bs.scss
  70. 2
      views/shared/preact.min.js
  71. 28
      views/shared/request.js
  72. 485
      views/shared/sha512.js
  73. 4
      views/shared/style.scss
  74. 150
      views/src/admin/admin.js
  75. 2
      views/src/admin/admin.scss
  76. 13
      views/src/authorize/authorize.js
  77. 44
      views/src/authorize/authorize.scss
  78. 154
      views/src/login/login.js
  79. 55
      views/src/login/login.scss
  80. 624
      views/src/login/login.tsx
  81. 1544
      views/src/login/u2f-api-polyfill.js
  82. 2
      views/src/main/main.js
  83. 130
      views/src/register/register.js
  84. 15
      views/src/register/register.scss
  85. 25
      views/tsconfig.json
  86. 10
      views/types.d.ts
  87. 2
      views_repo

10
apidoc.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"name": "openauth",
"description": "Open Auth REST API",
"title": "Open Auth REST",
"url": "/api"
}
"name": "openauth",
"description": "Open Auth REST API",
"title": "Open Auth REST",
"url": "/api"
}

82
locales/de.json

@ -1,42 +1,42 @@ @@ -1,42 +1,42 @@
{
"User not found": "Benutzer nicht gefunden",
"Password or username wrong": "Passwort oder Benutzername falsch",
"Authorize %s": "Authorize %s",
"Login": "Einloggen",
"You are not logged in or your login is expired": "Du bist nicht länger angemeldet oder deine Anmeldung ist abgelaufen.",
"Username or Email": "Benutzername oder Email",
"Password": "Passwort",
"Next": "Weiter",
"Register": "Registrieren",
"Mail": "Mail",
"Repeat Password": "Passwort wiederholen",
"Username": "Benutzername",
"Name": "Name",
"Registration code": "Registrierungs Schlüssel",
"You need to select one of the options": "Du musst eine der Optionen auswälen",
"Male": "Mann",
"Female": "Frau",
"Other": "Anderes",
"Registration code required": "Registrierungs Schlüssel benötigt",
"Username required": "Benutzername benötigt",
"Name required": "Name benötigt",
"Mail required": "Mail benötigt",
"The passwords do not match": "Die Passwörter stimmen nicht überein",
"Password is required": "Password benötigt",
"Invalid registration code": "Ungültiger Registrierungs Schlüssel",
"Username taken": "Benutzername nicht verfügbar",
"Mail linked with other account": "Mail ist bereits mit einem anderen Account verbunden",
"Registration code already used": "Registrierungs Schlüssel wurde bereits verwendet",
"Administration": "Administration",
"Field {{field}} is not defined": "Feld {{field}} fehlt",
"Field {{field}} has wrong type. It should be from type {{type}}": "Feld {{field}} hat den falschen Typ. Es sollte vom Typ {{type}} sein",
"Client has no permission for acces password auth": "Dieser Client hat keine Berechtigung password auth zu benutzen",
"Invalid token": "Ungültiger Token",
"By clicking on ALLOW, you allow this app to access the requested recources.": "Wenn sie ALLOW drücken, berechtigen sie die Applikation die beantragten Resourcen zu benutzen.",
"User": "User",
"No special token": "No special token",
"Login token invalid": "Login token invalid",
"No login token": "No login token",
"You are not logged in or your login is expired (Login token invalid)": "You are not logged in or your login is expired (Login token invalid)",
"You are not logged in or your login is expired (No special token)": "You are not logged in or your login is expired (No special token)"
}
"User not found": "Benutzer nicht gefunden",
"Password or username wrong": "Passwort oder Benutzername falsch",
"Authorize %s": "Authorize %s",
"Login": "Einloggen",
"You are not logged in or your login is expired": "Du bist nicht länger angemeldet oder deine Anmeldung ist abgelaufen.",
"Username or Email": "Benutzername oder Email",
"Password": "Passwort",
"Next": "Weiter",
"Register": "Registrieren",
"Mail": "Mail",
"Repeat Password": "Passwort wiederholen",
"Username": "Benutzername",
"Name": "Name",
"Registration code": "Registrierungs Schlüssel",
"You need to select one of the options": "Du musst eine der Optionen auswälen",
"Male": "Mann",
"Female": "Frau",
"Other": "Anderes",
"Registration code required": "Registrierungs Schlüssel benötigt",
"Username required": "Benutzername benötigt",
"Name required": "Name benötigt",
"Mail required": "Mail benötigt",
"The passwords do not match": "Die Passwörter stimmen nicht überein",
"Password is required": "Password benötigt",
"Invalid registration code": "Ungültiger Registrierungs Schlüssel",
"Username taken": "Benutzername nicht verfügbar",
"Mail linked with other account": "Mail ist bereits mit einem anderen Account verbunden",
"Registration code already used": "Registrierungs Schlüssel wurde bereits verwendet",
"Administration": "Administration",
"Field {{field}} is not defined": "Feld {{field}} fehlt",
"Field {{field}} has wrong type. It should be from type {{type}}": "Feld {{field}} hat den falschen Typ. Es sollte vom Typ {{type}} sein",
"Client has no permission for acces password auth": "Dieser Client hat keine Berechtigung password auth zu benutzen",
"Invalid token": "Ungültiger Token",
"By clicking on ALLOW, you allow this app to access the requested recources.": "Wenn sie ALLOW drücken, berechtigen sie die Applikation die beantragten Resourcen zu benutzen.",
"User": "User",
"No special token": "No special token",
"Login token invalid": "Login token invalid",
"No login token": "No login token",
"You are not logged in or your login is expired (Login token invalid)": "You are not logged in or your login is expired (Login token invalid)",
"You are not logged in or your login is expired (No special token)": "You are not logged in or your login is expired (No special token)"
}

32
locales/en.json

@ -1,17 +1,17 @@ @@ -1,17 +1,17 @@
{
"Login": "Login",
"Username or Email": "Username or Email",
"Password": "Password",
"Next": "Next",
"Invalid code": "Invalid code",
"You are not logged in or your login is expired": "You are not logged in or your login is expired",
"User not found": "User not found",
"No special token": "No special token",
"You are not logged in or your login is expired(No special token)": "You are not logged in or your login is expired(No special token)",
"Special token invalid": "Special token invalid",
"You are not logged in or your login is expired(Special token invalid)": "You are not logged in or your login is expired(Special token invalid)",
"No login token": "No login token",
"Login token invalid": "Login token invalid",
"Authorize %s": "Authorize %s",
"By clicking on ALLOW, you allow this app to access the requested recources.": "By clicking on ALLOW, you allow this app to access the requested recources."
}
"Login": "Login",
"Username or Email": "Username or Email",
"Password": "Password",
"Next": "Next",
"Invalid code": "Invalid code",
"You are not logged in or your login is expired": "You are not logged in or your login is expired",
"User not found": "User not found",
"No special token": "No special token",
"You are not logged in or your login is expired(No special token)": "You are not logged in or your login is expired(No special token)",
"Special token invalid": "Special token invalid",
"You are not logged in or your login is expired(Special token invalid)": "You are not logged in or your login is expired(Special token invalid)",
"No login token": "No login token",
"Login token invalid": "Login token invalid",
"Authorize %s": "Authorize %s",
"By clicking on ALLOW, you allow this app to access the requested recources.": "By clicking on ALLOW, you allow this app to access the requested recources."
}

6
package-lock.json generated

@ -2361,6 +2361,12 @@ @@ -2361,6 +2361,12 @@
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
"dev": true
},
"prettier": {
"version": "2.0.5",
"resolved": "https://npm.hibas123.de/prettier/-/prettier-2.0.5.tgz",
"integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==",
"dev": true
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",

4
package.json

@ -19,7 +19,8 @@ @@ -19,7 +19,8 @@
"watch-views": "cd views && npm run watch",
"install-views_repo": "git submodule init && git submodule update && cd views_repo && npm install ",
"build-views_repo": "cd views_repo && npm run build",
"watch-views_repo": "cd views_repo && npm run dev"
"watch-views_repo": "cd views_repo && npm run dev",
"format": "prettier --write ."
},
"pipelines": {
"install": [
@ -47,6 +48,7 @@ @@ -47,6 +48,7 @@
"apidoc": "^0.20.0",
"concurrently": "^5.1.0",
"nodemon": "^2.0.2",
"prettier": "^2.0.5",
"typescript": "^3.8.3"
},
"dependencies": {

178
src/api/admin/client.ts

@ -5,16 +5,15 @@ import Client from "../../models/client"; @@ -5,16 +5,15 @@ import Client from "../../models/client";
import verify, { Types } from "../middlewares/verify";
import { randomBytes } from "crypto";
const ClientRouter: Router = Router();
ClientRouter.route("/")
/**
* @api {get} /admin/client
* @apiName AdminGetClients
*
*
* @apiGroup admin_client
* @apiPermission admin
*
*
* @apiSuccess {Object[]} clients
* @apiSuccess {String} clients._id The internally used id
* @apiSuccess {String} clients.maintainer
@ -26,24 +25,26 @@ ClientRouter.route("/") @@ -26,24 +25,26 @@ ClientRouter.route("/")
* @apiSuccess {String} clients.client_id Client ID used outside of DB
* @apiSuccess {String} clients.client_secret
*/
.get(promiseMiddleware(async (req, res) => {
let clients = await Client.find({});
//ToDo check if user is required!
res.json(clients);
}))
.get(
promiseMiddleware(async (req, res) => {
let clients = await Client.find({});
//ToDo check if user is required!
res.json(clients);
})
)
/**
* @api {get} /admin/client
* @apiName AdminAddClients
*
*
* @apiGroup admin_client
* @apiPermission admin
*
*
* @apiParam {Boolean} internal Is it an internal app
* @apiParam {String} name
* @apiParam {String} redirect_url
* @apiParam {String} website
* @apiParam {String} logo
*
*
* @apiSuccess {Object[]} clients
* @apiSuccess {String} clients._id The internally used id
* @apiSuccess {String} clients.maintainer
@ -55,62 +56,70 @@ ClientRouter.route("/") @@ -55,62 +56,70 @@ ClientRouter.route("/")
* @apiSuccess {String} clients.client_id Client ID used outside of DB
* @apiSuccess {String} clients.client_secret
*/
.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);
}))
.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);
})
);
ClientRouter.route("/:id")
/**
* @api {delete} /admin/client/:id
* @apiParam {String} id Client _id
* @apiName AdminDeleteClient
*
*
* @apiGroup admin_client
* @apiPermission admin
*
*
* @apiSuccess {Boolean} success
*/
.delete(promiseMiddleware(async (req, res) => {
let { id } = req.params;
await Client.delete(id);
res.json({ success: true });
}))
.delete(
promiseMiddleware(async (req, res) => {
let { id } = req.params;
await Client.delete(id);
res.json({ success: true });
})
)
/**
* @api {put} /admin/client/:id
* @apiParam {String} id Client _id
* @apiName AdminUpdateClient
*
*
* @apiGroup admin_client
* @apiPermission admin
*
*
* @apiParam {Boolean} internal Is it an internal app
* @apiParam {String} name
* @apiParam {String} redirect_url
* @apiParam {String} website
* @apiParam {String} logo
*
*
* @apiSuccess {String} _id The internally used id
* @apiSuccess {String} maintainer UserID of client maintainer
* @apiSuccess {Boolean} internal Defines if it is a internal client
@ -118,40 +127,49 @@ ClientRouter.route("/:id") @@ -118,40 +127,49 @@ ClientRouter.route("/:id")
* @apiSuccess {String} redirect_url Redirect URL after login
* @apiSuccess {String} website Website of Client
* @apiSuccess {String} logo The Logo of the Client (optional)
* @apiSuccess {String} client_id Client ID used outside of DB
* @apiSuccess {String} client_id Client ID used outside of DB
* @apiSuccess {String} client_secret The client secret, that can be used to obtain token
*/
.put(verify({
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);
}))
.put(
verify(
{
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;
export default ClientRouter;

14
src/api/admin/index.ts

@ -9,12 +9,16 @@ import RequestError, { HttpStatusCode } from "../../helper/request_error"; @@ -9,12 +9,16 @@ import RequestError, { HttpStatusCode } from "../../helper/request_error";
const AdminRoute: Router = Router();
AdminRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
if (!req.isAdmin) throw new RequestError("You have no permission to access this API", HttpStatusCode.FORBIDDEN);
else next()
if (!req.isAdmin)
throw new RequestError(
"You have no permission to access this API",
HttpStatusCode.FORBIDDEN
);
else next();
});
AdminRoute.use("/client", ClientRoute);
AdminRoute.use("/regcode", RegCodeRoute)
AdminRoute.use("/user", UserRoute)
AdminRoute.use("/regcode", RegCodeRoute);
AdminRoute.use("/user", UserRoute);
AdminRoute.use("/permission", PermissionRoute);
export default AdminRoute;
export default AdminRoute;

12
src/api/admin/permission.ts

@ -56,18 +56,18 @@ PermissionRoute.route("/") @@ -56,18 +56,18 @@ PermissionRoute.route("/")
verify(
{
client: {
type: Types.STRING
type: Types.STRING,
},
name: {
type: Types.STRING
type: Types.STRING,
},
description: {
type: Types.STRING
type: Types.STRING,
},
type: {
type: Types.ENUM,
values: ["user", "client"]
}
values: ["user", "client"],
},
},
true
),
@ -83,7 +83,7 @@ PermissionRoute.route("/") @@ -83,7 +83,7 @@ PermissionRoute.route("/")
description: req.body.description,
name: req.body.name,
client: client._id,
grant_type: req.body.type
grant_type: req.body.type,
});
await Permission.save(permission);
res.json(permission);

60
src/api/admin/regcode.ts

@ -10,54 +10,60 @@ const RegCodeRoute: Router = Router(); @@ -10,54 +10,60 @@ const RegCodeRoute: Router = Router();
RegCodeRoute.route("/")
/**
* @api {get} /admin/regcode
* @apiName AdminGetRegcodes
*
* @apiName AdminGetRegcodes
*
* @apiGroup admin_regcode
* @apiPermission admin
*
*
* @apiSuccess {Object[]} regcodes
* @apiSuccess {String} permissions._id The ID
* @apiSuccess {String} permissions.token The Regcode Token
* @apiSuccess {String} permissions.valid Defines if the Regcode is valid
* @apiSuccess {String} permissions.validTill Expiration date of RegCode
*/
.get(promiseMiddleware(async (req, res) => {
let regcodes = await RegCode.find({});
res.json(regcodes);
}))
.get(
promiseMiddleware(async (req, res) => {
let regcodes = await RegCode.find({});
res.json(regcodes);
})
)
/**
* @api {delete} /admin/regcode
* @apiName AdminDeleteRegcode
*
*
* @apiParam {String} id The id of the RegCode
*
*
* @apiGroup admin_regcode
* @apiPermission admin
*
*
* @apiSuccess {Boolean} success
*/
.delete(promiseMiddleware(async (req, res) => {
let { id } = req.query;
await RegCode.delete(id);
res.json({ success: true });
}))
.delete(
promiseMiddleware(async (req, res) => {
let { id } = req.query;
await RegCode.delete(id);
res.json({ success: true });
})
)
/**
* @api {post} /admin/regcode
* @apiName AdminAddRegcode
*
* @apiName AdminAddRegcode
*
* @apiGroup admin_regcode
* @apiPermission admin
*
*
* @apiSuccess {String} code The newly created code
*/
.post(promiseMiddleware(async (req, res) => {
let regcode = RegCode.new({
token: randomBytes(10).toString("hex"),
valid: true,
validTill: moment().add("1", "month").toDate()
.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 });
})
await RegCode.save(regcode);
res.json({ code: regcode.token });
}))
);
export default RegCodeRoute;
export default RegCodeRoute;

92
src/api/admin/user.ts

@ -9,15 +9,15 @@ import LoginToken from "../../models/login_token"; @@ -9,15 +9,15 @@ 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()
})
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN);
else next();
});
UserRoute.route("/")
/**
* @api {get} /admin/user
* @apiName AdminGetUsers
*
* @apiName AdminGetUsers
*
* @apiGroup admin_user
* @apiPermission admin
* @apiSuccess {Object[]} user
@ -29,57 +29,65 @@ UserRoute.route("/") @@ -29,57 +29,65 @@ UserRoute.route("/")
* @apiSuccess {Number} user.gender 0 = none, 1 = male, 2 = female, 3 = other
* @apiSuccess {Boolean} user.admin Is admin or not
*/
.get(promiseMiddleware(async (req, res) => {
let users = await User.find({});
users.forEach(e => delete e.password && delete e.salt && delete e.encryption_key);
res.json(users);
}))
.get(
promiseMiddleware(async (req, res) => {
let users = await User.find({});
users.forEach(
(e) => delete e.password && delete e.salt && delete e.encryption_key
);
res.json(users);
})
)
/**
* @api {delete} /admin/user
* @apiName AdminDeleteUser
*
* @apiName AdminDeleteUser
*
* @apiParam {String} id The User ID
*
*
* @apiGroup admin_user
* @apiPermission admin
*
*
* @apiSuccess {Boolean} success
*/
.delete(promiseMiddleware(async (req, res) => {
let { id } = req.query;
let user = await User.findById(id);
.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 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 });
}))
await User.delete(user);
res.json({ success: true });
})
)
/**
* @api {put} /admin/user
* @apiName AdminChangeUser
*
* @apiName AdminChangeUser
*
* @apiParam {String} id The User ID
*
*
* @apiGroup admin_user
* @apiPermission admin
*
*
* @apiSuccess {Boolean} success
*
* @apiDescription Flipps the user role:
* admin -> user
*
* @apiDescription Flipps the user role:
* admin -> user
* user -> admin
*/
.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;
.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;

98
src/api/client/index.ts

@ -1,6 +1,9 @@ @@ -1,6 +1,9 @@
import { Request, Response, Router } from "express"
import { Request, Response, Router } from "express";
import Stacker from "../middlewares/stacker";
import { GetClientAuthMiddleware, GetClientApiAuthMiddleware } from "../middlewares/client";
import {
GetClientAuthMiddleware,
GetClientApiAuthMiddleware,
} from "../middlewares/client";
import { GetUserMiddleware } from "../middlewares/user";
import { createJWT } from "../../keys";
import Client from "../../models/client";
@ -8,55 +11,74 @@ import RequestError, { HttpStatusCode } from "../../helper/request_error"; @@ -8,55 +11,74 @@ import RequestError, { HttpStatusCode } from "../../helper/request_error";
import config from "../../config";
import Mail from "../../models/mail";
const ClientRouter = Router();
/**
* @api {get} /client/user
*
*
* @apiDescription Can be used for simple authentication of user. It will redirect the user to the redirect URI with a very short lived jwt.
*
*
* @apiParam {String} redirect_uri URL to redirect to on success
* @apiParam {String} state A optional state, that will be included in the JWT and redirect_uri as parameter
*
* @apiName ClientUser
* @apiName ClientUser
* @apiGroup client
*
* @apiPermission user_client Requires ClientID and Authenticated User
*/
ClientRouter.get("/user", Stacker(GetClientAuthMiddleware(false), GetUserMiddleware(false, false), async (req: Request, res: Response) => {
let { redirect_uri, state } = req.query;
if (redirect_uri !== req.client.redirect_url)
throw new RequestError("Invalid redirect URI", HttpStatusCode.BAD_REQUEST);
let jwt = await createJWT({
client: req.client.client_id,
uid: req.user.uid,
username: req.user.username,
state: state
}, {
expiresIn: 30,
issuer: config.core.url,
algorithm: "RS256",
subject: req.user.uid,
audience: req.client.client_id
}); //after 30 seconds this token is invalid
res.redirect(redirect_uri + "?jwt=" + jwt + (state ? `&state=${state}` : ""));
}));
ClientRouter.get("/account", Stacker(GetClientApiAuthMiddleware(), async (req: Request, res) => {
let mails = await Promise.all(req.user.mails.map(id => Mail.findById(id)));
let mail = mails.find(e => e.primary) || mails[0];
res.json({
user: {
username: req.user.username,
name: req.user.name,
email: mail
ClientRouter.get(
"/user",
Stacker(
GetClientAuthMiddleware(false),
GetUserMiddleware(false, false),
async (req: Request, res: Response) => {
let { redirect_uri, state } = req.query;
if (redirect_uri !== req.client.redirect_url)
throw new RequestError(
"Invalid redirect URI",
HttpStatusCode.BAD_REQUEST
);
let jwt = await createJWT(
{
client: req.client.client_id,
uid: req.user.uid,
username: req.user.username,
state: state,
},
{
expiresIn: 30,
issuer: config.core.url,
algorithm: "RS256",
subject: req.user.uid,
audience: req.client.client_id,
}
); //after 30 seconds this token is invalid
res.redirect(
redirect_uri + "?jwt=" + jwt + (state ? `&state=${state}` : "")
);
}
)
);
ClientRouter.get(
"/account",
Stacker(GetClientApiAuthMiddleware(), async (req: Request, res) => {
let mails = await Promise.all(
req.user.mails.map((id) => Mail.findById(id))
);
let mail = mails.find((e) => e.primary) || mails[0];
res.json({
user: {
username: req.user.username,
name: req.user.name,
email: mail,
},
});
})
}));
);
export default ClientRouter;

24
src/api/client/permissions.ts

@ -2,7 +2,7 @@ import { Request, Response } from "express"; @@ -2,7 +2,7 @@ import { Request, Response } from "express";
import Stacker from "../middlewares/stacker";
import {
ClientAuthMiddleware,
GetClientAuthMiddleware
GetClientAuthMiddleware,
} from "../middlewares/client";
import Permission from "../../models/permissions";
import User from "../../models/user";
@ -22,19 +22,19 @@ export const GetPermissions = Stacker( @@ -22,19 +22,19 @@ export const GetPermissions = Stacker(
if (user) {
const grant = await Grant.findOne({
client: req.client._id,
user: user
user: user,
});
permissions = await Promise.all(
grant.permissions.map(perm => Permission.findById(perm))
).then(res =>
grant.permissions.map((perm) => Permission.findById(perm))
).then((res) =>
res
.filter(e => e.grant_type === "client")
.map(e => {
.filter((e) => e.grant_type === "client")
.map((e) => {
return {
id: e._id.toHexString(),
name: e.name,
description: e.description
description: e.description,
};
})
);
@ -43,10 +43,10 @@ export const GetPermissions = Stacker( @@ -43,10 +43,10 @@ export const GetPermissions = Stacker(
if (permission) {
const grants = await Grant.find({
client: req.client._id,
permissions: new ObjectID(permission)
permissions: new ObjectID(permission),
});
users = grants.map(grant => grant.user.toHexString());
users = grants.map((grant) => grant.user.toHexString());
}
res.json({ permissions, users });
@ -73,14 +73,14 @@ export const PostPermissions = Stacker( @@ -73,14 +73,14 @@ export const PostPermissions = Stacker(
let grant = await Grant.findOne({
client: req.client._id,
user: req.user._id
user: req.user._id,
});
if (!grant) {
grant = Grant.new({
client: req.client._id,
user: req.user._id,
permissions: []
permissions: [],
});
}
@ -92,7 +92,7 @@ export const PostPermissions = Stacker( @@ -92,7 +92,7 @@ export const PostPermissions = Stacker(
await Grant.save(grant);
res.json({
success: true
success: true,
});
}
);

6
src/api/index.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import * as express from "express"
import * as express from "express";
import AdminRoute from "./admin";
import UserRoute from "./user";
import InternalRoute from "./internal";
@ -9,7 +9,7 @@ import OAuthRoute from "./oauth"; @@ -9,7 +9,7 @@ import OAuthRoute from "./oauth";
const ApiRouter: express.IRouter = express.Router();
ApiRouter.use("/admin", AdminRoute);
ApiRouter.use(cors())
ApiRouter.use(cors());
ApiRouter.use("/user", UserRoute);
ApiRouter.use("/internal", InternalRoute);
ApiRouter.use("/oauth", OAuthRoute);
@ -22,4 +22,4 @@ ApiRouter.use("/", ClientRouter); @@ -22,4 +22,4 @@ ApiRouter.use("/", ClientRouter);
// Legacy reasons (deprecated)
ApiRouter.post("/login", Login);
export default ApiRouter;
export default ApiRouter;

12
src/api/internal/index.ts

@ -6,10 +6,10 @@ const InternalRoute: Router = Router(); @@ -6,10 +6,10 @@ const InternalRoute: Router = Router();
/**
* @api {get} /internal/oauth
* @apiName ClientInteralOAuth
*
*
* @apiGroup client_internal
* @apiPermission client_internal Only ClientID
*
*
* @apiParam {String} redirect_uri Redirect URI called after success
* @apiParam {String} state State will be set in RedirectURI for the client to check
*/
@ -18,13 +18,13 @@ InternalRoute.get("/oauth", OAuthInternalApp); @@ -18,13 +18,13 @@ InternalRoute.get("/oauth", OAuthInternalApp);
/**
* @api {post} /internal/password
* @apiName ClientInteralPassword
*
*
* @apiGroup client_internal
* @apiPermission client_internal Requires ClientID and Secret
*
*
* @apiParam {String} username Username (either username or UID)
* @apiParam {String} uid User ID (either username or UID)
* @apiParam {String} password Hashed and Salted according to specification
*/
InternalRoute.post("/password", PasswordAuth)
export default InternalRoute;
InternalRoute.post("/password", PasswordAuth);
export default InternalRoute;

24
src/api/internal/oauth.ts

@ -6,11 +6,16 @@ import RequestError, { HttpStatusCode } from "../../helper/request_error"; @@ -6,11 +6,16 @@ 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,
export const OAuthInternalApp = Stacker(
GetClientAuthMiddleware(false, true),
UserMiddleware,
async (req: Request, res: Response) => {
let { redirect_uri, state } = req.query
let { redirect_uri, state } = req.query;
if (!redirect_uri) {
throw new RequestError("No redirect url set!", HttpStatusCode.BAD_REQUEST);
throw new RequestError(
"No redirect url set!",
HttpStatusCode.BAD_REQUEST
);
}
let sep = redirect_uri.indexOf("?") < 0 ? "?" : "&";
@ -20,10 +25,17 @@ export const OAuthInternalApp = Stacker(GetClientAuthMiddleware(false, true), Us @@ -20,10 +25,17 @@ export const OAuthInternalApp = Stacker(GetClientAuthMiddleware(false, true), Us
client: req.client._id,
validTill: moment().add(30, "minutes").toDate(),
code: randomBytes(16).toString("hex"),
permissions: []
permissions: [],
});
await ClientCode.save(code);
res.redirect(redirect_uri + sep + "code=" + code.code + (state ? "&state=" + state : ""));
res.redirect(
redirect_uri +
sep +
"code=" +
code.code +
(state ? "&state=" + state : "")
);
res.end();
});
}
);

44
src/api/internal/password.ts

@ -4,22 +4,32 @@ import Stacker from "../middlewares/stacker"; @@ -4,22 +4,32 @@ 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);
}
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 });
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;
);
export default PasswordAuth;

57
src/api/middlewares/client.ts

@ -6,7 +6,11 @@ import User from "../../models/user"; @@ -6,7 +6,11 @@ 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) {
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;
@ -24,19 +28,29 @@ export function GetClientAuthMiddleware(checksecret = true, internal = false, ch @@ -24,19 +28,29 @@ export function GetClientAuthMiddleware(checksecret = true, internal = false, ch
}
if (!client_id || (!client_secret && checksecret)) {
throw new RequestError("No client credentials", HttpStatusCode.BAD_REQUEST);
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;
if (!checksecret && !(checksecret_if_available && client_secret))
delete w.client_secret;
let client = await Client.findOne(w)
let client = await Client.findOne(w);
if (!client) {
throw new RequestError("Invalid client_id" + (checksecret ? "or client_secret" : ""), HttpStatusCode.BAD_REQUEST);
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)
throw new RequestError(
req.__("Client has no permission for access"),
HttpStatusCode.FORBIDDEN
);
}
req.client = client;
next();
@ -44,7 +58,7 @@ export function GetClientAuthMiddleware(checksecret = true, internal = false, ch @@ -44,7 +58,7 @@ export function GetClientAuthMiddleware(checksecret = true, internal = false, ch
if (next) next(e);
else throw e;
}
}
};
}
export const ClientAuthMiddleware = GetClientAuthMiddleware();
@ -52,10 +66,13 @@ export const ClientAuthMiddleware = GetClientAuthMiddleware(); @@ -52,10 +66,13 @@ 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: string = req.query.access_token || req.headers.authorization;
if (!token)
throw invalid_err;
const invalid_err = new RequestError(
req.__("You are not logged in or your login is expired"),
HttpStatusCode.UNAUTHORIZED
);
let token: string =
req.query.access_token || req.headers.authorization;
if (!token) throw invalid_err;
if (token.toLowerCase().startsWith("bearer "))
token = token.substring(7);
@ -64,19 +81,21 @@ export function GetClientApiAuthMiddleware(permissions?: string[]) { @@ -64,19 +81,21 @@ export function GetClientApiAuthMiddleware(permissions?: string[]) {
try {
data = await validateJWT(token);
} catch (err) {
throw invalid_err
throw invalid_err;
}
let user = await User.findOne({ uid: data.user });
if (!user)
throw invalid_err;
if (!user) throw invalid_err;
let client = await Client.findOne({ client_id: data.application })
if (!client)
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)))
if (
permissions &&
(!data.permissions ||
!permissions.every((e) => data.permissions.indexOf(e) >= 0))
)
throw invalid_err;
req.user = user;
@ -86,5 +105,5 @@ export function GetClientApiAuthMiddleware(permissions?: string[]) { @@ -86,5 +105,5 @@ export function GetClientApiAuthMiddleware(permissions?: string[]) {
if (next) next(e);
else throw e;
}
}
};
}

26
src/api/middlewares/stacker.ts

@ -8,19 +8,21 @@ function call(handler: RH, req: Request, res: Response) { @@ -8,19 +8,21 @@ function call(handler: RH, req: Request, res: Response) {
let p = handler(req, res, (err) => {
if (err) no(err);
else yes();
})
if (p && p.catch) p.catch(err => no(err));
})
});
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);
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();
}
next();
});
}
export default Stacker;
);
};
export default Stacker;

6
src/api/middlewares/user.ts

@ -22,7 +22,7 @@ export function GetUserMiddleware( @@ -22,7 +22,7 @@ export function GetUserMiddleware(
redirect_uri?: string,
validated = true
) {
return promiseMiddleware(async function(
return promiseMiddleware(async function (
req: Request,
res: Response,
next?: NextFunction
@ -57,7 +57,7 @@ export function GetUserMiddleware( @@ -57,7 +57,7 @@ export function GetUserMiddleware(
token: special,
special: true,
valid: true,
user: token.user
user: token.user,
});
if (!(await CheckToken(special_token, validated)))
invalid("Special token invalid");
@ -68,7 +68,7 @@ export function GetUserMiddleware( @@ -68,7 +68,7 @@ export function GetUserMiddleware(
req.isAdmin = user.admin;
req.token = {
login: token,
special: special_token
special: special_token,
};
if (next) next();

90
src/api/middlewares/verify.ts

@ -1,6 +1,14 @@ @@ -1,6 +1,14 @@
import { Request, Response, NextFunction } from "express"
import { Request, Response, NextFunction } from "express";
import { Logging } from "@hibas123/nodelogging";
import { isBoolean, isString, isNumber, isObject, isDate, isArray, isSymbol } from "util";
import {
isBoolean,
isString,
isNumber,
isObject,
isDate,
isArray,
isSymbol,
} from "util";
import RequestError, { HttpStatusCode } from "../../helper/request_error";
export enum Types {
@ -11,39 +19,41 @@ export enum Types { @@ -11,39 +19,41 @@ export enum Types {
OBJECT,
DATE,
ARRAY,
ENUM
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)
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
type: Types;
query?: boolean;
optional?: boolean;
/**
* Only when Type.ENUM
*
*
* values to check before
*/
values?: string[]
values?: string[];
/**
* Only when Type.STRING
*/
notempty?: boolean // Only STRING
notempty?: boolean; // Only STRING
}
export interface Checks {
[index: string]: CheckObject// | Types
[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 }[] = []
let errors: { message: string; field: string }[] = [];
function check(data: any, field_name: string, field: CheckObject) {
if (data !== undefined && data !== null) {
@ -78,48 +88,60 @@ export default function (fields: Checks, noadditional = false) { @@ -78,48 +88,60 @@ export default function (fields: Checks, noadditional = false) {
}
break;
default:
Logging.error(`Invalid type to check: ${field.type} ${Types[field.type]}`)
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
})
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
})
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)
let field = fields[field_name];
let data = fields[field_name].query
? req.query[field_name]
: req.body[field_name];