A simple file sharing site with an easy to use API and online panel.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

152 Zeilen
4.7KB

  1. const express = require('express');
  2. const router = express.Router();
  3. const config = require('config');
  4. const fs = require('fs').promises;
  5. const ModelPath = '../../models/';
  6. const User = require(ModelPath + 'User.js');
  7. const Invite = require(ModelPath + 'Invite.js');
  8. const passport = require('passport');
  9. const canonicalizeRequest = require('../../util/canonicalize').canonicalizeRequest;
  10. const requireAuth = require('../../util/auth').requireAuth;
  11. const wrap = require('../../util/wrap.js');
  12. const verifyBody = require('../../util/verifyBody');
  13. const rateLimit = require('express-rate-limit');
  14. // Wraps passport.authenticate to return a promise
  15. const authenticate = (req, res, next) => {
  16. return new Promise((resolve) => {
  17. passport.authenticate('local', (err, user) => {
  18. resolve(user);
  19. })(req, res, next);
  20. });
  21. };
  22. // Wraps passport session creation for async usage
  23. const login = (user, req) => {
  24. return new Promise((resolve) => {
  25. req.login(user, resolve);
  26. });
  27. };
  28. // Query the database for a valid invite code. An error message property is set if invalid.
  29. const validateInvite = wrap(async (req, res, next) => {
  30. const invite = await Invite.findOne({code: req.body.invite}).catch(next);
  31. if (!invite) {
  32. // Log failure
  33. await fs.appendFile('auth.log', `${new Date().toISOString()} register ${req.ip}\n`);
  34. return res.status(422).json({message: 'Invalid invite code.'});
  35. }
  36. if (invite.used)
  37. return res.status(422).json({message: 'Invite already used.'});
  38. if (invite.expires != null && invite.expires < Date.now())
  39. return res.status(422).json({message: 'Invite expired.'});
  40. req.invite = invite;
  41. next();
  42. });
  43. // Check if the requested username is valid
  44. const validateUsername = wrap(async (req, res, next) => {
  45. const username = req.body.username;
  46. const count = await User.countDocuments({username: username}).catch(next);
  47. if (count !== 0)
  48. return res.status(422).json({message: 'Username in use.'});
  49. next();
  50. });
  51. const registerLimiter = config.get('RateLimit.enable')
  52. ? rateLimit({
  53. windowMs: config.get('RateLimit.register.window') * 1000,
  54. max: config.get('RateLimit.register.max'),
  55. skipSuccessfulRequests: true
  56. })
  57. : (req, res, next) => { next(); };
  58. const registerProps = [
  59. {
  60. name: 'displayname',
  61. type: 'string',
  62. maxLength: config.get('User.Username.maxLength'),
  63. sanitize: true,
  64. restrict: new RegExp(config.get('User.Username.restrictedChars')),
  65. },
  66. {name: 'password', type: 'string'},
  67. {name: 'invite', type: 'string'}];
  68. router.post('/register',
  69. registerLimiter,
  70. verifyBody(registerProps), canonicalizeRequest,
  71. validateInvite, validateUsername,
  72. wrap(async (req, res, next) => {
  73. // Update the database
  74. await Promise.all([
  75. User.register({
  76. username: req.body.username,
  77. displayname: req.body.displayname,
  78. scope: req.invite.scope,
  79. date: Date.now()
  80. }, req.body.password).catch(next),
  81. Invite.updateOne({code: req.invite.code}, {recipient: req.body.username, used: Date.now()}).catch(next)
  82. ]);
  83. res.status(200).json({'message': 'Registration successful.'});
  84. }));
  85. const loginLimiter = config.get('RateLimit.enable')
  86. ? rateLimit({
  87. windowMs: config.get('RateLimit.login.window') * 1000,
  88. max: config.get('RateLimit.login.max'),
  89. skipSuccessfulRequests: true
  90. })
  91. : (req, res, next) => { next(); };
  92. const loginProps = [
  93. {name: 'username', type: 'string', optional: true},
  94. {name: 'displayname', type: 'string', optional: true},
  95. {name: 'password', type: 'string'}];
  96. router.post('/login',
  97. loginLimiter,
  98. verifyBody(loginProps),
  99. canonicalizeRequest,
  100. wrap(async (req, res, next) => {
  101. // Authenticate
  102. const user = await authenticate(req, res, next);
  103. if (!user) {
  104. // Log failure
  105. await fs.appendFile('auth.log', `${new Date().toISOString()} login ${req.ip}\n`);
  106. return res.status(401).json({'message': 'Unauthorized.'});
  107. }
  108. // Create session
  109. await login(user, req);
  110. // Set session vars
  111. req.session.passport.displayname = user.displayname;
  112. req.session.passport.scope = user.scope;
  113. res.status(200).json({'message': 'Logged in.'});
  114. }));
  115. router.post('/logout', function (req, res) {
  116. if (!req.isAuthenticated())
  117. return res.status(400).json({message: 'Not logged in.'});
  118. req.logout();
  119. res.status(200).json({'message': 'Logged out.'});
  120. });
  121. router.get('/whoami', requireAuth(), (req, res) => {
  122. res.status(200).json({
  123. username: req.username,
  124. displayname: req.displayname,
  125. scope: req.scope,
  126. key: req.key
  127. });
  128. });
  129. module.exports = router;