Skip to content

Commit dbf1e2f

Browse files
committed
Merge branch 'preview' of https://github.com/zecrypt-io/zecrypt-server into preview
2 parents a6529c9 + 99981ba commit dbf1e2f

File tree

14 files changed

+1802
-7
lines changed

14 files changed

+1802
-7
lines changed

SECURITY.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Security Policy
2+
3+
## 📌 Project: Zecrypt Labs
4+
5+
Zecrypt Lab is a zero-knowledge, end-to-end encrypted password manager designed with privacy and user data security as its top priorities. We take security issues seriously and appreciate responsible disclosures that help us improve the application for everyone.
6+
7+
---
8+
9+
## 🔐 Supported Versions
10+
11+
We actively maintain and patch security issues in the following versions:
12+
13+
| Version | Supported |
14+
|---------|-----------|
15+
| Latest ||
16+
17+
18+
If you're not using the latest version, we recommend upgrading as soon as possible.
19+
20+
---
21+
22+
## 📣 Reporting a Vulnerability
23+
24+
If you discover a security vulnerability in Secrets Lab, please **do not create a public GitHub issue**. Instead, report it privately to ensure the safety of our users:
25+
26+
- **Email**: `[email protected]`
27+
28+
When reporting a vulnerability, please include:
29+
30+
- A clear and concise description of the issue.
31+
- Steps to reproduce (if possible).
32+
- Any potential impact or exploitation scenarios.
33+
- Suggestions for a fix (if applicable).
34+
35+
We aim to respond to security reports **within 72 hours** and provide updates as we work toward a resolution.
36+
37+
---
38+
39+
## 🔄 Vulnerability Handling Process
40+
41+
1. **Initial Acknowledgement** (within 48 hours).
42+
2. **Verification & Impact Analysis**.
43+
3. **Patch Development** and coordinated disclosure.
44+
4. **Security Advisory Release** via GitHub and official channels.
45+
46+
---
47+
48+
## 🛡️ Security Features
49+
50+
Secrets Lab is designed with security at its core:
51+
52+
- **Zero-Knowledge Architecture**: We do not have access to your passwords or encryption keys.
53+
- **End-to-End Encryption**: All data is encrypted client-side before being sent to the server.
54+
- **No Raw Data Storage**: We never log or store unencrypted secrets.
55+
- **Automatic Logout & Session Expiry**
56+
- **Rate Limiting & Brute-force Protection**
57+
58+
---
59+
60+
## 🧪 Responsible Disclosure Rewards
61+
62+
We’re currently not offering monetary bounties, but all valid disclosures will be publicly credited (if desired) in our **Security Hall of Fame**.
63+
64+
---
65+
66+
## 🙌 Acknowledgments
67+
68+
We thank all security researchers and ethical hackers who help improve Secrets Lab. Your contributions help us build a safer digital future.
69+
70+
---
71+
72+
## 📄 License
73+
74+
This project is open-source and available under the [custom license](./LICENSE), restricting commercial use. Please respect its terms when contributing or reusing.
75+
76+
---
77+
78+
Stay safe,
79+
**The Zecrypt Labs Team**
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"use client";
2+
3+
import { DashboardLayout } from "@/components/dashboard-layout";
4+
import { EnvContentWrapper } from "@/components/env-content-wrapper";
5+
import { use } from "react";
6+
7+
export default function EnvPage({ params }: { params: Promise<{ locale: string }> }) {
8+
// Properly unwrap the params Promise using React.use
9+
const { locale } = use(params);
10+
11+
return (
12+
<DashboardLayout locale={locale}>
13+
<EnvContentWrapper />
14+
</DashboardLayout>
15+
);
16+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { DashboardLayout } from "@/components/dashboard-layout";
2+
import { NotesContent } from "@/components/notes-content";
3+
4+
export default async function AccountsPage({ params }: { params: Promise<{ locale: string }> }) {
5+
const { locale } = await params; // Await params
6+
7+
return (
8+
<DashboardLayout locale={locale}>
9+
<NotesContent />
10+
</DashboardLayout>
11+
);
12+
}

packages/frontend-web/app/globals.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,26 @@ body {
190190
100% { transform: rotate(360deg); }
191191
}
192192

193+
}
194+
195+
/* TipTap/ProseMirror editor focus and caret styles */
196+
.ProseMirror {
197+
outline: none !important;
198+
border: 1.5px solid #e5e7eb; /* Subtle border (Tailwind border-gray-200) */
199+
border-radius: 0.5rem; /* Rounded corners */
200+
min-height: 200px;
201+
padding: 0.75rem;
202+
transition: border-color 0.2s, box-shadow 0.2s;
203+
font-size: 1rem;
204+
background: transparent;
205+
caret-color: #60a5fa; /* Lighter blue (Tailwind blue-400) */
206+
}
207+
208+
.ProseMirror:focus {
209+
border-color: #bae6fd; /* Very light blue (Tailwind sky-200) */
210+
box-shadow: 0 0 0 2px #bae6fd66; /* Very light blue glow */
211+
}
212+
213+
.ProseMirror:focus-visible {
214+
outline: none !important;
193215
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { useSelector } from "react-redux";
5+
import { RootState } from "@/libs/Redux/store";
6+
import { Button } from "@/components/ui/button";
7+
import { Input } from "@/components/ui/input";
8+
import { Textarea } from "@/components/ui/textarea";
9+
import { Plus, X, AlertCircle } from "lucide-react";
10+
import { Badge } from "@/components/ui/badge";
11+
import { useTranslator } from "@/hooks/use-translations";
12+
import { useNoteManagement } from "@/hooks/use-note-management";
13+
import { useEditor, EditorContent } from '@tiptap/react';
14+
import StarterKit from '@tiptap/starter-kit';
15+
16+
interface AddNoteDialogProps {
17+
open: boolean;
18+
onOpenChange: (open: boolean) => void;
19+
onClose: () => void;
20+
onNoteAdded: () => void;
21+
}
22+
23+
export function AddNoteDialog({ open, onOpenChange, onClose, onNoteAdded }: AddNoteDialogProps) {
24+
const { translate } = useTranslator();
25+
const [title, setTitle] = useState("");
26+
const [tags, setTags] = useState<string[]>([]);
27+
const [newTag, setNewTag] = useState("");
28+
const [error, setError] = useState("");
29+
const [isSubmitting, setIsSubmitting] = useState(false);
30+
31+
const selectedWorkspaceId = useSelector((state: RootState) => state.workspace.selectedWorkspaceId);
32+
const selectedProjectId = useSelector((state: RootState) => state.workspace.selectedProjectId);
33+
const { handleAddNote } = useNoteManagement({ selectedWorkspaceId, selectedProjectId });
34+
35+
// TipTap editor setup
36+
const editor = useEditor({
37+
extensions: [StarterKit],
38+
content: '',
39+
});
40+
41+
const addTag = (tag: string) => {
42+
if (tag && !tags.includes(tag)) {
43+
setTags([...tags, tag]);
44+
setNewTag("");
45+
}
46+
};
47+
48+
const removeTag = (tag: string) => {
49+
setTags(tags.filter((t) => t !== tag));
50+
};
51+
52+
const handleSubmit = async () => {
53+
if (!title || !editor || editor.isEmpty) {
54+
setError(translate("please_fill_all_required_fields", "notes"));
55+
return;
56+
}
57+
setIsSubmitting(true);
58+
setError("");
59+
try {
60+
const noteContent = JSON.stringify(editor.getJSON());
61+
await handleAddNote({ title, data: noteContent, tags });
62+
onNoteAdded();
63+
onOpenChange(false);
64+
} catch (e) {
65+
setError(translate("error_adding_note", "notes"));
66+
} finally {
67+
setIsSubmitting(false);
68+
}
69+
};
70+
71+
// Basic formatting toolbar
72+
const MenuBar = () => editor ? (
73+
<div className="flex gap-2 mb-2 border-b pb-2">
74+
<Button type="button" size="sm" aria-label="Bold" variant={editor.isActive('bold') ? 'default' : 'outline'} onClick={() => editor.chain().focus().toggleBold().run()}><b>B</b></Button>
75+
<Button type="button" size="sm" aria-label="Italic" variant={editor.isActive('italic') ? 'default' : 'outline'} onClick={() => editor.chain().focus().toggleItalic().run()}><i>I</i></Button>
76+
<Button type="button" size="sm" aria-label="Heading 1" variant={editor.isActive('heading', { level: 1 }) ? 'default' : 'outline'} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}>H1</Button>
77+
<Button type="button" size="sm" aria-label="Heading 2" variant={editor.isActive('heading', { level: 2 }) ? 'default' : 'outline'} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}>H2</Button>
78+
<Button type="button" size="sm" aria-label="Bullet List" variant={editor.isActive('bulletList') ? 'default' : 'outline'} onClick={() => editor.chain().focus().toggleBulletList().run()}>• List</Button>
79+
<Button type="button" size="sm" aria-label="Ordered List" variant={editor.isActive('orderedList') ? 'default' : 'outline'} onClick={() => editor.chain().focus().toggleOrderedList().run()}>1. List</Button>
80+
</div>
81+
) : null;
82+
83+
return (
84+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm overflow-y-auto py-6 transition-all duration-300">
85+
<div className="w-full max-w-lg rounded-2xl bg-card p-8 border border-border shadow-2xl relative my-auto animate-fade-in">
86+
<div className="mb-8 text-center">
87+
<h2 className="text-2xl font-bold tracking-tight">{translate("add_new_note", "notes", { default: "Add New Note" })}</h2>
88+
{error && (
89+
<div className="mt-4 p-2 bg-red-50 border border-red-200 rounded-md flex items-center gap-2 text-red-600">
90+
<AlertCircle className="h-4 w-4 flex-shrink-0" />
91+
<p className="text-sm">{error}</p>
92+
</div>
93+
)}
94+
</div>
95+
<div className="space-y-6 max-h-[70vh] overflow-y-auto pr-2">
96+
<div className="space-y-2">
97+
<label className="text-base font-semibold">
98+
{translate("note_title", "notes", { default: "Title" })} <span className="text-red-500">*</span>
99+
</label>
100+
<Input
101+
placeholder={translate("enter_note_title", "notes", { default: "Enter note title" })}
102+
value={title}
103+
onChange={(e) => setTitle(e.target.value)}
104+
required
105+
className="text-lg px-4 py-3 rounded-lg border-2 border-border focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all"
106+
/>
107+
</div>
108+
<div className="space-y-2">
109+
<label className="text-base font-semibold">
110+
{translate("note_content", "notes", { default: "Content" })} <span className="text-red-500">*</span>
111+
</label>
112+
<div className="sticky top-0 z-10 bg-card rounded-t-lg pb-2">
113+
<MenuBar />
114+
</div>
115+
<EditorContent editor={editor} className="min-h-[200px] border rounded-lg p-3 bg-background text-base focus:outline-none transition-all" />
116+
</div>
117+
<div className="space-y-2">
118+
<label className="text-base font-semibold">{translate("tags", "notes", { default: "Tags" })}</label>
119+
<div className="flex flex-wrap gap-2 mb-2">
120+
{tags.map((tag) => (
121+
<Badge key={tag} variant="secondary" className="flex items-center gap-1 px-3 py-1 rounded-full text-sm">
122+
{tag}
123+
<X className="h-3 w-3 cursor-pointer" onClick={() => removeTag(tag)} />
124+
</Badge>
125+
))}
126+
</div>
127+
<div className="flex items-center gap-2">
128+
<Input
129+
placeholder={translate("add_a_tag", "notes", { default: "Add a tag" })}
130+
value={newTag}
131+
onChange={(e) => setNewTag(e.target.value)}
132+
onKeyDown={(e) => {
133+
if (e.key === "Enter") {
134+
e.preventDefault();
135+
addTag(newTag);
136+
}
137+
}}
138+
className="rounded-full px-4 py-2"
139+
/>
140+
<Button type="button" variant="outline" size="icon" onClick={() => addTag(newTag)} className="rounded-full">
141+
<Plus className="h-4 w-4" />
142+
</Button>
143+
</div>
144+
</div>
145+
<div className="flex items-center justify-between gap-4 pt-6">
146+
<Button variant="outline" className="w-1/2 py-3 rounded-lg text-base" onClick={() => onOpenChange(false)} disabled={isSubmitting}>
147+
{translate("cancel", "notes", { default: "Cancel" })}
148+
</Button>
149+
<Button
150+
variant="default"
151+
className="w-1/2 py-3 rounded-lg text-base bg-primary text-primary-foreground shadow-md hover:bg-primary/90 transition-all"
152+
onClick={handleSubmit}
153+
disabled={isSubmitting}
154+
>
155+
{isSubmitting ? `${translate("adding", "notes", { default: "Adding..." })}` : translate("add_note", "notes", { default: "Add Note" })}
156+
</Button>
157+
</div>
158+
</div>
159+
</div>
160+
</div>
161+
);
162+
}

packages/frontend-web/components/dashboard-layout.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,40 @@ const navigationCategories: NavigationCategory[] = [
208208
}
209209
];
210210

211+
// Replace with your actual extension ID from chrome://extensions
212+
const EXTENSION_ID = "bbfbjmmofbhcnegicongikfhceadhdek";
213+
214+
function ExtensionMessenger() {
215+
const accessToken = useSelector((state: RootState) => state.user.userData?.access_token);
216+
const selectedWorkspaceId = useSelector((state: RootState) => state.workspace.selectedWorkspaceId);
217+
const selectedProjectId = useSelector((state: RootState) => state.workspace.selectedProjectId);
218+
219+
useEffect(() => {
220+
if (accessToken && selectedWorkspaceId && selectedProjectId) {
221+
if ((window as any).chrome && (window as any).chrome.runtime && (window as any).chrome.runtime.sendMessage) {
222+
(window as any).chrome.runtime.sendMessage(
223+
EXTENSION_ID,
224+
{
225+
type: "LOGIN",
226+
token: accessToken,
227+
workspaceId: selectedWorkspaceId,
228+
projectId: selectedProjectId,
229+
},
230+
(response: { success?: boolean }) => {
231+
if (response && response.success) {
232+
console.log("Zecrypt extension: Login data sent successfully!");
233+
} else {
234+
console.warn("Zecrypt extension: Failed to send login data.");
235+
}
236+
}
237+
);
238+
}
239+
}
240+
}, [accessToken, selectedWorkspaceId, selectedProjectId]);
241+
242+
return null;
243+
}
244+
211245
export function DashboardLayout({ children, locale = "en" }: DashboardLayoutProps) {
212246
const pathname = usePathname();
213247
const router = useRouter();
@@ -564,6 +598,7 @@ export function DashboardLayout({ children, locale = "en" }: DashboardLayoutProp
564598

565599
return (
566600
<div className="flex min-h-screen bg-background">
601+
<ExtensionMessenger />
567602
<ChatWidget />
568603
<SearchModal
569604
isOpen={showSearchModal}

0 commit comments

Comments
 (0)