@muhgholy/next-drive 4.23.7 → 4.23.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -1
- package/dist/{chunk-LAKT7IJJ.cjs → chunk-V75PCJHT.cjs} +962 -773
- package/dist/chunk-V75PCJHT.cjs.map +1 -0
- package/dist/{chunk-MVYNW56R.js → chunk-XUPDNN2U.js} +957 -770
- package/dist/chunk-XUPDNN2U.js.map +1 -0
- package/dist/client/components/drive/{RenameAccountDialog.d.ts → account/rename.d.ts} +2 -2
- package/dist/client/components/drive/account/rename.d.ts.map +1 -0
- package/dist/client/components/drive/{dnd-provider.d.ts → dnd/context.d.ts} +1 -1
- package/dist/client/components/drive/dnd/context.d.ts.map +1 -0
- package/dist/client/components/drive/{CreateFolderDialog.d.ts → folder/create.d.ts} +2 -2
- package/dist/client/components/drive/folder/create.d.ts.map +1 -0
- package/dist/client/components/drive/{RenameDialog.d.ts → item/rename.d.ts} +3 -3
- package/dist/client/components/drive/item/rename.d.ts.map +1 -0
- package/dist/client/components/{dialog.d.ts → shared/confirm.d.ts} +2 -2
- package/dist/client/components/shared/confirm.d.ts.map +1 -0
- package/dist/client/components/ui/{alert-dialog.d.ts → alert-modal.d.ts} +1 -1
- package/dist/client/components/ui/alert-modal.d.ts.map +1 -0
- package/dist/client/components/ui/{dialog-fullscreen.d.ts → fullscreen.d.ts} +1 -1
- package/dist/client/components/ui/fullscreen.d.ts.map +1 -0
- package/dist/client/components/ui/{dialog.d.ts → modal.d.ts} +1 -1
- package/dist/client/components/ui/modal.d.ts.map +1 -0
- package/dist/client/context.d.ts.map +1 -1
- package/dist/client/file-chooser.d.ts +1 -0
- package/dist/client/file-chooser.d.ts.map +1 -1
- package/dist/client/hooks/{useUpload.d.ts → use-upload.d.ts} +2 -2
- package/dist/client/hooks/use-upload.d.ts.map +1 -0
- package/dist/client/index.cjs +315 -206
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.ts +12 -11
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +314 -205
- package/dist/client/index.js.map +1 -1
- package/dist/server/actions/auth.d.ts +4 -0
- package/dist/server/actions/auth.d.ts.map +1 -0
- package/dist/server/actions/cors.d.ts +4 -0
- package/dist/server/actions/cors.d.ts.map +1 -0
- package/dist/server/actions/drive.d.ts +18 -0
- package/dist/server/actions/drive.d.ts.map +1 -0
- package/dist/server/actions/public.d.ts +4 -0
- package/dist/server/actions/public.d.ts.map +1 -0
- package/dist/server/actions/shared.d.ts +14 -0
- package/dist/server/actions/shared.d.ts.map +1 -0
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/controllers/drive.d.ts +26 -0
- package/dist/server/controllers/drive.d.ts.map +1 -1
- package/dist/server/database/mongoose/schema/drive.d.ts +1 -0
- package/dist/server/database/mongoose/schema/drive.d.ts.map +1 -1
- package/dist/server/express.cjs +11 -11
- package/dist/server/express.js +2 -2
- package/dist/server/hono.cjs +11 -11
- package/dist/server/hono.js +2 -2
- package/dist/server/index.cjs +24 -16
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/security/{cryptoUtils.d.ts → crypto-utils.d.ts} +1 -1
- package/dist/server/security/crypto-utils.d.ts.map +1 -0
- package/dist/server/security/{mimeFilter.d.ts → mime-filter.d.ts} +1 -1
- package/dist/server/security/mime-filter.d.ts.map +1 -0
- package/dist/server/storage-adapters/google.d.ts.map +1 -0
- package/dist/server/storage-adapters/local.d.ts.map +1 -0
- package/dist/server/utils/{folderValidation.d.ts → folder-validation.d.ts} +1 -1
- package/dist/server/utils/folder-validation.d.ts.map +1 -0
- package/dist/server/utils/{imageConvert.d.ts → image-convert.d.ts} +1 -1
- package/dist/server/utils/image-convert.d.ts.map +1 -0
- package/dist/server/zod/schemas.d.ts +5 -0
- package/dist/server/zod/schemas.d.ts.map +1 -1
- package/dist/types/lib/database/drive.d.ts +1 -0
- package/dist/types/lib/database/drive.d.ts.map +1 -1
- package/dist/types/lib/database/index.d.ts +2 -2
- package/dist/types/lib/database/index.d.ts.map +1 -1
- package/dist/types/server/config.d.ts +17 -0
- package/dist/types/server/config.d.ts.map +1 -1
- package/dist/types/server/index.d.ts +5 -5
- package/dist/types/server/index.d.ts.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-LAKT7IJJ.cjs.map +0 -1
- package/dist/chunk-MVYNW56R.js.map +0 -1
- package/dist/client/components/dialog.d.ts.map +0 -1
- package/dist/client/components/drive/CreateFolderDialog.d.ts.map +0 -1
- package/dist/client/components/drive/RenameAccountDialog.d.ts.map +0 -1
- package/dist/client/components/drive/RenameDialog.d.ts.map +0 -1
- package/dist/client/components/drive/dnd-provider.d.ts.map +0 -1
- package/dist/client/components/ui/alert-dialog.d.ts.map +0 -1
- package/dist/client/components/ui/dialog-fullscreen.d.ts.map +0 -1
- package/dist/client/components/ui/dialog.d.ts.map +0 -1
- package/dist/client/hooks/useUpload.d.ts.map +0 -1
- package/dist/server/providers/google.d.ts.map +0 -1
- package/dist/server/providers/local.d.ts.map +0 -1
- package/dist/server/security/cryptoUtils.d.ts.map +0 -1
- package/dist/server/security/mimeFilter.d.ts.map +0 -1
- package/dist/server/utils/folderValidation.d.ts.map +0 -1
- package/dist/server/utils/imageConvert.d.ts.map +0 -1
- /package/dist/server/{providers → storage-adapters}/google.d.ts +0 -0
- /package/dist/server/{providers → storage-adapters}/local.d.ts +0 -0
package/README.md
CHANGED
|
@@ -7,7 +7,8 @@ File storage and management for Next.js and Express apps. Includes a responsive
|
|
|
7
7
|
- 📁 **File Management** – Upload, rename, move, organize files and folders
|
|
8
8
|
- 🔍 **Search** – Search active files or trash with real-time filtering
|
|
9
9
|
- 🗑️ **Trash System** – Soft delete, restore, and empty trash
|
|
10
|
-
-
|
|
10
|
+
- � **Anonymous Uploads** – One-time uploads with auto-expiry, confirmation, and abuse prevention
|
|
11
|
+
- �📱 **Responsive UI** – Optimized for desktop and mobile
|
|
11
12
|
- 🎬 **Video Thumbnails** – Auto-generated thumbnails (requires FFmpeg)
|
|
12
13
|
- 🔐 **Security** – Signed URLs and configurable upload limits
|
|
13
14
|
- 📊 **View Modes** – Grid/List views with sorting and grouping
|
|
@@ -188,6 +189,8 @@ function MyForm() {
|
|
|
188
189
|
}
|
|
189
190
|
```
|
|
190
191
|
|
|
192
|
+
> For unauthenticated, one-time uploads, add the `allowUnauthenticated` prop. See [Anonymous (One-Time) Uploads](#anonymous-one-time-uploads).
|
|
193
|
+
|
|
191
194
|
---
|
|
192
195
|
|
|
193
196
|
## Express Integration
|
|
@@ -582,6 +585,128 @@ await driveDelete(items[0]);
|
|
|
582
585
|
|
|
583
586
|
---
|
|
584
587
|
|
|
588
|
+
## Anonymous (One-Time) Uploads
|
|
589
|
+
|
|
590
|
+
Let **unauthenticated** users upload a file once — without giving them access to a drive. The user picks a file, it uploads with progress (just like the drive explorer), and you receive a normal `TDriveFile` (with a real `drive.id`). The file is **temporary**: it is automatically deleted after a TTL (default **60 minutes**) unless your backend explicitly confirms it.
|
|
591
|
+
|
|
592
|
+
The same `DriveFileChooser` adapts to the visitor automatically: **logged-in users get the full drive explorer** (browse + pick existing files or upload to their own drive, stored permanently), while **anonymous visitors get a one-time upload** (temporary, auto-expiring). You opt in with a single `allowUnauthenticated` prop.
|
|
593
|
+
|
|
594
|
+
This is useful for forms where you need a `drive.id` for an anonymous visitor (e.g. a contact form attachment, a job application, or a guest submission) but don't want to expose your authenticated drive.
|
|
595
|
+
|
|
596
|
+
**Lifecycle:** `pick file → upload → returns TDriveFile (temporary)` → your backend saves `drive.id` → `driveConfirm(id)` keeps it → otherwise `drivePurgeExpired()` deletes it after the TTL.
|
|
597
|
+
|
|
598
|
+
### 1. Enable in your server configuration
|
|
599
|
+
|
|
600
|
+
Add an `unauthenticated` block to `security`. It is **completely separate** from your authenticated limits (it has its own size and MIME allow-list). The feature is disabled unless `enabled: true`.
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
security: {
|
|
604
|
+
// ...your normal authenticated limits
|
|
605
|
+
maxUploadSizeInBytes: 50 * 1024 * 1024,
|
|
606
|
+
allowedMimeTypes: ["image/*", "video/*", "application/pdf"],
|
|
607
|
+
|
|
608
|
+
unauthenticated: {
|
|
609
|
+
enabled: true,
|
|
610
|
+
ttlMinutes: 60, // Lifetime before auto-delete (default 60)
|
|
611
|
+
maxUploadSizeInBytes: 25 * 1024 * 1024, // 25MB (separate from authenticated limit)
|
|
612
|
+
allowedMimeTypes: ["image/*", "application/pdf"],
|
|
613
|
+
|
|
614
|
+
// Optional abuse prevention (all fields optional)
|
|
615
|
+
abuse: {
|
|
616
|
+
perIp: { windowMinutes: 10, max: 20 }, // Max 20 uploads / 10 min per IP
|
|
617
|
+
hourlyPerIp: 60, // Max 60 uploads / hour per IP
|
|
618
|
+
maxConcurrent: 10, // Max simultaneous anonymous uploads (global)
|
|
619
|
+
maxLiveBytes: 2 * 1024 * 1024 * 1024, // Max total live temporary storage (global)
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
> Anonymous uploads do **not** count against any user's quota and are stored with no owner.
|
|
626
|
+
|
|
627
|
+
### 2. Client component
|
|
628
|
+
|
|
629
|
+
Pass the `allowUnauthenticated` prop to `DriveFileChooser`. It checks the visitor's auth status once on mount:
|
|
630
|
+
|
|
631
|
+
- **Logged in** → opens the normal drive explorer (browse and pick existing files or upload to their own drive).
|
|
632
|
+
- **Not logged in** → opens the native file picker and uploads anonymously, returning the uploaded `TDriveFile`.
|
|
633
|
+
|
|
634
|
+
```tsx
|
|
635
|
+
import { useState } from "react";
|
|
636
|
+
import { DriveFileChooser } from "@muhgholy/next-drive/client";
|
|
637
|
+
import type { TDriveFile } from "@muhgholy/next-drive/client";
|
|
638
|
+
|
|
639
|
+
function GuestForm() {
|
|
640
|
+
const [file, setFile] = useState<TDriveFile | null>(null);
|
|
641
|
+
|
|
642
|
+
return (
|
|
643
|
+
<DriveFileChooser
|
|
644
|
+
allowUnauthenticated
|
|
645
|
+
value={file}
|
|
646
|
+
onChange={setFile}
|
|
647
|
+
accept="image/*"
|
|
648
|
+
placeholder="Upload an attachment"
|
|
649
|
+
/>
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
> `DriveFileChooser` must be inside a `DriveProvider` (it reads `apiEndpoint` and resolves auth from context). The `multiple` and `accept` props work the same as the standard chooser.
|
|
655
|
+
|
|
656
|
+
### 3. Confirm to keep the file
|
|
657
|
+
|
|
658
|
+
After you persist the returned `drive.id` (e.g. attach it to a submitted form record), call `driveConfirm` to clear its expiry so it is never auto-deleted:
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
import { driveConfirm } from "@muhgholy/next-drive/server";
|
|
662
|
+
|
|
663
|
+
const kept = await driveConfirm(driveFile.id);
|
|
664
|
+
// kept === true if the file exists
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
**Returns:** `Promise<boolean>` — `true` if the file exists. Safe to call on files uploaded while logged in (they are already permanent, so it is a harmless no-op) — so your form handler can always call `driveConfirm` regardless of whether the visitor was authenticated.
|
|
668
|
+
|
|
669
|
+
### 4. Purge expired (unconfirmed) files
|
|
670
|
+
|
|
671
|
+
Run `drivePurgeExpired` on a schedule (cron job or interval) to permanently delete temporary files whose TTL has passed and were never confirmed:
|
|
672
|
+
|
|
673
|
+
```typescript
|
|
674
|
+
import { drivePurgeExpired } from "@muhgholy/next-drive/server";
|
|
675
|
+
|
|
676
|
+
// e.g. in a cron route or a setInterval on your server
|
|
677
|
+
const result = await drivePurgeExpired();
|
|
678
|
+
console.log(`Purged ${result.removed.length} expired files, freed ${result.totalFreedInBytes} bytes`);
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
**Returns:** `{ removed: string[], totalFreedInBytes: number }`
|
|
682
|
+
|
|
683
|
+
> [!IMPORTANT]
|
|
684
|
+
> Confirmed files are never purged. Only files that are both expired **and** unconfirmed are removed. If you never call `drivePurgeExpired`, temporary files will remain — schedule it (e.g. hourly).
|
|
685
|
+
|
|
686
|
+
### Determining the client IP (behind a proxy)
|
|
687
|
+
|
|
688
|
+
Per-IP limits resolve the client IP from `cf-connecting-ip`, then the first entry of `x-forwarded-for`, then the socket address. Override the trusted headers or supply your own resolver:
|
|
689
|
+
|
|
690
|
+
```typescript
|
|
691
|
+
unauthenticated: {
|
|
692
|
+
enabled: true,
|
|
693
|
+
maxUploadSizeInBytes: 25 * 1024 * 1024,
|
|
694
|
+
allowedMimeTypes: ["image/*"],
|
|
695
|
+
abuse: {
|
|
696
|
+
perIp: { windowMinutes: 10, max: 20 },
|
|
697
|
+
trustedHeaders: ["cf-connecting-ip", "x-forwarded-for"], // checked in order
|
|
698
|
+
clientId: (req) => (req.headers["cf-connecting-ip"] as string) ?? "unknown", // full override
|
|
699
|
+
},
|
|
700
|
+
}
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
> [!WARNING]
|
|
704
|
+
> Only trust forwarded IP headers if every request reaches your origin through a proxy you control (e.g. Cloudflare). If clients can reach the origin directly, these headers can be spoofed — lock your origin down to your proxy's IPs.
|
|
705
|
+
>
|
|
706
|
+
> Abuse counters are kept in-memory per process. In a multi-instance deployment, each instance enforces limits independently; use a shared store (e.g. Redis) if you need cross-instance limits.
|
|
707
|
+
|
|
708
|
+
---
|
|
709
|
+
|
|
585
710
|
## Configuration Options
|
|
586
711
|
|
|
587
712
|
### Security
|
|
@@ -596,9 +721,35 @@ security: {
|
|
|
596
721
|
expiresIn: 3600, // seconds
|
|
597
722
|
},
|
|
598
723
|
trash: { retentionDays: 30 },
|
|
724
|
+
// Anonymous one-time uploads (see "Anonymous (One-Time) Uploads" section)
|
|
725
|
+
unauthenticated: {
|
|
726
|
+
enabled: true,
|
|
727
|
+
ttlMinutes: 60,
|
|
728
|
+
maxUploadSizeInBytes: 25 * 1024 * 1024,
|
|
729
|
+
allowedMimeTypes: ['image/*', 'application/pdf'],
|
|
730
|
+
abuse: {
|
|
731
|
+
perIp: { windowMinutes: 10, max: 20 },
|
|
732
|
+
hourlyPerIp: 60,
|
|
733
|
+
maxConcurrent: 10,
|
|
734
|
+
maxLiveBytes: 2 * 1024 * 1024 * 1024,
|
|
735
|
+
},
|
|
736
|
+
},
|
|
599
737
|
}
|
|
600
738
|
```
|
|
601
739
|
|
|
740
|
+
| Option | Type | Default | Description |
|
|
741
|
+
| ------------------------------------- | ---------- | ----------- | ---------------------------------------------------------------- |
|
|
742
|
+
| `unauthenticated.enabled` | `boolean` | `false` | Enable anonymous one-time uploads |
|
|
743
|
+
| `unauthenticated.ttlMinutes` | `number` | `60` | Minutes before an unconfirmed upload becomes eligible for purge |
|
|
744
|
+
| `unauthenticated.maxUploadSizeInBytes`| `number` | — | Max size per anonymous upload (separate from authenticated) |
|
|
745
|
+
| `unauthenticated.allowedMimeTypes` | `string[]` | — | Allowed MIME types for anonymous uploads |
|
|
746
|
+
| `unauthenticated.abuse.perIp` | `{ windowMinutes, max }` | — | Sliding-window per-IP upload limit |
|
|
747
|
+
| `unauthenticated.abuse.hourlyPerIp` | `number` | — | Max anonymous uploads per IP per hour |
|
|
748
|
+
| `unauthenticated.abuse.maxConcurrent` | `number` | — | Max simultaneous anonymous uploads (global, per process) |
|
|
749
|
+
| `unauthenticated.abuse.maxLiveBytes` | `number` | — | Max total live temporary storage in bytes (global) |
|
|
750
|
+
| `unauthenticated.abuse.trustedHeaders`| `string[]` | `['cf-connecting-ip', 'x-forwarded-for']` | Headers checked in order to resolve client IP |
|
|
751
|
+
| `unauthenticated.abuse.clientId` | `(req) => string` | — | Full override for resolving the client identifier |
|
|
752
|
+
|
|
602
753
|
### CORS (Cross-Origin)
|
|
603
754
|
|
|
604
755
|
Required when client and API are on different domains:
|