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