No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 

149 líneas
4.8 KiB

  1. const config = require('../setup/config');
  2. const crypto = require('crypto');
  3. const debug = require('debug')('server-connect:auth');
  4. class AuthProvider {
  5. constructor(app, opts, name) {
  6. this.app = app;
  7. this.name = name;
  8. this.identity = this.app.getSession(this.name + 'Id') || false;
  9. this.secret = opts.secret || config.secret;
  10. this.basicAuth = opts.basicAuth;
  11. this.basicRealm = opts.basicRealm;
  12. this.passwordVerify = opts.passwordVerify || false;
  13. this.cookieOpts = {
  14. domain: opts.domain || undefined,
  15. httpOnly: true,
  16. maxAge: (opts.expires || 30) * 24 * 60 * 60 * 1000, // from days to ms
  17. path: opts.path || '/',
  18. secure: !!opts.secure,
  19. sameSite: opts.sameSite || 'Strict',
  20. signed: true
  21. };
  22. }
  23. async autoLogin() {
  24. if (this.basicAuth) {
  25. const auth = require('../core/basicauth')(this.app.req);
  26. debug(`Basic auth credentials received: %o`, auth);
  27. if (auth) await this.login(auth.username, auth.password, false, true);
  28. }
  29. const cookie = this.app.getCookie(this.name + '.auth', true);
  30. if (cookie) {
  31. const auth = this.decrypt(cookie);
  32. debug(`Login with cookie: %o`, auth);
  33. if (auth) await this.login(auth.username, auth.password, true, true);
  34. } else {
  35. debug(`No login cookie found`);
  36. }
  37. }
  38. async login(username, password, remember, autoLogin) {
  39. const identity = await this.validate(username, password, this.passwordVerify);
  40. if (!identity) {
  41. await this.logout();
  42. if (!autoLogin) {
  43. this.unauthorized();
  44. return false;
  45. }
  46. } else {
  47. this.app.setSession(this.name + 'Id', identity);
  48. this.app.set('identity', identity);
  49. if (remember) {
  50. debug('setCookie', identity, username, password);
  51. this.app.setCookie(this.name + '.auth', this.encrypt({ username, password }), this.cookieOpts);
  52. }
  53. this.identity = identity;
  54. }
  55. return identity;
  56. }
  57. async logout() {
  58. this.app.removeSession(this.name + 'Id');
  59. this.app.removeCookie(this.name + '.auth', this.cookieOpts);
  60. this.app.remove('identity');
  61. this.identity = false;
  62. }
  63. async restrict(opts) {
  64. if (this.identity === false) {
  65. if (opts.loginUrl) {
  66. if (this.app.req.fragment) {
  67. this.app.res.status(222).send(opts.loginUrl);
  68. } else {
  69. this.app.res.redirect(opts.loginUrl);
  70. }
  71. } else {
  72. this.unauthorized();
  73. }
  74. return;
  75. }
  76. if (opts.permissions) {
  77. const allowed = await this.permissions(this.identity, opts.permissions);
  78. if (!allowed) {
  79. if (opts.forbiddenUrl) {
  80. if (this.app.req.fragment) {
  81. this.app.res.status(222).send(opts.forbiddenUrl);
  82. } else {
  83. this.app.res.redirect(opts.forbiddenUrl);
  84. }
  85. } else {
  86. this.forbidden();
  87. }
  88. }
  89. }
  90. }
  91. encrypt(data) {
  92. const iv = crypto.randomBytes(16);
  93. const key = crypto.scryptSync(this.secret, iv, 32);
  94. const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
  95. const encrypted = cipher.update(JSON.stringify(data), 'utf8', 'base64');
  96. return iv.toString('base64') + '.' + encrypted + cipher.final('base64');
  97. }
  98. decrypt(data) {
  99. // try/catch to prevent errors on currupt cookies
  100. try {
  101. const iv = Buffer.from(data.split('.')[0], 'base64');
  102. const key = crypto.scryptSync(this.secret, iv, 32);
  103. const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
  104. const decrypted = decipher.update(data.split('.')[1], 'base64', 'utf8');
  105. return JSON.parse(decrypted + decipher.final('utf8'));
  106. } catch (err) {
  107. return undefined;
  108. }
  109. }
  110. unauthorized() {
  111. if (this.basicAuth) {
  112. this.app.res.set('WWW-Authenticate', `Basic Realm="${this.basicRealm}"`);
  113. }
  114. this.app.res.sendStatus(401);
  115. }
  116. forbidden() {
  117. this.app.res.sendStatus(403);
  118. }
  119. async validate(username, password) {
  120. throw new Error('auth.validate needs to be extended.');
  121. }
  122. async permissions(identity, permissions) {
  123. throw new Error('auth.permissions needs to be extended.');
  124. }
  125. }
  126. module.exports = AuthProvider;