98 lines
2.5 KiB
JavaScript
98 lines
2.5 KiB
JavaScript
const crypto = require('crypto');
|
|
const bcrypt = require('bcrypt');
|
|
const { ApiToken } = require('../../models');
|
|
|
|
const TOKEN_PREFIX_LENGTH = 12;
|
|
|
|
const serializeApiToken = (tokenInstance) => {
|
|
if (!tokenInstance) return null;
|
|
const tokenJson = tokenInstance.toJSON();
|
|
return {
|
|
id: tokenJson.id,
|
|
name: tokenJson.name,
|
|
token_prefix: tokenJson.token_prefix,
|
|
created_at: tokenJson.created_at,
|
|
updated_at: tokenJson.updated_at,
|
|
last_used_at: tokenJson.last_used_at,
|
|
expires_at: tokenJson.expires_at,
|
|
revoked_at: tokenJson.revoked_at,
|
|
};
|
|
};
|
|
|
|
const generateRawToken = () => `tt_${crypto.randomBytes(32).toString('hex')}`;
|
|
|
|
async function createApiToken({ userId, name, expiresAt, abilities = null }) {
|
|
const rawToken = generateRawToken();
|
|
const tokenHash = await bcrypt.hash(rawToken, 12);
|
|
const tokenPrefix = rawToken.slice(0, TOKEN_PREFIX_LENGTH);
|
|
|
|
const tokenRecord = await ApiToken.create({
|
|
user_id: userId,
|
|
name: name || 'Personal Access Token',
|
|
token_hash: tokenHash,
|
|
token_prefix: tokenPrefix,
|
|
abilities,
|
|
expires_at: expiresAt || null,
|
|
});
|
|
|
|
return { rawToken, tokenRecord };
|
|
}
|
|
|
|
async function findValidTokenByValue(tokenValue) {
|
|
if (!tokenValue) return null;
|
|
const prefix = tokenValue.slice(0, TOKEN_PREFIX_LENGTH);
|
|
const possibleTokens = await ApiToken.findAll({
|
|
where: { token_prefix: prefix },
|
|
order: [['created_at', 'DESC']],
|
|
});
|
|
|
|
for (const token of possibleTokens) {
|
|
if (token.revoked_at) continue;
|
|
if (token.expires_at && token.expires_at < new Date()) continue;
|
|
const match = await bcrypt.compare(tokenValue, token.token_hash);
|
|
if (match) {
|
|
return token;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
async function revokeApiToken(tokenId, userId) {
|
|
const token = await ApiToken.findOne({
|
|
where: { id: tokenId, user_id: userId },
|
|
});
|
|
|
|
if (!token) {
|
|
return null;
|
|
}
|
|
|
|
if (!token.revoked_at) {
|
|
token.revoked_at = new Date();
|
|
await token.save();
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
async function deleteApiToken(tokenId, userId) {
|
|
const token = await ApiToken.findOne({
|
|
where: { id: tokenId, user_id: userId },
|
|
});
|
|
|
|
if (!token) {
|
|
return null;
|
|
}
|
|
|
|
await token.destroy();
|
|
return true;
|
|
}
|
|
|
|
module.exports = {
|
|
createApiToken,
|
|
revokeApiToken,
|
|
deleteApiToken,
|
|
findValidTokenByValue,
|
|
serializeApiToken,
|
|
TOKEN_PREFIX_LENGTH,
|
|
};
|