@@ -2,7 +2,7 @@ import { createHash, timingSafeEqual } from "node:crypto";
22import jwt from "jsonwebtoken" ;
33import { nanoid } from "nanoid" ;
44import { config } from "./config.js" ;
5- import { db } from "./db.js" ;
5+ import { queryOne , queryAll , execute } from "./db.js" ;
66import type { Request , Response , NextFunction } from "express" ;
77
88export type UserRow = {
@@ -36,22 +36,23 @@ function verifyPassword(password: string, hash: string): boolean {
3636 }
3737}
3838
39- export function registerUser ( username : string , password : string , displayName ?: string ) : UserRow | null {
39+ export async function registerUser ( username : string , password : string , displayName ?: string ) : Promise < UserRow | null > {
4040 const lowerUsername = username . toLowerCase ( ) ;
41- const existing = db . prepare ( "SELECT id FROM users WHERE username = ?" ) . get ( lowerUsername ) ;
41+ const existing = await queryOne ( "SELECT id FROM users WHERE username = $1" , [ lowerUsername ] ) ;
4242 if ( existing ) return null ;
4343
4444 const id = nanoid ( ) ;
4545 const hash = hashPassword ( password ) ;
46- db . prepare (
47- "INSERT INTO users (id, username, password_hash, display_name, role, plan) VALUES (?, ?, ?, ?, ?, ?)"
48- ) . run ( id , lowerUsername , hash , displayName || username , "user" , "free" ) ;
46+ await execute (
47+ "INSERT INTO users (id, username, password_hash, display_name, role, plan) VALUES ($1, $2, $3, $4, $5, $6)" ,
48+ [ id , lowerUsername , hash , displayName || username , "user" , "free" ]
49+ ) ;
4950
50- return db . prepare ( "SELECT * FROM users WHERE id = ?" ) . get ( id ) as UserRow ;
51+ return await queryOne < UserRow > ( "SELECT * FROM users WHERE id = $1" , [ id ] ) ?? null ;
5152}
5253
53- export function loginUser ( username : string , password : string ) : { token : string ; user : Omit < UserRow , "password_hash" > } | null {
54- const user = db . prepare ( "SELECT * FROM users WHERE username = ?" ) . get ( username . toLowerCase ( ) ) as UserRow | undefined ;
54+ export async function loginUser ( username : string , password : string ) : Promise < { token : string ; user : Omit < UserRow , "password_hash" > } | null > {
55+ const user = await queryOne < UserRow > ( "SELECT * FROM users WHERE username = $1" , [ username . toLowerCase ( ) ] ) ;
5556 if ( ! user ) return null ;
5657 if ( ! verifyPassword ( password , user . password_hash ) ) return null ;
5758
@@ -67,23 +68,33 @@ export function loginUser(username: string, password: string): { token: string;
6768
6869export function verifyToken ( token : string ) {
6970 try {
70- if ( isTokenRevoked ( token ) ) return null ;
71+ if ( isTokenRevokedSync ( token ) ) return null ;
7172 return jwt . verify ( token , config . jwtSecret ) as { userId : string ; username : string ; role : string ; plan : string } ;
7273 } catch {
7374 return null ;
7475 }
7576}
7677
77- export function revokeToken ( token : string ) {
78+ // Token blacklist cache for synchronous checks in middleware
79+ const revokedTokens = new Set < string > ( ) ;
80+
81+ export async function revokeToken ( token : string ) {
7882 const hash = createHash ( "sha256" ) . update ( token ) . digest ( "hex" ) ;
7983 const decoded = jwt . decode ( token ) as { exp ?: number } | null ;
8084 const expiresAt = decoded ?. exp ? new Date ( decoded . exp * 1000 ) . toISOString ( ) : new Date ( Date . now ( ) + 7 * 86400000 ) . toISOString ( ) ;
81- db . prepare ( "INSERT OR IGNORE INTO token_blacklist (token_hash, expires_at) VALUES (?, ?)" ) . run ( hash , expiresAt ) ;
85+ await execute ( "INSERT INTO token_blacklist (token_hash, expires_at) VALUES ($1, $2) ON CONFLICT DO NOTHING" , [ hash , expiresAt ] ) ;
86+ revokedTokens . add ( hash ) ;
8287}
8388
84- function isTokenRevoked ( token : string ) : boolean {
89+ function isTokenRevokedSync ( token : string ) : boolean {
8590 const hash = createHash ( "sha256" ) . update ( token ) . digest ( "hex" ) ;
86- return ! ! db . prepare ( "SELECT 1 FROM token_blacklist WHERE token_hash = ?" ) . get ( hash ) ;
91+ return revokedTokens . has ( hash ) ;
92+ }
93+
94+ // Load revoked tokens into cache on startup
95+ export async function loadRevokedTokens ( ) {
96+ const rows = await queryAll < { token_hash : string } > ( "SELECT token_hash FROM token_blacklist WHERE expires_at > NOW()" ) ;
97+ for ( const row of rows ) revokedTokens . add ( row . token_hash ) ;
8798}
8899
89100// Optional auth middleware - attaches user if token present, doesn't block
@@ -125,20 +136,20 @@ export function requireAdmin(req: Request, res: Response, next: NextFunction) {
125136}
126137
127138// Check AI rate limits
128- export function checkAILimit ( userId ?: string ) : { allowed : boolean ; remaining : number } {
139+ export async function checkAILimit ( userId ?: string ) : Promise < { allowed : boolean ; remaining : number } > {
129140 if ( ! userId ) return { allowed : true , remaining : 50 } ; // anonymous gets 50/day
130141
131- const user = db . prepare ( "SELECT * FROM users WHERE id = ?" ) . get ( userId ) as UserRow | undefined ;
142+ const user = await queryOne < UserRow > ( "SELECT * FROM users WHERE id = $1" , [ userId ] ) ;
132143 if ( ! user ) return { allowed : true , remaining : 50 } ;
133144
134145 // Admin bypasses limits
135146 if ( user . role === "admin" ) return { allowed : true , remaining : 9999 } ;
136147
137148 // Reset counter if new day
138149 const today = new Date ( ) . toISOString ( ) . split ( "T" ) [ 0 ] ;
139- const resetDay = user . ai_requests_reset . split ( "T" ) [ 0 ] ;
150+ const resetDay = new Date ( user . ai_requests_reset ) . toISOString ( ) . split ( "T" ) [ 0 ] ;
140151 if ( today !== resetDay ) {
141- db . prepare ( "UPDATE users SET ai_requests_today = 0, ai_requests_reset = CURRENT_TIMESTAMP WHERE id = ?" ) . run ( userId ) ;
152+ await execute ( "UPDATE users SET ai_requests_today = 0, ai_requests_reset = CURRENT_TIMESTAMP WHERE id = $1" , [ userId ] ) ;
142153 return { allowed : true , remaining : user . plan === "pro" ? 500 : 50 } ;
143154 }
144155
@@ -147,15 +158,15 @@ export function checkAILimit(userId?: string): { allowed: boolean; remaining: nu
147158 return { allowed : remaining > 0 , remaining } ;
148159}
149160
150- export function incrementAIUsage ( userId : string ) {
151- db . prepare ( "UPDATE users SET ai_requests_today = ai_requests_today + 1 WHERE id = ?" ) . run ( userId ) ;
161+ export async function incrementAIUsage ( userId : string ) {
162+ await execute ( "UPDATE users SET ai_requests_today = ai_requests_today + 1 WHERE id = $1" , [ userId ] ) ;
152163}
153164
154- export function getAnalytics ( ) {
155- const users = ( db . prepare ( "SELECT COUNT(*) as count FROM users" ) . get ( ) as any ) . count ;
156- const projects = ( db . prepare ( "SELECT COUNT(*) as count FROM projects" ) . get ( ) as any ) . count ;
157- const agentRuns = ( db . prepare ( "SELECT COUNT(*) as count FROM agent_runs" ) . get ( ) as any ) . count ;
158- const donations = ( db . prepare ( "SELECT COALESCE(SUM(amount), 0) as total FROM donations" ) . get ( ) as any ) . total ;
159- const proUsers = ( db . prepare ( "SELECT COUNT(*) as count FROM users WHERE plan = 'pro'" ) . get ( ) as any ) . count ;
165+ export async function getAnalytics ( ) {
166+ const users = ( await queryOne < { count : number } > ( "SELECT COUNT(*) as count FROM users" ) ) ? .count || 0 ;
167+ const projects = ( await queryOne < { count : number } > ( "SELECT COUNT(*) as count FROM projects" ) ) ? .count || 0 ;
168+ const agentRuns = ( await queryOne < { count : number } > ( "SELECT COUNT(*) as count FROM agent_runs" ) ) ? .count || 0 ;
169+ const donations = ( await queryOne < { total : number } > ( "SELECT COALESCE(SUM(amount), 0) as total FROM donations" ) ) ? .total || 0 ;
170+ const proUsers = ( await queryOne < { count : number } > ( "SELECT COUNT(*) as count FROM users WHERE plan = 'pro'" ) ) ? .count || 0 ;
160171 return { users, projects, agentRuns, totalDonations : donations , proUsers } ;
161172}
0 commit comments