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.
 
 
 

93 lines
2.7 KiB

import jwt, { Secret, SignOptions } from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import crypto from 'crypto';
import { env } from '../config/env.js';
import { db } from '../config/database.js';
export async function createTokens(userId: number, username: string) {
// Access token (short-lived)
const accessToken = jwt.sign(
{ userId, username, type: 'access' },
env.jwtSecret as Secret,
{ expiresIn: env.accessTokenExpiry } as SignOptions
);
// Refresh token (long-lived)
const refreshTokenValue = crypto.randomBytes(64).toString('hex');
const refreshToken = jwt.sign(
{ token: refreshTokenValue, userId, type: 'refresh' },
env.jwtRefreshSecret as Secret,
{ expiresIn: env.refreshTokenExpiry } as SignOptions
);
// Store refresh token in database
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7); // 7 days
await db.execute({
sql: `INSERT INTO refresh_tokens (user_id, token, expires_at)
VALUES (?, ?, ?)`,
args: [userId, refreshTokenValue, expiresAt.toISOString()]
});
return { accessToken, refreshToken };
}
export async function refreshAccessToken(refreshToken: string) {
try {
const decoded = jwt.verify(refreshToken, env.jwtRefreshSecret) as {
token: string;
userId: number;
};
// Check if refresh token exists and is valid
const result = await db.execute({
sql: `SELECT rt.*, u.username
FROM refresh_tokens rt
JOIN users u ON rt.user_id = u.id
WHERE rt.token = ? AND rt.expires_at > ?`,
args: [decoded.token, new Date().toISOString()]
});
if (!result.rows.length) {
throw new Error('Invalid refresh token');
}
const tokenData = result.rows[0];
// Generate new access token
const accessToken = jwt.sign(
{ userId: tokenData.user_id, username: tokenData.username, type: 'access' },
env.jwtSecret as Secret,
{ expiresIn: env.accessTokenExpiry } as SignOptions
);
return { accessToken };
} catch (error) {
throw new Error('Invalid or expired refresh token');
}
}
export async function revokeRefreshToken(token: string) {
await db.execute({
sql: 'DELETE FROM refresh_tokens WHERE token = ?',
args: [token]
});
}
export async function revokeAllUserTokens(userId: number) {
await db.execute({
sql: 'DELETE FROM refresh_tokens WHERE user_id = ?',
args: [userId]
});
}
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
export async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 10);
}