fix: replace unguarded crypto.randomUUID() with safe generateUUID() utility
This commit is contained in:
@@ -18,7 +18,7 @@ import { useContactStore, getContactDisplayName } from "@/stores/contact-store";
|
|||||||
import { useAuthStore, redirectToLogin } from "@/stores/auth-store";
|
import { useAuthStore, redirectToLogin } from "@/stores/auth-store";
|
||||||
import { useEmailStore } from "@/stores/email-store";
|
import { useEmailStore } from "@/stores/email-store";
|
||||||
import { toast } from "@/stores/toast-store";
|
import { toast } from "@/stores/toast-store";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn, generateUUID } from "@/lib/utils";
|
||||||
import { NavigationRail } from "@/components/layout/navigation-rail";
|
import { NavigationRail } from "@/components/layout/navigation-rail";
|
||||||
import { SidebarAppsModal } from "@/components/layout/sidebar-apps-modal";
|
import { SidebarAppsModal } from "@/components/layout/sidebar-apps-modal";
|
||||||
import { InlineAppView } from "@/components/layout/inline-app-view";
|
import { InlineAppView } from "@/components/layout/inline-app-view";
|
||||||
@@ -274,7 +274,7 @@ export default function ContactsPage() {
|
|||||||
toast.success(t("toast.created"));
|
toast.success(t("toast.created"));
|
||||||
} else {
|
} else {
|
||||||
const localContact: ContactCard = {
|
const localContact: ContactCard = {
|
||||||
id: `local-${crypto.randomUUID()}`,
|
id: `local-${generateUUID()}`,
|
||||||
addressBookIds: {},
|
addressBookIds: {},
|
||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
} from "@/lib/calendar-participants";
|
} from "@/lib/calendar-participants";
|
||||||
import { PluginSlot } from "@/components/plugins/plugin-slot";
|
import { PluginSlot } from "@/components/plugins/plugin-slot";
|
||||||
import { useSettingsStore } from "@/stores/settings-store";
|
import { useSettingsStore } from "@/stores/settings-store";
|
||||||
|
import { generateUUID } from "@/lib/utils";
|
||||||
|
|
||||||
export interface PendingEventPreview {
|
export interface PendingEventPreview {
|
||||||
start: Date;
|
start: Date;
|
||||||
@@ -402,9 +403,7 @@ export function EventModal({
|
|||||||
if (!event || !onDuplicate) return;
|
if (!event || !onDuplicate) return;
|
||||||
const start = getEventStartDate(event);
|
const start = getEventStartDate(event);
|
||||||
const newStart = addDays(start, 1);
|
const newStart = addDays(start, 1);
|
||||||
const newUid = typeof crypto !== 'undefined' && crypto.randomUUID
|
const newUid = generateUUID();
|
||||||
? crypto.randomUUID()
|
|
||||||
: `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
||||||
const data: Partial<CalendarEvent> = {
|
const data: Partial<CalendarEvent> = {
|
||||||
uid: newUid,
|
uid: newUid,
|
||||||
title: event.title,
|
title: event.title,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { EMAIL_SANITIZE_CONFIG, collapseBlockedImageContainers } from "@/lib/ema
|
|||||||
import { hasMeaningfulHtmlBody } from "@/lib/signature-utils";
|
import { hasMeaningfulHtmlBody } from "@/lib/signature-utils";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Avatar } from "@/components/ui/avatar";
|
import { Avatar } from "@/components/ui/avatar";
|
||||||
import { formatFileSize, cn, buildMailboxTree, MailboxNode, formatDateTime } from "@/lib/utils";
|
import { formatFileSize, cn, buildMailboxTree, MailboxNode, formatDateTime, generateUUID } from "@/lib/utils";
|
||||||
import { getSecurityStatus, extractListHeaders } from "@/lib/email-headers";
|
import { getSecurityStatus, extractListHeaders } from "@/lib/email-headers";
|
||||||
import {
|
import {
|
||||||
Reply,
|
Reply,
|
||||||
@@ -4874,7 +4874,7 @@ export function EmailViewer({
|
|||||||
if (client && supportsSync) {
|
if (client && supportsSync) {
|
||||||
createContact(client, contactData).then(() => toast.success('Contact added'));
|
createContact(client, contactData).then(() => toast.success('Contact added'));
|
||||||
} else {
|
} else {
|
||||||
addLocalContact({ id: `local-${crypto.randomUUID()}`, addressBookIds: {}, ...contactData } as ContactCard);
|
addLocalContact({ id: `local-${generateUUID()}`, addressBookIds: {}, ...contactData } as ContactCard);
|
||||||
toast.success('Contact added');
|
toast.success('Contact added');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import type {
|
|||||||
FilterActionType,
|
FilterActionType,
|
||||||
} from "@/lib/jmap/sieve-types";
|
} from "@/lib/jmap/sieve-types";
|
||||||
import type { Mailbox } from "@/lib/jmap/types";
|
import type { Mailbox } from "@/lib/jmap/types";
|
||||||
import { buildMailboxTree, flattenMailboxTree, type MailboxNode } from "@/lib/utils";
|
import { buildMailboxTree, flattenMailboxTree, type MailboxNode, generateUUID } from "@/lib/utils";
|
||||||
|
|
||||||
interface FilterRuleModalProps {
|
interface FilterRuleModalProps {
|
||||||
rule?: FilterRule;
|
rule?: FilterRule;
|
||||||
@@ -109,7 +109,7 @@ export function FilterRuleModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSave({
|
onSave({
|
||||||
id: rule?.id || crypto.randomUUID(),
|
id: rule?.id || generateUUID(),
|
||||||
name: trimmedName,
|
name: trimmedName,
|
||||||
enabled: rule?.enabled ?? true,
|
enabled: rule?.enabled ?? true,
|
||||||
matchType,
|
matchType,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { CalendarEvent, CalendarParticipant } from '@/lib/jmap/types';
|
import type { CalendarEvent, CalendarParticipant } from '@/lib/jmap/types';
|
||||||
|
import { generateUUID } from '@/lib/utils';
|
||||||
|
|
||||||
export interface ParticipantInfo {
|
export interface ParticipantInfo {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -104,9 +105,7 @@ export function buildParticipantMap(
|
|||||||
): Record<string, Partial<CalendarParticipant>> {
|
): Record<string, Partial<CalendarParticipant>> {
|
||||||
const participants: Record<string, Partial<CalendarParticipant>> = {};
|
const participants: Record<string, Partial<CalendarParticipant>> = {};
|
||||||
|
|
||||||
const generateId = () => typeof crypto !== 'undefined' && crypto.randomUUID
|
const generateId = () => generateUUID();
|
||||||
? crypto.randomUUID()
|
|
||||||
: `p-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
||||||
|
|
||||||
participants[generateId()] = {
|
participants[generateId()] = {
|
||||||
'@type': 'Participant',
|
'@type': 'Participant',
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
* All line endings are CRLF per RFC 5322.
|
* All line endings are CRLF per RFC 5322.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { generateUUID } from '@/lib/utils';
|
||||||
|
|
||||||
const CRLF = '\r\n';
|
const CRLF = '\r\n';
|
||||||
|
|
||||||
export interface MimeAttachment {
|
export interface MimeAttachment {
|
||||||
@@ -43,7 +45,7 @@ export function buildMimeMessage(input: MimeMessageInput): Uint8Array {
|
|||||||
// BCC is intentionally omitted from the MIME headers per RFC 5322
|
// BCC is intentionally omitted from the MIME headers per RFC 5322
|
||||||
lines.push(formatHeader('Subject', encodeHeaderValue(input.subject)));
|
lines.push(formatHeader('Subject', encodeHeaderValue(input.subject)));
|
||||||
lines.push(formatHeader('Date', formatDate(input.date ?? new Date())));
|
lines.push(formatHeader('Date', formatDate(input.date ?? new Date())));
|
||||||
lines.push(formatHeader('Message-ID', input.messageId ?? `<${crypto.randomUUID()}@smime.local>`));
|
lines.push(formatHeader('Message-ID', input.messageId ?? `<${generateUUID()}@smime.local>`));
|
||||||
if (input.inReplyTo) {
|
if (input.inReplyTo) {
|
||||||
lines.push(formatHeader('In-Reply-To', input.inReplyTo));
|
lines.push(formatHeader('In-Reply-To', input.inReplyTo));
|
||||||
}
|
}
|
||||||
@@ -245,7 +247,7 @@ export function wrapCmsAsSmimeMessage(cmsBlob: Blob | ArrayBuffer | Uint8Array,
|
|||||||
}
|
}
|
||||||
lines.push(formatHeader('Subject', encodeHeaderValue(input.subject)));
|
lines.push(formatHeader('Subject', encodeHeaderValue(input.subject)));
|
||||||
lines.push(formatHeader('Date', formatDate(input.date ?? new Date())));
|
lines.push(formatHeader('Date', formatDate(input.date ?? new Date())));
|
||||||
lines.push(formatHeader('Message-ID', input.messageId ?? `<${crypto.randomUUID()}@smime.local>`));
|
lines.push(formatHeader('Message-ID', input.messageId ?? `<${generateUUID()}@smime.local>`));
|
||||||
if (input.inReplyTo) {
|
if (input.inReplyTo) {
|
||||||
lines.push(formatHeader('In-Reply-To', input.inReplyTo));
|
lines.push(formatHeader('In-Reply-To', input.inReplyTo));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as asn1js from 'asn1js';
|
import * as asn1js from 'asn1js';
|
||||||
import * as pkijs from 'pkijs';
|
import * as pkijs from 'pkijs';
|
||||||
|
import { generateUUID } from '@/lib/utils';
|
||||||
import {
|
import {
|
||||||
extractCertificateInfo,
|
extractCertificateInfo,
|
||||||
classifyCapabilities,
|
classifyCapabilities,
|
||||||
@@ -154,7 +155,7 @@ export async function importPkcs12(
|
|||||||
const email = certInfo.emailAddresses[0] ?? '';
|
const email = certInfo.emailAddresses[0] ?? '';
|
||||||
|
|
||||||
const keyRecord: SmimeKeyRecord = {
|
const keyRecord: SmimeKeyRecord = {
|
||||||
id: crypto.randomUUID(),
|
id: generateUUID(),
|
||||||
email: email.toLowerCase(),
|
email: email.toLowerCase(),
|
||||||
certificate: leafCertDer,
|
certificate: leafCertDer,
|
||||||
certificateChain: chainCertsDer,
|
certificateChain: chainCertsDer,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
import type { EmailTemplate } from './template-types';
|
import type { EmailTemplate } from './template-types';
|
||||||
import { BUILT_IN_PLACEHOLDERS } from './template-types';
|
import { BUILT_IN_PLACEHOLDERS } from './template-types';
|
||||||
|
import { generateUUID } from './utils';
|
||||||
|
|
||||||
const PLACEHOLDER_REGEX = /\{\{(\w+)\}\}/g;
|
const PLACEHOLDER_REGEX = /\{\{(\w+)\}\}/g;
|
||||||
const MAX_TEMPLATE_NAME_LENGTH = 200;
|
const MAX_TEMPLATE_NAME_LENGTH = 200;
|
||||||
@@ -149,7 +150,7 @@ export function importTemplates(json: string): ImportResult {
|
|||||||
const recipients = t.defaultRecipients as Record<string, unknown> | undefined;
|
const recipients = t.defaultRecipients as Record<string, unknown> | undefined;
|
||||||
|
|
||||||
templates.push({
|
templates.push({
|
||||||
id: crypto.randomUUID(),
|
id: generateUUID(),
|
||||||
name: sanitizeText(t.name),
|
name: sanitizeText(t.name),
|
||||||
subject: sanitizeText(t.subject),
|
subject: sanitizeText(t.subject),
|
||||||
body: sanitizeText(t.body),
|
body: sanitizeText(t.body),
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ export function cn(...inputs: ClassValue[]) {
|
|||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateUUID(): string {
|
||||||
|
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||||
|
return crypto.randomUUID();
|
||||||
|
}
|
||||||
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function formatDate(date: Date | string): string {
|
export function formatDate(date: Date | string): string {
|
||||||
const d = typeof date === "string" ? new Date(date) : date;
|
const d = typeof date === "string" ? new Date(date) : date;
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
import type { ContactCard, NameComponent, ContactOnlineService, AnniversaryDate, PartialDate } from "@/lib/jmap/types";
|
import type { ContactCard, NameComponent, ContactOnlineService, AnniversaryDate, PartialDate } from "@/lib/jmap/types";
|
||||||
|
import { generateUUID } from "@/lib/utils";
|
||||||
|
|
||||||
// Convert RFC 9553 AnniversaryDate (PartialDate|Timestamp|string) to vCard date string
|
// Convert RFC 9553 AnniversaryDate (PartialDate|Timestamp|string) to vCard date string
|
||||||
function anniversaryDateToVcardString(date: AnniversaryDate): string {
|
function anniversaryDateToVcardString(date: AnniversaryDate): string {
|
||||||
@@ -153,7 +154,7 @@ export function parseVCard(vcfString: string): ContactCard[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildContact(raw: Record<string, string[]>): ContactCard | null {
|
function buildContact(raw: Record<string, string[]>): ContactCard | null {
|
||||||
const id = `import-${crypto.randomUUID()}`;
|
const id = `import-${generateUUID()}`;
|
||||||
const card: ContactCard = { id, addressBookIds: {} };
|
const card: ContactCard = { id, addressBookIds: {} };
|
||||||
|
|
||||||
for (const [fullKey, values] of Object.entries(raw)) {
|
for (const [fullKey, values] of Object.entries(raw)) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { debug } from '@/lib/debug';
|
|||||||
import { normalizeAllDayDuration } from '@/lib/calendar-utils';
|
import { normalizeAllDayDuration } from '@/lib/calendar-utils';
|
||||||
import { sanitizeOutgoingCalendarEventData } from '@/lib/calendar-event-normalization';
|
import { sanitizeOutgoingCalendarEventData } from '@/lib/calendar-event-normalization';
|
||||||
import { expandRecurringEvents } from '@/lib/recurrence-expansion';
|
import { expandRecurringEvents } from '@/lib/recurrence-expansion';
|
||||||
|
import { generateUUID } from '@/lib/utils';
|
||||||
|
|
||||||
export type CalendarViewMode = 'month' | 'week' | 'day' | 'agenda' | 'tasks';
|
export type CalendarViewMode = 'month' | 'week' | 'day' | 'agenda' | 'tasks';
|
||||||
|
|
||||||
@@ -722,9 +723,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
|||||||
if (!calendar) throw new Error('Failed to create calendar');
|
if (!calendar) throw new Error('Failed to create calendar');
|
||||||
|
|
||||||
const subscription: ICalSubscription = {
|
const subscription: ICalSubscription = {
|
||||||
id: typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
id: generateUUID(),
|
||||||
? crypto.randomUUID()
|
|
||||||
: `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
||||||
url,
|
url,
|
||||||
calendarId: calendar.id,
|
calendarId: calendar.id,
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { create } from 'zustand';
|
|||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
import type { ContactCard, AddressBook, ContactName } from '@/lib/jmap/types';
|
import type { ContactCard, AddressBook, ContactName } from '@/lib/jmap/types';
|
||||||
import type { IJMAPClient } from '@/lib/jmap/client-interface';
|
import type { IJMAPClient } from '@/lib/jmap/client-interface';
|
||||||
|
import { generateUUID } from '@/lib/utils';
|
||||||
|
|
||||||
export function getContactDisplayName(contact: ContactCard): string {
|
export function getContactDisplayName(contact: ContactCard): string {
|
||||||
if (contact.name?.components) {
|
if (contact.name?.components) {
|
||||||
@@ -378,7 +379,7 @@ export const useContactStore = create<ContactStore>()(
|
|||||||
set((state) => ({ contacts: [...state.contacts, created] }));
|
set((state) => ({ contacts: [...state.contacts, created] }));
|
||||||
} else {
|
} else {
|
||||||
const localGroup: ContactCard = {
|
const localGroup: ContactCard = {
|
||||||
id: `local-${crypto.randomUUID()}`,
|
id: `local-${generateUUID()}`,
|
||||||
addressBookIds: {},
|
addressBookIds: {},
|
||||||
...groupData,
|
...groupData,
|
||||||
} as ContactCard;
|
} as ContactCard;
|
||||||
@@ -617,7 +618,7 @@ export const useContactStore = create<ContactStore>()(
|
|||||||
} else {
|
} else {
|
||||||
const localContact: ContactCard = {
|
const localContact: ContactCard = {
|
||||||
...contact,
|
...contact,
|
||||||
id: `local-${crypto.randomUUID()}`,
|
id: `local-${generateUUID()}`,
|
||||||
};
|
};
|
||||||
set((state) => ({ contacts: [...state.contacts, localContact] }));
|
set((state) => ({ contacts: [...state.contacts, localContact] }));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
import type { SmimeKeyRecord, SmimePublicCert } from '@/lib/smime/types';
|
import type { SmimeKeyRecord, SmimePublicCert } from '@/lib/smime/types';
|
||||||
|
import { generateUUID } from '@/lib/utils';
|
||||||
import {
|
import {
|
||||||
saveKeyRecord,
|
saveKeyRecord,
|
||||||
listKeyRecords,
|
listKeyRecords,
|
||||||
@@ -287,7 +288,7 @@ export const useSmimeStore = create<SmimeStore>()(
|
|||||||
const email = info.emailAddresses[0] ?? '';
|
const email = info.emailAddresses[0] ?? '';
|
||||||
|
|
||||||
const publicCert: SmimePublicCert = {
|
const publicCert: SmimePublicCert = {
|
||||||
id: crypto.randomUUID(),
|
id: generateUUID(),
|
||||||
accountId: get().currentAccountId ?? undefined,
|
accountId: get().currentAccountId ?? undefined,
|
||||||
email: email.toLowerCase(),
|
email: email.toLowerCase(),
|
||||||
certificate: der,
|
certificate: der,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
importTemplates as importUtil,
|
importTemplates as importUtil,
|
||||||
filterTemplates,
|
filterTemplates,
|
||||||
} from '@/lib/template-utils';
|
} from '@/lib/template-utils';
|
||||||
|
import { generateUUID } from '@/lib/utils';
|
||||||
|
|
||||||
const MAX_RECENT = 5;
|
const MAX_RECENT = 5;
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ export const useTemplateStore = create<TemplateStore>()(
|
|||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const template: EmailTemplate = {
|
const template: EmailTemplate = {
|
||||||
...data,
|
...data,
|
||||||
id: crypto.randomUUID(),
|
id: generateUUID(),
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
};
|
};
|
||||||
@@ -73,8 +74,9 @@ export const useTemplateStore = create<TemplateStore>()(
|
|||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const duplicate: EmailTemplate = {
|
const duplicate: EmailTemplate = {
|
||||||
...original,
|
...original,
|
||||||
id: crypto.randomUUID(),
|
id: generateUUID(),
|
||||||
name: `${original.name} ${nameSuffix || '(copy)'}`,
|
name: `${original.name} ${nameSuffix || '(copy)'}`,
|
||||||
|
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
|
|||||||
Reference in New Issue
Block a user