tududi/backend/modules/users/apiTokenService.js
Chris 542be2c1e9
Fix bug 366 (#764)
* Optimize DB

* Clean up names

* fixup! Clean up names

* fixup! fixup! Clean up names
2026-01-07 18:18:07 +02:00

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,
};