2018-02-18 02:00:56 +00:00
|
|
|
const fs = require("fs");
|
|
|
|
const path = require("path");
|
|
|
|
const crypto = require("crypto");
|
|
|
|
const jwt = require("jsonwebtoken");
|
|
|
|
const invariant = require("invariant");
|
|
|
|
const forge = require("node-forge");
|
2018-02-18 04:21:19 +00:00
|
|
|
|
|
|
|
const db = require("./utils/redis");
|
2017-11-11 20:18:13 +00:00
|
|
|
|
2018-02-18 02:00:56 +00:00
|
|
|
let keys;
|
2017-11-25 21:25:01 +00:00
|
|
|
if (process.env.NODE_ENV === "production") {
|
2017-11-11 20:18:13 +00:00
|
|
|
keys = {
|
2017-11-25 21:25:01 +00:00
|
|
|
public: fs.readFileSync(path.resolve(__dirname, "../public.key"), "utf8"),
|
2017-11-11 20:18:13 +00:00
|
|
|
private: process.env.PRIVATE_KEY
|
2018-02-18 02:00:56 +00:00
|
|
|
};
|
2017-11-11 20:18:13 +00:00
|
|
|
|
2018-02-18 02:00:56 +00:00
|
|
|
invariant(keys.private, "Missing $PRIVATE_KEY environment variable");
|
2017-11-11 20:18:13 +00:00
|
|
|
} else {
|
|
|
|
// Generate a random keypair for dev/testing.
|
|
|
|
// See https://gist.github.com/sebadoom/2b70969e70db5da9a203bebd9cff099f
|
2018-02-18 02:00:56 +00:00
|
|
|
const keypair = forge.rsa.generateKeyPair({ bits: 2048 });
|
2017-11-11 20:18:13 +00:00
|
|
|
keys = {
|
|
|
|
public: forge.pki.publicKeyToPem(keypair.publicKey, 72),
|
|
|
|
private: forge.pki.privateKeyToPem(keypair.privateKey, 72)
|
2018-02-18 02:00:56 +00:00
|
|
|
};
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getCurrentSeconds() {
|
2018-02-18 02:00:56 +00:00
|
|
|
return Math.floor(Date.now() / 1000);
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function createTokenId() {
|
2018-02-18 02:00:56 +00:00
|
|
|
return crypto.randomBytes(16).toString("hex");
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function createToken(scopes = {}) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const payload = {
|
|
|
|
jti: createTokenId(),
|
2017-11-25 21:25:01 +00:00
|
|
|
iss: "https://unpkg.com",
|
2017-11-11 20:18:13 +00:00
|
|
|
iat: getCurrentSeconds(),
|
|
|
|
scopes
|
2018-02-18 02:00:56 +00:00
|
|
|
};
|
2017-11-11 20:18:13 +00:00
|
|
|
|
2017-11-25 21:25:01 +00:00
|
|
|
jwt.sign(payload, keys.private, { algorithm: "RS256" }, (error, token) => {
|
2017-11-11 20:18:13 +00:00
|
|
|
if (error) {
|
2018-02-18 02:00:56 +00:00
|
|
|
reject(error);
|
2017-11-11 20:18:13 +00:00
|
|
|
} else {
|
2018-02-18 02:00:56 +00:00
|
|
|
resolve(token);
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
2018-02-18 02:00:56 +00:00
|
|
|
});
|
|
|
|
});
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
|
|
|
|
2018-04-04 05:32:32 +00:00
|
|
|
const revokedTokensSet = "revoked-tokens";
|
2017-11-11 20:18:13 +00:00
|
|
|
|
|
|
|
function verifyToken(token) {
|
|
|
|
return new Promise((resolve, reject) => {
|
2018-02-18 02:00:56 +00:00
|
|
|
const options = { algorithms: ["RS256"] };
|
2017-11-11 20:18:13 +00:00
|
|
|
|
|
|
|
jwt.verify(token, keys.public, options, (error, payload) => {
|
|
|
|
if (error) {
|
2018-02-18 02:00:56 +00:00
|
|
|
reject(error);
|
2017-11-11 20:18:13 +00:00
|
|
|
} else {
|
|
|
|
if (payload.jti) {
|
2018-04-04 05:32:32 +00:00
|
|
|
db.sismember(revokedTokensSet, payload.jti, (error, value) => {
|
2017-11-11 20:18:13 +00:00
|
|
|
if (error) {
|
2018-02-18 02:00:56 +00:00
|
|
|
reject(error);
|
2017-11-11 20:18:13 +00:00
|
|
|
} else {
|
2018-02-18 02:00:56 +00:00
|
|
|
resolve(value === 0 ? payload : null);
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
2018-02-18 02:00:56 +00:00
|
|
|
});
|
2017-11-11 20:18:13 +00:00
|
|
|
} else {
|
2018-02-18 02:00:56 +00:00
|
|
|
resolve(null);
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
|
|
|
}
|
2018-02-18 02:00:56 +00:00
|
|
|
});
|
|
|
|
});
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function revokeToken(token) {
|
|
|
|
return verifyToken(token).then(payload => {
|
|
|
|
if (payload) {
|
|
|
|
return new Promise((resolve, reject) => {
|
2018-04-04 05:32:32 +00:00
|
|
|
db.sadd(revokedTokensSet, payload.jti, error => {
|
2017-11-11 20:18:13 +00:00
|
|
|
if (error) {
|
2018-02-18 02:00:56 +00:00
|
|
|
reject(error);
|
2017-11-11 20:18:13 +00:00
|
|
|
} else {
|
2018-02-18 02:00:56 +00:00
|
|
|
resolve();
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
2018-02-18 02:00:56 +00:00
|
|
|
});
|
|
|
|
});
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
2018-02-18 02:00:56 +00:00
|
|
|
});
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function removeAllRevokedTokens() {
|
|
|
|
return new Promise((resolve, reject) => {
|
2018-04-04 05:32:32 +00:00
|
|
|
db.del(revokedTokensSet, error => {
|
2017-11-11 20:18:13 +00:00
|
|
|
if (error) {
|
2018-02-18 02:00:56 +00:00
|
|
|
reject(error);
|
2017-11-11 20:18:13 +00:00
|
|
|
} else {
|
2018-02-18 02:00:56 +00:00
|
|
|
resolve();
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
2018-02-18 02:00:56 +00:00
|
|
|
});
|
|
|
|
});
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getPublicKey() {
|
2018-02-18 02:00:56 +00:00
|
|
|
return keys.public;
|
2017-11-11 20:18:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
createToken,
|
|
|
|
verifyToken,
|
|
|
|
revokeToken,
|
|
|
|
removeAllRevokedTokens,
|
|
|
|
getPublicKey
|
2018-02-18 02:00:56 +00:00
|
|
|
};
|