1
1
"use client" ;
2
2
3
- import { deletePasswordItem , getPasswords } from "@/app/actions" ;
4
- import { Button } from "@/components/ui/button" ;
5
- import { Input } from "@/components/ui/input" ;
6
- import { ScrollArea } from "@/components/ui/scroll-area" ;
7
- import { Sheet , SheetContent } from "@/components/ui/sheet" ;
8
- import { CreatePasswordDialog } from "@/components/vault/dialogs/create-password-dialog" ;
9
- import { EditPasswordDialog } from "@/components/vault/dialogs/edit-password-dialog" ;
10
- import { cn } from "@/lib/utils" ;
11
- import { decrypt } from "@/utils/encryption" ;
12
- import { useUser } from "@clerk/nextjs" ;
13
- import { Prisma } from "@prisma/client" ;
14
- import { Plus , SquareArrowOutUpRight , Trash , User } from "lucide-react" ;
3
+ import { deletePasswordItem , getPasswords } from "@/app/actions" ;
4
+ import { Button } from "@/components/ui/button" ;
5
+ import { Input } from "@/components/ui/input" ;
6
+ import { ScrollArea } from "@/components/ui/scroll-area" ;
7
+ import { Sheet , SheetContent } from "@/components/ui/sheet" ;
8
+ import { CreatePasswordDialog } from "@/components/vault/dialogs/create-password-dialog" ;
9
+ import { EditPasswordDialog } from "@/components/vault/dialogs/edit-password-dialog" ;
10
+ import { cn } from "@/lib/utils" ;
11
+ import { decrypt , generateAndStoreKey , retrieveKey } from "@/utils/encryption" ;
12
+ import { useUser } from "@clerk/nextjs" ;
13
+ import { Prisma } from "@prisma/client" ;
14
+ import { Plus , SquareArrowOutUpRight , Trash , User } from "lucide-react" ;
15
15
import Image from "next/image" ;
16
- import { useRouter } from "next/navigation" ;
17
- import { useEffect , useState } from "react" ;
16
+ import { useRouter } from "next/navigation" ;
17
+ import { useEffect , useState } from "react" ;
18
18
import toast from "react-hot-toast" ;
19
19
import {
20
20
ContextMenu ,
@@ -23,16 +23,17 @@ import {
23
23
ContextMenuLabel ,
24
24
ContextMenuTrigger ,
25
25
} from "../ui/context-menu" ;
26
- import { EmptyState } from "./empty-state" ;
27
- import { PasswordDetails } from "./password-details" ;
28
- import { Sidebar } from "./sidebar" ;
26
+ import { EmptyState } from "./empty-state" ;
27
+ import { PasswordDetails } from "./password-details" ;
28
+ import { Sidebar } from "./sidebar" ;
29
29
30
30
interface PasswordEntry {
31
31
id : string ;
32
32
name : string ;
33
33
username : string ;
34
34
website : string ;
35
35
password : string ;
36
+ iv : string ;
36
37
updatedAt : string ;
37
38
lastAccess : string ;
38
39
created : string ;
@@ -63,30 +64,61 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
63
64
const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
64
65
const [ filteredEntries , setFilteredEntries ] = useState < PasswordEntry [ ] > ( [ ] ) ;
65
66
const [ passwords , setPasswords ] = useState < PasswordEntry [ ] > ( [ ] ) ;
66
- const [ passwordItems , setPasswordItems ] = useState ( user ?. passwordItems )
67
+ const [ passwordItems , setPasswordItems ] = useState ( user ?. passwordItems ) ;
67
68
68
69
useEffect ( ( ) => {
69
- if ( ! clerkUser ) return ;
70
+ const ensureEncryptionKey = async ( ) => {
71
+ if ( ! clerkUser ) return ;
70
72
71
- if ( ! user ?. passwordItems || ! passwordItems ) return ;
73
+ const userId = clerkUser . id ;
72
74
73
- const decryptedPasswords = passwordItems
74
- . map ( ( item ) => ( {
75
- id : item . id ,
76
- name : decrypt ( item . username , clerkUser ) ,
77
- username : decrypt ( item . username , clerkUser ) ,
78
- website : decrypt ( item . website , clerkUser ) ,
79
- password : decrypt ( item . password , clerkUser ) ,
80
- updatedAt : item . updatedAt . toISOString ( ) ,
81
- lastAccess : item . updatedAt . toISOString ( ) ,
82
- created : item . createdAt . toISOString ( ) ,
83
- } ) )
84
- . sort (
85
- ( a , b ) => new Date ( b . created ) . getTime ( ) - new Date ( a . created ) . getTime ( )
86
- ) ;
75
+ try {
76
+ await retrieveKey ( userId ) ;
77
+ toast . success ( "Encryption key found" ) ;
78
+ } catch {
79
+ toast . success ( "Generating encryption key..." ) ;
80
+ await generateAndStoreKey ( userId ) ;
81
+ }
82
+ } ;
83
+
84
+ ensureEncryptionKey ( ) ;
85
+ } , [ clerkUser ] ) ;
87
86
88
- setPasswords ( decryptedPasswords ) ;
87
+ useEffect ( ( ) => {
88
+ if ( ! clerkUser ) return ;
89
+
90
+ if ( ! user ?. passwordItems || ! passwordItems ) return ;
91
+
92
+ const decryptPasswords = async ( ) => {
93
+ const decryptedPasswords = await Promise . all (
94
+ passwordItems . map ( async ( item ) => {
95
+ try {
96
+ const decryptedItem = {
97
+ id : item . id ,
98
+ name : await decrypt ( item . username , item . usernameIV , clerkUser . id ) ,
99
+ username : await decrypt ( item . username , item . usernameIV , clerkUser . id ) ,
100
+ website : await decrypt ( item . website , item . websiteIV , clerkUser . id ) ,
101
+ password : await decrypt ( item . password , item . passwordIV , clerkUser . id ) ,
102
+ updatedAt : item . updatedAt . toISOString ( ) ,
103
+ lastAccess : item . updatedAt . toISOString ( ) ,
104
+ created : item . createdAt . toISOString ( ) ,
105
+ } ;
106
+ return decryptedItem ;
107
+ } catch ( error ) {
108
+ console . error ( `Error decrypting item ID: ${ item . id } ` , error ) ;
109
+ return null ;
110
+ }
111
+ } )
112
+ ) ;
113
+
114
+ setPasswords (
115
+ decryptedPasswords . filter ( ( item ) : item is PasswordEntry => item !== null )
116
+ ) ;
117
+ } ;
118
+
119
+ decryptPasswords ( ) ;
89
120
} , [ user ?. passwordItems , clerkUser , passwordItems ] ) ;
121
+
90
122
91
123
const handleSearchChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
92
124
setSearchQuery ( e . target . value ) ;
@@ -212,8 +244,10 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
212
244
onClick = { async ( ) => {
213
245
try {
214
246
await deletePasswordItem ( password . id ) ;
215
- const updatedItems = await getPasswords ( user ?. id as string )
216
- setPasswordItems ( updatedItems ?. passwordItems ) ;
247
+ const updatedItems = await getPasswords (
248
+ user ?. id as string
249
+ ) ;
250
+ setPasswordItems ( updatedItems ?. passwordItems ) ;
217
251
if ( selectedEntry ?. id === password . id ) {
218
252
setSelectedEntry ( null ) ;
219
253
}
@@ -285,8 +319,8 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
285
319
onClose = { async ( ) => {
286
320
setIsCreateDialogOpen ( false ) ;
287
321
setSelectedEntry ( null ) ;
288
- const userWithPasswords = await getPasswords ( user ?. id as string )
289
- setPasswordItems ( userWithPasswords ?. passwordItems )
322
+ const userWithPasswords = await getPasswords ( user ?. id as string ) ;
323
+ setPasswordItems ( userWithPasswords ?. passwordItems ) ;
290
324
} }
291
325
/>
292
326
</ div >
0 commit comments