From 19cfc7b375320c91f6422b1d5ab5bb2ab743c3e0 Mon Sep 17 00:00:00 2001 From: alphaeusmote <41258468-alphaeusmote@users.noreply.replit.com> Date: Fri, 11 Apr 2025 17:45:57 +0000 Subject: [PATCH] Improve API token expiration handling by adding date and time validation and ensuring consistent `expiresAt` data type. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/97ca9771-6ea6-4874-95e0-c9ca04f36ec8.jpg --- client/src/pages/api-tokens.tsx | 17 ++++++++++++- server/storage.ts | 44 +++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/client/src/pages/api-tokens.tsx b/client/src/pages/api-tokens.tsx index 714b60a..2756d8f 100644 --- a/client/src/pages/api-tokens.tsx +++ b/client/src/pages/api-tokens.tsx @@ -79,7 +79,9 @@ const tokenFormSchema = z.object({ role: z.string().min(1, "Role is required"), expiresIn: z.string().optional(), customDate: z.date().optional(), - customTime: z.string().optional(), + customTime: z.string().regex(/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/, { + message: "Time must be in 24-hour format HH:MM" + }).optional(), }).refine((data) => { // If custom expiration is selected, a date must be provided if (data.expiresIn === 'custom' && !data.customDate) { @@ -89,6 +91,19 @@ const tokenFormSchema = z.object({ }, { message: "Please select a date for the custom expiration", path: ["customDate"] +}).refine((data) => { + // If a custom date is provided, validate it's in the future + if (data.customDate) { + const now = new Date(); + const dateOnly = new Date(data.customDate); + dateOnly.setHours(0, 0, 0, 0); + now.setHours(0, 0, 0, 0); + return dateOnly >= now; + } + return true; +}, { + message: "Expiration date must be in the future", + path: ["customDate"] }); export default function ApiTokensPage() { diff --git a/server/storage.ts b/server/storage.ts index ccb2ea9..7a443ea 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -523,6 +523,20 @@ export class MemStorage implements IStorage { console.log("No expiresAt provided in token data"); } + // Make sure expiresAt is either a Date object or null + let finalExpiresAt: Date | null = null; + if (expiresAt) { + if (expiresAt instanceof Date) { + finalExpiresAt = expiresAt; + } else if (typeof expiresAt === 'string') { + try { + finalExpiresAt = new Date(expiresAt); + } catch (e) { + console.error("Could not convert expiresAt string to Date:", e); + } + } + } + const newToken: ApiToken = { id: numId.toString(), name: token.name || "Unnamed Token", @@ -532,7 +546,7 @@ export class MemStorage implements IStorage { role: token.role || 'readonly', createdBy: token.createdBy || "1", isActive: token.isActive ?? true, - expiresAt: expiresAt, + expiresAt: finalExpiresAt, createdAt }; @@ -546,7 +560,33 @@ export class MemStorage implements IStorage { const token = await this.getApiToken(id); if (!token) return undefined; - const updatedToken = { ...token, ...tokenData }; + // Handle expiresAt specially to ensure it's a Date object or null + let finalExpiresAt = token.expiresAt; + if ('expiresAt' in tokenData) { + const newExpiresAt = tokenData.expiresAt; + if (newExpiresAt === null) { + finalExpiresAt = null; + } else if (newExpiresAt instanceof Date) { + finalExpiresAt = newExpiresAt; + } else if (typeof newExpiresAt === 'string') { + try { + finalExpiresAt = new Date(newExpiresAt); + } catch (e) { + console.error("Could not convert expiresAt string to Date:", e); + } + } + + // Remove expiresAt to prevent type issues + const { expiresAt, ...otherData } = tokenData; + tokenData = otherData; + } + + const updatedToken = { + ...token, + ...tokenData, + expiresAt: finalExpiresAt + }; + this.apiTokensMap.set(numId, updatedToken); return updatedToken; }