token.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. /**
  2. NOTE: This is not a database table, this is a model of a Token object that can be created/loaded
  3. and then has abilities after that.
  4. */
  5. import crypto from "node:crypto";
  6. import jwt from "jsonwebtoken";
  7. import _ from "lodash";
  8. import { getPrivateKey, getPublicKey } from "../lib/config.js";
  9. import errs from "../lib/error.js";
  10. import { global as logger } from "../logger.js";
  11. const ALGO = "RS256";
  12. export default () => {
  13. let tokenData = {};
  14. const self = {
  15. /**
  16. * @param {Object} payload
  17. * @returns {Promise}
  18. */
  19. create: (payload) => {
  20. if (!getPrivateKey()) {
  21. logger.error("Private key is empty!");
  22. }
  23. // sign with RSA SHA256
  24. const options = {
  25. algorithm: ALGO,
  26. expiresIn: payload.expiresIn || "1d",
  27. };
  28. payload.jti = crypto.randomBytes(12).toString("base64").substring(-8);
  29. return new Promise((resolve, reject) => {
  30. jwt.sign(payload, getPrivateKey(), options, (err, token) => {
  31. if (err) {
  32. reject(err);
  33. } else {
  34. tokenData = payload;
  35. resolve({
  36. token: token,
  37. payload: payload,
  38. });
  39. }
  40. });
  41. });
  42. },
  43. /**
  44. * @param {String} token
  45. * @returns {Promise}
  46. */
  47. load: (token) => {
  48. if (!getPublicKey()) {
  49. logger.error("Public key is empty!");
  50. }
  51. return new Promise((resolve, reject) => {
  52. try {
  53. if (!token || token === null || token === "null") {
  54. reject(new errs.AuthError("Empty token"));
  55. } else {
  56. jwt.verify(
  57. token,
  58. getPublicKey(),
  59. { ignoreExpiration: false, algorithms: [ALGO] },
  60. (err, result) => {
  61. if (err) {
  62. if (err.name === "TokenExpiredError") {
  63. reject(new errs.AuthError("Token has expired", err));
  64. } else {
  65. reject(err);
  66. }
  67. } else {
  68. tokenData = result;
  69. // Hack: some tokens out in the wild have a scope of 'all' instead of 'user'.
  70. // For 30 days at least, we need to replace 'all' with user.
  71. if (
  72. typeof tokenData.scope !== "undefined" &&
  73. _.indexOf(tokenData.scope, "all") !== -1
  74. ) {
  75. tokenData.scope = ["user"];
  76. }
  77. resolve(tokenData);
  78. }
  79. },
  80. );
  81. }
  82. } catch (err) {
  83. reject(err);
  84. }
  85. });
  86. },
  87. /**
  88. * Does the token have the specified scope?
  89. *
  90. * @param {String} scope
  91. * @returns {Boolean}
  92. */
  93. hasScope: (scope) => typeof tokenData.scope !== "undefined" && _.indexOf(tokenData.scope, scope) !== -1,
  94. /**
  95. * @param {String} key
  96. * @return {*}
  97. */
  98. get: (key) => {
  99. if (typeof tokenData[key] !== "undefined") {
  100. return tokenData[key];
  101. }
  102. return null;
  103. },
  104. /**
  105. * @param {String} key
  106. * @param {*} value
  107. */
  108. set: (key, value) => {
  109. tokenData[key] = value;
  110. },
  111. /**
  112. * @param [defaultValue]
  113. * @returns {Integer}
  114. */
  115. getUserId: (defaultValue) => {
  116. const attrs = self.get("attrs");
  117. if (attrs?.id) {
  118. return attrs.id;
  119. }
  120. return defaultValue || 0;
  121. },
  122. };
  123. return self;
  124. };