Files

141 lines
3.0 KiB
JavaScript
Raw Permalink Normal View History

2020-02-19 15:55:06 +11:00
/**
NOTE: This is not a database table, this is a model of a Token object that can be created/loaded
and then has abilities after that.
*/
2025-09-02 21:43:00 +10:00
import crypto from "node:crypto";
import jwt from "jsonwebtoken";
import _ from "lodash";
import { getPrivateKey, getPublicKey } from "../lib/config.js";
import errs from "../lib/error.js";
import { global as logger } from "../logger.js";
2020-02-19 15:55:06 +11:00
2025-09-02 21:43:00 +10:00
const ALGO = "RS256";
2020-02-19 15:55:06 +11:00
2025-09-02 21:43:00 +10:00
export default () => {
2025-09-03 19:13:00 +10:00
let tokenData = {};
2020-02-19 15:55:06 +11:00
2023-03-21 16:53:39 +10:00
const self = {
2020-02-19 15:55:06 +11:00
/**
* @param {Object} payload
* @returns {Promise}
*/
create: (payload) => {
2025-09-02 21:43:00 +10:00
if (!getPrivateKey()) {
logger.error("Private key is empty!");
2023-03-21 16:53:39 +10:00
}
2020-02-19 15:55:06 +11:00
// sign with RSA SHA256
2023-03-21 16:53:39 +10:00
const options = {
2020-02-19 15:55:06 +11:00
algorithm: ALGO,
2025-09-02 21:43:00 +10:00
expiresIn: payload.expiresIn || "1d",
2020-02-19 15:55:06 +11:00
};
2025-09-02 21:43:00 +10:00
payload.jti = crypto.randomBytes(12).toString("base64").substring(-8);
2020-02-19 15:55:06 +11:00
return new Promise((resolve, reject) => {
2025-09-02 21:43:00 +10:00
jwt.sign(payload, getPrivateKey(), options, (err, token) => {
2020-02-19 15:55:06 +11:00
if (err) {
reject(err);
} else {
2025-09-03 19:13:00 +10:00
tokenData = payload;
2020-02-19 15:55:06 +11:00
resolve({
2025-09-02 21:43:00 +10:00
token: token,
payload: payload,
2020-02-19 15:55:06 +11:00
});
}
});
});
},
/**
* @param {String} token
* @returns {Promise}
*/
2025-09-02 21:43:00 +10:00
load: (token) => {
if (!getPublicKey()) {
logger.error("Public key is empty!");
2023-03-21 16:53:39 +10:00
}
2020-02-19 15:55:06 +11:00
return new Promise((resolve, reject) => {
try {
2025-09-02 21:43:00 +10:00
if (!token || token === null || token === "null") {
reject(new errs.AuthError("Empty token"));
2020-02-19 15:55:06 +11:00
} else {
2025-09-02 21:43:00 +10:00
jwt.verify(
token,
getPublicKey(),
{ ignoreExpiration: false, algorithms: [ALGO] },
(err, result) => {
if (err) {
if (err.name === "TokenExpiredError") {
reject(new errs.AuthError("Token has expired", err));
} else {
reject(err);
}
2020-02-19 15:55:06 +11:00
} else {
2025-09-03 19:13:00 +10:00
tokenData = result;
2025-09-02 21:43:00 +10:00
// Hack: some tokens out in the wild have a scope of 'all' instead of 'user'.
// For 30 days at least, we need to replace 'all' with user.
if (
2025-09-03 19:13:00 +10:00
typeof tokenData.scope !== "undefined" &&
_.indexOf(tokenData.scope, "all") !== -1
2025-09-02 21:43:00 +10:00
) {
2025-09-03 19:13:00 +10:00
tokenData.scope = ["user"];
2025-09-02 21:43:00 +10:00
}
2025-09-03 19:13:00 +10:00
resolve(tokenData);
2020-02-19 15:55:06 +11:00
}
2025-09-02 21:43:00 +10:00
},
);
2020-02-19 15:55:06 +11:00
}
} catch (err) {
reject(err);
}
});
},
/**
* Does the token have the specified scope?
*
* @param {String} scope
* @returns {Boolean}
*/
2025-09-03 19:13:00 +10:00
hasScope: (scope) => typeof tokenData.scope !== "undefined" && _.indexOf(tokenData.scope, scope) !== -1,
2020-02-19 15:55:06 +11:00
/**
* @param {String} key
* @return {*}
*/
2025-09-02 21:43:00 +10:00
get: (key) => {
2025-09-03 19:13:00 +10:00
if (typeof tokenData[key] !== "undefined") {
return tokenData[key];
2020-02-19 15:55:06 +11:00
}
return null;
},
/**
* @param {String} key
* @param {*} value
*/
2025-09-02 21:43:00 +10:00
set: (key, value) => {
2025-09-03 19:13:00 +10:00
tokenData[key] = value;
2020-02-19 15:55:06 +11:00
},
/**
* @param [defaultValue]
2020-02-19 15:55:06 +11:00
* @returns {Integer}
*/
getUserId: (defaultValue) => {
2025-09-02 21:43:00 +10:00
const attrs = self.get("attrs");
2025-09-10 21:38:02 +10:00
if (attrs?.id) {
2020-02-19 15:55:06 +11:00
return attrs.id;
}
return defaultValue || 0;
2025-09-02 21:43:00 +10:00
},
2020-02-19 15:55:06 +11:00
};
return self;
};