You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

104 lines
3.5 KiB

11 months ago
  1. const { makeJWT } = require("../utils/http");
  2. const prisma = require("../utils/prisma");
  3. /**
  4. * Temporary auth tokens are used for simple SSO.
  5. * They simply enable the ability for a time-based token to be used in the query of the /sso/login URL
  6. * to login as a user without the need of a username and password. These tokens are single-use and expire.
  7. */
  8. const TemporaryAuthToken = {
  9. expiry: 1000 * 60 * 6, // 1 hour
  10. tablename: "temporary_auth_tokens",
  11. writable: [],
  12. makeTempToken: () => {
  13. const uuidAPIKey = require("uuid-apikey");
  14. return `allm-tat-${uuidAPIKey.create().apiKey}`;
  15. },
  16. /**
  17. * Issues a temporary auth token for a user via its ID.
  18. * @param {number} userId
  19. * @returns {Promise<{token: string|null, error: string | null}>}
  20. */
  21. issue: async function (userId = null) {
  22. if (!userId)
  23. throw new Error("User ID is required to issue a temporary auth token.");
  24. await this.invalidateUserTokens(userId);
  25. try {
  26. const token = this.makeTempToken();
  27. const expiresAt = new Date(Date.now() + this.expiry);
  28. await prisma.temporary_auth_tokens.create({
  29. data: {
  30. token,
  31. expiresAt,
  32. userId: Number(userId),
  33. },
  34. });
  35. return { token, error: null };
  36. } catch (error) {
  37. console.error("FAILED TO CREATE TEMPORARY AUTH TOKEN.", error.message);
  38. return { token: null, error: error.message };
  39. }
  40. },
  41. /**
  42. * Invalidates (deletes) all temporary auth tokens for a user via their ID.
  43. * @param {number} userId
  44. * @returns {Promise<boolean>}
  45. */
  46. invalidateUserTokens: async function (userId) {
  47. if (!userId)
  48. throw new Error(
  49. "User ID is required to invalidate temporary auth tokens."
  50. );
  51. await prisma.temporary_auth_tokens.deleteMany({
  52. where: { userId: Number(userId) },
  53. });
  54. return true;
  55. },
  56. /**
  57. * Validates a temporary auth token and returns the session token
  58. * to be set in the browser localStorage for authentication.
  59. * @param {string} publicToken - the token to validate against
  60. * @returns {Promise<{sessionToken: string|null, token: import("@prisma/client").temporary_auth_tokens & {user: import("@prisma/client").users} | null, error: string | null}>}
  61. */
  62. validate: async function (publicToken = "") {
  63. /** @type {import("@prisma/client").temporary_auth_tokens & {user: import("@prisma/client").users} | undefined | null} **/
  64. let token;
  65. try {
  66. if (!publicToken)
  67. throw new Error(
  68. "Public token is required to validate a temporary auth token."
  69. );
  70. token = await prisma.temporary_auth_tokens.findUnique({
  71. where: { token: String(publicToken) },
  72. include: { user: true },
  73. });
  74. if (!token) throw new Error("Invalid token.");
  75. if (token.expiresAt < new Date()) throw new Error("Token expired.");
  76. if (token.user.suspended) throw new Error("User account suspended.");
  77. // Create a new session token for the user valid for 30 days
  78. const sessionToken = makeJWT(
  79. { id: token.user.id, username: token.user.username },
  80. "30d"
  81. );
  82. return { sessionToken, token, error: null };
  83. } catch (error) {
  84. console.error("FAILED TO VALIDATE TEMPORARY AUTH TOKEN.", error.message);
  85. return { sessionToken: null, token: null, error: error.message };
  86. } finally {
  87. // Delete the token after it has been used under all circumstances if it was retrieved
  88. if (token)
  89. await prisma.temporary_auth_tokens.delete({ where: { id: token.id } });
  90. }
  91. },
  92. };
  93. module.exports = { TemporaryAuthToken };