From 8b27c6b1403be70b58f7b6eed08520be469f79eb Mon Sep 17 00:00:00 2001 From: Nixyi Date: Wed, 18 Dec 2024 16:08:06 -0300 Subject: [PATCH] Stable Release (I think) Added all SQL scripts by using a python script to fetch them. Also added a "About" page and a skeleton to the chat page. Fixed the register function that was not setting the public_key on the database --- README.md | 256 ++++++++++++++++-- src/app/[id]/page.tsx | 40 +-- src/app/[id]/skeleton.tsx | 50 ++++ src/app/about/page.tsx | 234 ++++++++++++++++ src/app/api/auth/register/route.ts | 7 +- src/app/api/user/send/update/key/route.ts | 1 - src/app/auth/login/page.tsx | 12 +- src/app/auth/login/register.ts | 2 +- src/app/layout.tsx | 12 +- src/app/page.tsx | 6 +- .../main/realtime/{threads.tsx => index.tsx} | 14 +- src/components/main/realtime/request.tsx | 42 --- src/components/main/sidebar/rightsidebar.tsx | 8 +- src/components/main/sidebar/sidebar.tsx | 2 +- src/components/ui/skeleton.tsx | 15 + src/lib/crypto/keys.ts | 2 +- src/middleware.ts | 2 + supabase/.temp/cli-latest | 1 - supabase/main.py | 116 ++++++++ ...el_security_policies_for_messaging_app.sql | 37 --- .../sql_snippets/Add public key to users.sql | 1 + ...time and Replica Identity for Messages.sql | 1 + ...et Replica Identity for Messages Table.sql | 1 + .../Create Private Thread Function.sql | 1 + .../Create Private Thread Function_1.sql | 1 + ...plica Identity for Thread Participants.sql | 1 + ... Full Replica Identity for Users Table.sql | 1 + .../Enable Replication for Messages Table.sql | 1 + supabase/sql_snippets/Get Thread Details.sql | 1 + supabase/sql_snippets/Get User Threads.sql | 1 + ...w Level Security Policies for Messages.sql | 1 + ...el Security Policies for Messaging App.sql | 1 + .../sql_snippets/Send Message Function.sql | 1 + .../Update User Requests Function.sql | 1 + ...User Access Policy for Search Function.sql | 1 + .../User Management Functions.sql | 1 + .../sql_snippets/User Management Table.sql | 1 + .../sql_snippets/User Registration Policy.sql | 1 + .../User Requests and Messages Management.sql | 1 + .../sql_snippets/User and Message Indexes.sql | 1 + supabase/sql_snippets/Users Table.sql | 1 + ...user_access_policy_for_search_function.sql | 59 ---- supabase/user_and_message_indexes.sql | 5 - supabase/user_management_functions.sql | 116 -------- supabase/user_management_table.sql | 46 ---- supabase/user_registration_policy.sql | 47 ---- supabase/user_search_function.sql | 1 - supabase/users_table.sql | 27 -- 48 files changed, 736 insertions(+), 445 deletions(-) create mode 100644 src/app/[id]/skeleton.tsx create mode 100644 src/app/about/page.tsx rename src/components/main/realtime/{threads.tsx => index.tsx} (84%) delete mode 100644 src/components/main/realtime/request.tsx create mode 100644 src/components/ui/skeleton.tsx delete mode 100644 supabase/.temp/cli-latest create mode 100644 supabase/main.py delete mode 100644 supabase/row_level_security_policies_for_messaging_app.sql create mode 100644 supabase/sql_snippets/Add public key to users.sql create mode 100644 supabase/sql_snippets/Check Realtime and Replica Identity for Messages.sql create mode 100644 supabase/sql_snippets/Check and Set Replica Identity for Messages Table.sql create mode 100644 supabase/sql_snippets/Create Private Thread Function.sql create mode 100644 supabase/sql_snippets/Create Private Thread Function_1.sql create mode 100644 supabase/sql_snippets/Enable Full Replica Identity for Thread Participants.sql create mode 100644 supabase/sql_snippets/Enable Full Replica Identity for Users Table.sql create mode 100644 supabase/sql_snippets/Enable Replication for Messages Table.sql create mode 100644 supabase/sql_snippets/Get Thread Details.sql create mode 100644 supabase/sql_snippets/Get User Threads.sql create mode 100644 supabase/sql_snippets/Row Level Security Policies for Messages.sql create mode 100644 supabase/sql_snippets/Row Level Security Policies for Messaging App.sql create mode 100644 supabase/sql_snippets/Send Message Function.sql create mode 100644 supabase/sql_snippets/Update User Requests Function.sql create mode 100644 supabase/sql_snippets/User Access Policy for Search Function.sql create mode 100644 supabase/sql_snippets/User Management Functions.sql create mode 100644 supabase/sql_snippets/User Management Table.sql create mode 100644 supabase/sql_snippets/User Registration Policy.sql create mode 100644 supabase/sql_snippets/User Requests and Messages Management.sql create mode 100644 supabase/sql_snippets/User and Message Indexes.sql create mode 100644 supabase/sql_snippets/Users Table.sql delete mode 100644 supabase/user_access_policy_for_search_function.sql delete mode 100644 supabase/user_and_message_indexes.sql delete mode 100644 supabase/user_management_functions.sql delete mode 100644 supabase/user_management_table.sql delete mode 100644 supabase/user_registration_policy.sql delete mode 100644 supabase/user_search_function.sql delete mode 100644 supabase/users_table.sql diff --git a/README.md b/README.md index 3ea1400..df58f05 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,249 @@ -- 1 - What will your software do? +# Silent Whisper - SiPher -My software will encrypt messages just like WhatsApp does by using a system of people having a key and sharing them -with one another. +[//]: # (TODO:) -- 1.1 - What features will it have? +### Video Demo: -I'll let the user choose multiple encryption methods, this will make it more secure and reliable. -Only the user will have its password that he could share with another user. +### Description: -- 1.2 How will it be executed? +I created this app mainly to learn more about design and improve my skills in this area, plus learn a bit more about how +E2EE encryption works. -Mainly by creating a database that would only hold a username and a password, could use Supabase for that -or a simple MongoDb cluster. +I ran into LOTS of problems (like, seriously, a ton) when starting the app, which made me use some workarounds to get it +working 100%. -- 2- What new skills will you need to acquire? +#### What does it do? -For this one, mainly how cryptography works on message exchanging. +Here's what it does: -- 2.1 - What topics will you need to research? +1. You register your account with just a Username and Password - no email or obvious identification needed +2. You share your SUUID with another user who then requests consent to start a chat +3. Once a chat starts, you can send messages to that user, following this flow: + - You send a message + - It gets encrypted using RSA-OAEP with SHA-256 and then encoded in Base64 format + - It's sent to the server, stored in the database, and then triggers Supabase's Realtime to update both chats in + real-time + - Rinse & Repeat -I'll also have to research about the recommended cases on how to store or handle each user. +That's the basic functionality of the app - just encrypting messages and sending them to a server that eventually stores +them and uses a websocket connection (not really sure if it's a websocket, but through debugging, I noticed that at +least in development, it uses websocket). Nothing special or functionality that would make the app really secure or +ideal for real use. -- 3- If working with one or two classmates, who will do what? +--- -Will do by myself. +#### Design Choices -- 4 - In the world of software, most everything takes longer to implement than you expect. And so itโ€™s not uncommon to accomplish less in a fixed amount of time than you hope. What might you consider to be a good outcome for your project? A better outcome? The best outcome? +#### Tech Stack -The best outcome for this would be an app that could at least: -Log in/Register the user -Let the user choose its encryption method -Let the user change his password to a maximum of a 12-letter word \ No newline at end of file +For the tech stack, I decided to use: + +- NextJs - Makes my life easier since Vercel can host it in a free plan +- Supabase - Has the Realtime feature, in which Vercel + +And that's it, really. I only used those two to create this app. Along with obviously WebApis that are supported in +browsers. + +If curious, though, I use IntelliJ products to code because I like their products. + +#### Front-End + +I had a lot of trouble with the design, mainly because I wanted the app to be pretty, minimalist, and work well enough. + +For the front-end design, I'll admit I used Claude (Anthropic) to make better decisions about the app, such as styling +issues (Mainly trying to make it mobile compatible). +Even though I used AI for help, I had in mind what I wanted: Similar to WhatsApp. With an empty margin and +the app UI smaller than the total browser screen. This really helped make the design cleaner, for some reason. + +I also decided that, in the main design, I wanted to use a more striking color with a deeper color - in this case, +orange and black had a great contrast. + +I did use ShadCn to make my life easier since it's a really good library for better development on the front-end. I also +considered using bootstrap or other libraries such as MaterialUi, but ShadCn had the easiest setup, was more +minimalistic +and I could control the components in a better way. + +#### Back-End + +The back-end design was a bit easier to do, thanks to how easy Supabase and NextJS API routes are to use, so there +wasn't much debate about this specific part. Even though I had many problems, mainly with RLS policies in Supabase, due +to pure lack of experience with it. For a better experience, I also used Supabase's own AI to help debug scripts, drop +functions, and request the best approach method for this project. + +I debated myself a lot when making the SQL scripts, though. They changed way too much and probably this has a weird DB +structure. First I had in mind that each thread should be "indexable" (meaning, if the thread could be searched or not +for joining), then I changed it to each user being indexable or not (meaning a user could search for another using by +either using that user's SUUID or username) and I went with that.
+Then I had to change the message structure due to forgetting that each message sent should be encrypted for the current +user too, else that user wouldn't be able to read what he sent to that user due to that message being encrypted only +with +the public key of the receiver end. With that, I also had to change the thread structures, making them separate in 3 +tables: + +- "message_threads" - The main table +- "thread_participants" - Holds the participants in each thread by indexing the thread id and + user id +- "messages" - Holds the messages for both the user that sent them (By encrypting that message with the user's own + public + key for access) and the receiver. The front-end can differenciate between the sender/receiver by using the key " + sender_uuid" + and comparing the logged user's uuid with that key. Each message is indexed to the thread_id for retrieval + +The main issue I did run into was: Supabase does not support username-only login.
+So I had to improvise. I have a few domains that I bought some years ago and set the app to use that domain as a false +e-mail: + +```typescript +const domain = process.env.DOMAIN; + +if (!domain) { + return NextResponse.json({ + error: "Server is misconfigured, please check env variables and try again." + }, + { + status: 500 + }) +} else if (!username || !password || !public_key) { + return NextResponse.json({ + error: "Missing params" + }, {status: 400}) +} + +// First create the auth user +const {data: {user}, error: authError} = await supabase.auth.signUp({ + email: `${username}@${domain}`, // Using username as email + password: password, +}) +``` + +This function represents the register, but the login-flow also works in a similar way, you can check +its [script](./src/app/api/auth/login/route.ts) too. + +Is this a breach on their policy? Well, I don't think it is... At least I hope it isn't. + +But this works when setting a username-only login without having too much trouble. + +Also, here's a cool badge: + +[![wakatime](https://wakatime.com/badge/user/e0979afa-f854-452d-b8a8-56f9d69eaa3b/project/eea66021-88c7-4467-8434-937fabc8149a.svg)](https://wakatime.com/badge/user/e0979afa-f854-452d-b8a8-56f9d69eaa3b/project/eea66021-88c7-4467-8434-937fabc8149a) + +--- + +##### Team MVPs + +By team MVPs, I mean the functions that took the most work and time to get done and finished to a state where they +worked well enough (as far as I could test). + +1. [CryptoManager](./src/lib/crypto/keys.ts) + + This function really gave me A LOT of headaches, seriously, A LOT of headaches. + + Starting with how the encryption would work, I first thought of something like PGP, but it would be VERY long and + possibly conflict with Supabase when storing it since I didn't know how it would handle a very long context. I admit + I asked Claude for help to decide the best method for this situation, and I still feel it's not as secure as I + wanted, but it works perfectly and isn't too complex. + + Another important point that I decided on design-wise is that both users would need to have the same message + encrypted 2x. One from who sent it using their own public key (So that user can read their own message) and one for + who will receive it using that user's public key (So they can also read the received message). + + Here are the key functions with detailed explanations: +

+ `static async generateUserKeys(): Promise`: + Generates a private and public key when called +

+ `static async storePrivateKey(privateKey: CryptoKey): Promise`: + Stores the private key in the "IndexedDB" database +

+ `static async deletePrivateKey(): Promise`: + Deletes the previously recorded private key. If there isn't one, returns an error. +

+ `static async getPrivateKey(): Promise`: + Returns the user's current key for message decryption. Returns "null" if there isn't a key +

+ `static async prepareAndSendMessage(message: string, senderPublicKey: JsonWebKey, recipientPublicKey: JsonWebKey, threadId: string): Promise`: + Prepares the message for both users using the "encryptMessage" method, and then sends it to the " + /api/user/send/message" API that invokes the SQL function in Supabase +

+ `static async decryptThreadMessages(messages: any[], userUuid: string): Promise`: + Receives an array of messages (from Supabase's API) and decrypts both the sent and received messages using the + current user's private key. For messages that the user themselves sent, decryption is also done using the current + user's private key, since it was encrypted for both sender and recipient. +

+ `static async encryptMessage(message: string, recipientPublicKey: JsonWebKey): Promise`: + Encrypts a message, returning a base64 encoded string after being encrypted using RSA-OAEP +

+ `static async exportPrivateKey(filename: string = 'private-key-backup'): Promise<{ text: string, file: File } | null>`: + Helper function to facilitate the backup of the current private key +

+ `static async validateKeyPair(privateKeyJwk: JsonWebKey, publicKeyJwk: JsonWebKey): Promise`: + Validates the current private key with the public key stored in the database by encrypting a message with a + timestamp, then trying to decrypt it afterward. Returns a boolean in both cases. +

+ `static async restoreFromBackup(privateKeyJwk: JsonWebKey, publicKeyJwk: JsonWebKey): Promise`: + Helper function to restore a backup. Not currently being used. +

+ `private static async openDB(): Promise`: + Private function to open the database connection. + + 2. [SQL Functions](./supabase/sql_snippets) + + Seriously, the amount of trouble I had with SQL functions is unreal... Not just functions, but also RLS policies, + realtime permissions, etc. I had to ask for help from Supabase AI (and a bit from Claude, since honestly, + Supabase's doesn't give as much explanation for corrections and other stuff). + + The main functions are: + + ```sql + CREATE OR REPLACE FUNCTION public.create_private_thread(participant_suuid TEXT) RETURNS UUID + ``` + Creates a private thread by getting the current user suuid (current_user_suuid) and the target user, checks if + there's already a thread with those 2 participants and creates one if there isn't or returns an existing thread + id + + ```sql + CREATE OR REPLACE FUNCTION public.get_thread(thread_uuid UUID, user_id UUID) + ``` + Retrieves a thread using its uuid along with the user_id. If found, returns the thread information (thread_id, + participants, participants_suuids, messages). If the thread doesn't exist, returns an empty value. + + ```sql + CREATE OR REPLACE FUNCTION public.get_user_threads(user_id UUID) + ``` + Retrieves a user's threads using their own uuid, returning an array of existing threads + + ```sql + CREATE OR REPLACE FUNCTION public.send_message( + thread_uuid UUID, + sender_content TEXT, + recipient_content TEXT + ) RETURNS UUID + ``` + Inserts both users' messages into the database, both encrypted with their respective keys + + It's totally possible I forgot some functions or that others were deleted during development, so I included all + the functions made, along with RLS policies and triggers. + Some functions weren't mentioned because they weren't as problematic to make. There is also a high possibility of + this app being really insecure since I am not too familiar with SQL (I always preferred NoSQL dbs.) + + I will not document each page since I don't think it's necessary and that would make this README too long and + cluttered. + +I did re-use code of previous projects as inspiration. Mainly the middleware and some other styling (Such as the +Sidebar). + +I did not mention any API because the API routes mainly use supabase's functions to work, so I do not think it is +necessary to mention them here. + +--- + +For clarification, I did use AI to help me on this project: + +- Claude - Helped with NextJs and React debugging (I don't know how to read the errors on react, sometimes it just + outputs a simple message without explicit details on where the error happened), helping on some SQL functions too ( + Mainly RLS issues on realtime). Also helped when I couldn't really fix the style of some components. +- Supabase's AI - I don't think it helped that much since, honestly, I don't think it's quite good at the purpose it was + made to serve. Might be a skill issue on my part though. It helped mainly in debugging of some scripts that weren't + working properly, since Supabase does not really support logs (at least, I never found where to look at) + +You can check it out by using this link: https://sipher.space \ No newline at end of file diff --git a/src/app/[id]/page.tsx b/src/app/[id]/page.tsx index 1f683b8..2f249ca 100644 --- a/src/app/[id]/page.tsx +++ b/src/app/[id]/page.tsx @@ -46,9 +46,9 @@ import {useSharedState} from "@/hooks/shared-states"; import {createBrowserClient} from '@/lib/supabase/browser' import {CryptoManager} from "@/lib/crypto/keys"; import {REALTIME_SUBSCRIBE_STATES} from "@supabase/realtime-js"; +import ChatSkeleton from "@/app/[id]/skeleton"; export default function ChatPage() { - const {theme} = useTheme(); const {toast} = useToast(); const supabase = createBrowserClient(); @@ -59,7 +59,7 @@ export default function ChatPage() { const [showUserDialog, setShowUserDialog] = useState(false); const [isEncrypted, setIsEncrypted] = useState(true); - const [realtimeSubscribed, setRealtimeSubscribed] = useState() + const [realtimeSubscribed, setRealtimeSubscribed] = useState(REALTIME_SUBSCRIBE_STATES.CLOSED); const [isLoaded, setIsLoaded] = useState(false); @@ -115,13 +115,14 @@ export default function ChatPage() { ) .subscribe((status) => { setRealtimeSubscribed(status) - console.log('Realtime subscription status:', status) + console.info(`Subscription for thread ${threadId} has the status "${status}"`) + console.info("If closed, something bad might be happening at the backend.") }) return () => { supabase.removeChannel(channel) } - }, [threadId]) + }, [threadId, currentUser.uuid, supabase]) useEffect(() => { const getUserDataAndChat = async () => { @@ -157,17 +158,29 @@ export default function ChatPage() { setMessages([]) setIsLoaded(false) } - }, [setUser, setMessages, setIsLoaded, threads]) + }, [setUser, setMessages, setIsLoaded, threads, currentUser.suuid, currentUser.uuid, getUser, threadId, toast]) // Damn, quite a lot of dependencies, but lint said I should add it so.... + + useEffect(() => { + if (!realtimeSubscribed) return; + + const timeoutId = setTimeout(() => { + if (realtimeSubscribed === 'TIMED_OUT' || realtimeSubscribed === 'CLOSED') { + toast({ + title: "Connection Issue", + description: "You might need to restart your browser due to connection issues.", + variant: "destructive", + duration: 10000, + }); + } + }, 10000); + + return () => clearTimeout(timeoutId); + }, [realtimeSubscribed, toast]); if (!isLoaded || !user || realtimeSubscribed !== "SUBSCRIBED") { - return ( - <> - a - - ) + return ; } - // Mock functions - replace with actual implementations const checkUserValidity = async () => { // Implementation for checking user validity setShowUserDialog(true); @@ -193,12 +206,10 @@ export default function ChatPage() { user.public_key, threadId ) - }; return (
- {/* Chat Header */}
@@ -279,7 +290,6 @@ export default function ChatPage() {
- {/* Chat Messages */}
@@ -312,7 +322,6 @@ export default function ChatPage() {
- {/* Input Area */}
- {/* Dialogs */} diff --git a/src/app/[id]/skeleton.tsx b/src/app/[id]/skeleton.tsx new file mode 100644 index 0000000..ff18e85 --- /dev/null +++ b/src/app/[id]/skeleton.tsx @@ -0,0 +1,50 @@ +import {Skeleton} from "@/components/ui/skeleton"; +import {ScrollArea} from "@/components/ui/scroll-area"; + +export default function ChatSkeleton() { + return ( +
+ {/* Header Skeleton */} +
+
+ + +
+
+ + +
+
+ + {/* Messages Skeleton */} + +
+ {/* Left message */} +
+ +
+ {/* Right message */} +
+ +
+ {/* Left message */} +
+ +
+ {/* Right message */} +
+ +
+
+
+ + {/* Input Area Skeleton */} +
+
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx new file mode 100644 index 0000000..015ee0d --- /dev/null +++ b/src/app/about/page.tsx @@ -0,0 +1,234 @@ +"use client" +import {motion} from "framer-motion"; +import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card"; +import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert"; +import {Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"; +import {Separator} from "@/components/ui/separator"; +import {AlertTriangle, KeyRound, Lock, MessageSquare, Shield, UserCheck,} from "lucide-react"; + +export default function AboutPage() { + const containerVariants = { + hidden: {opacity: 0}, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1 + } + } + }; + + const itemVariants = { + hidden: {opacity: 0, y: 20}, + visible: {opacity: 1, y: 0} + }; + + return ( + + +

About SiPher

+

+ Where privacy meets simplicity in secure communication +

+
+ + + + + + + Important Notice + + SiPher is a CS50X final project and is not intended for production use. + While we implement strong encryption, please do not use it for sensitive communications. + + + + + + + + How SiPher Works + + Understanding the security behind your messages + + + +
+
+ +
+

Key Generation

+

+ Each user has a unique public-private key pair generated in their browser. Lost it and didn't + make a + backup? Welp, skill issue I guess. +

+
+
+ +
+ +
+

End-to-End Encryption

+

+ Messages are encrypted before leaving your device +

+
+
+ +
+ +
+

Zero (And A Half) Trust

+

+ Server never sees your decrypted messages. But we do store their encrypted version though lmao. +

+
+
+ +
+ +
+

User Privacy

+

+ Users are identified by unique IDs, not personal information. No e-mail, no nothing, only your ID + (and probably IP due to Supabase logging it) +

+
+
+
+
+
+
+ + + + + Technical Details + + The technology powering SiPher's "security" + + + +
+

Encryption

+
    +
  • RSA-OAEP for key exchange
  • +
  • AES-GCM for message encryption
  • +
  • PBKDF2 for key derivation
  • +
  • SHA-256 for message integrity
  • +
+
+ +
+

Implementation

+
    +
  • Web Crypto API for cryptographic operations
  • +
  • Next.js for the application framework
  • +
  • Supabase for real-time messaging
  • +
  • TailwindCSS and ShadcnUI for the interface (I suck at design)
  • +
+
+
+
+
+ + + + + Frequently Asked Questions + + + + + How secure are my messages? + + Messages are encrypted using industry-standard algorithms and never stored in plaintext. + However, as this is an educational project, I recommend not using it for sensitive communications. + If you do and I get a notice, I will give out the data I have on you. I don't care. + + + + + What happens if I lose my private key? + + If you lose your private key, you won't be able to decrypt previous messages. + You can generate a new key pair, but you'll need to start fresh conversations, previous messages + from + other conversations will be lost forever. + Always backup your private key in the settings. + + + + + Can I recover deleted messages? + + You can't even delete chats, imagine messages lmao. + + + + + How do I verify a user's identity? + + Each user has a unique SUUID (Short UUID) that can be shared and verified. + You can verify a user's identity by comparing their SUUID in a secure channel. + + + + + Is SiPher open source? + + Not yet. As this is a CS50X final project, the code will be made available + for educational purposes in the future. + + + + + Will you continue this project after submitting it? + + Probably. It's quite fun dealing with encryption. + + + + + + + + + + + Message Flow + + How your message travels from you to the other user + + + +
+
+ +
+
+
+
+ +
+
+

+ Messages are encrypted on your device before being sent through our servers, + ensuring end-to-end encryption for all communications. +

+ + + + + +

Built with ๐Ÿ’– as a CS50X final project

+
+ + ); +} \ No newline at end of file diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts index 4c554ab..37af265 100644 --- a/src/app/api/auth/register/route.ts +++ b/src/app/api/auth/register/route.ts @@ -2,7 +2,7 @@ import {NextResponse} from 'next/server' import {createClient} from "@/lib/supabase/server"; export async function POST(request: Request) { - const {username, password} = await request.json() + const {username, password, public_key} = await request.json() const supabase = await createClient() try { @@ -15,6 +15,10 @@ export async function POST(request: Request) { { status: 500 }) + } else if (!username || !password || !public_key) { + return NextResponse.json({ + error: "Missing params" + }, {status: 400}) } // First create the auth user @@ -32,6 +36,7 @@ export async function POST(request: Request) { .insert({ uuid: user.id, username: username, + public_key }) if (insertError) { diff --git a/src/app/api/user/send/update/key/route.ts b/src/app/api/user/send/update/key/route.ts index 5b38a5c..f59e220 100644 --- a/src/app/api/user/send/update/key/route.ts +++ b/src/app/api/user/send/update/key/route.ts @@ -1,4 +1,3 @@ -// app/api/user/keys/update/route.ts import {createClient} from "@/lib/supabase/server"; import {NextResponse} from "next/server"; diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 4fabf1f..afa6049 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -39,11 +39,19 @@ export default function AuthPage() { check().then(() => { console.log("Login page check finished") }) - }, []); + }, [check]); if (!mounted) { return
- {/* Optional: Add a loading spinner or skeleton here */} +
; } diff --git a/src/app/auth/login/register.ts b/src/app/auth/login/register.ts index b1c395e..e9aa465 100644 --- a/src/app/auth/login/register.ts +++ b/src/app/auth/login/register.ts @@ -20,7 +20,7 @@ export default async function Register(username: string, password: string) { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({username, password, publicKey: exportedPublic}), // Stringifies the JSON + body: JSON.stringify({username, password, public_key: exportedPublic}), // Stringifies the JSON }); // Default error handler, if not OK just return whatever the API returned diff --git a/src/app/layout.tsx b/src/app/layout.tsx index fbeac88..ad055f3 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -54,11 +54,13 @@ export default async function RootLayout( -
-
- - {children} - +
+
+
+ + {children} + +
diff --git a/src/app/page.tsx b/src/app/page.tsx index 54f9e6e..7834722 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -26,7 +26,7 @@ export default function SiPher() { /** CryptoManager Alert */ const [privateKeyPresent, setPrivateKeyPresent] = useState(true); - const [backupPanel, setBackupPanel] = useState(false); + const [backupPanel, setBackupPanel] = useState(false); // I still need to do this, but... ugh. /** Consent Form states */ const [showConsentForm, setShowConsentForm] = useState(false); @@ -206,7 +206,7 @@ export default function SiPher() {
+ className={`relative flex-1 ${currentTheme === "dark" ? "dark" : ""} w-full bg-gradient-to-b from-background to-background/95`}>
F.A.Q

- + How does this works? diff --git a/src/components/main/realtime/threads.tsx b/src/components/main/realtime/index.tsx similarity index 84% rename from src/components/main/realtime/threads.tsx rename to src/components/main/realtime/index.tsx index 5834b5f..ae11924 100644 --- a/src/components/main/realtime/threads.tsx +++ b/src/components/main/realtime/index.tsx @@ -1,31 +1,27 @@ // hooks/useRealtime.ts -import {Dispatch, SetStateAction, useEffect} from 'react' +import {Dispatch, SetStateAction, useCallback, useEffect} from 'react' import {createBrowserClient} from '@/lib/supabase/browser' import {useUser} from '@/contexts/user' -import {useToast} from '@/hooks/use-toast' interface UseRealtimeProps { setThreads: Dispatch>; - threads: SiPher.Thread[] } -export function useRealtime({setThreads, threads}: UseRealtimeProps) { +export function useRealtime({setThreads}: UseRealtimeProps) { const supabase = createBrowserClient(); const {user, updateUser} = useUser(); - const {toast} = useToast(); - const fetchAndUpdateThreads = async () => { + const fetchAndUpdateThreads = useCallback(async () => { try { const response = await fetch("/api/user/get/threads"); if (response.ok) { const {threads} = await response.json(); - console.log('Setting threads:', threads); setThreads(threads); } } catch (error) { console.error('Error fetching threads:', error); } - }; + }, [setThreads]) useEffect(() => { if (!user) return; @@ -77,5 +73,5 @@ export function useRealtime({setThreads, threads}: UseRealtimeProps) { userUpdate.unsubscribe() } - }, [user?.uuid]); + }, [user?.uuid, fetchAndUpdateThreads, supabase, updateUser, user]); } \ No newline at end of file diff --git a/src/components/main/realtime/request.tsx b/src/components/main/realtime/request.tsx deleted file mode 100644 index a6fa683..0000000 --- a/src/components/main/realtime/request.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// components/RealtimeRequests.tsx -'use client' - -import {Dispatch, SetStateAction, useEffect} from 'react' -import {useToast} from "@/hooks/use-toast" -import {useUser} from "@/contexts/user" -import {createBrowserClient} from "@/lib/supabase/browser"; - -interface RealtimeRequests { - setRequests: Dispatch> -} - -export function RealtimeRequests( - { - setRequests, - }: RealtimeRequests -) { - const {toast} = useToast() - const {user, updateUser} = useUser() - - useEffect(() => { - if (!user) return - - createBrowserClient().channel("realtime requests").on("postgres_changes", { - event: 'UPDATE', - schema: 'public', - table: 'users', - filter: `uuid=eq.${user.uuid}`, - }, async (payload) => { - console.log(payload) - if (payload.new.requests !== payload.old.requests) { - try { - setRequests(payload.new.requests) - } catch (error) { - console.error('Error writing to stream:', error) - } - } - }).subscribe() - }, []) - - return null -} \ No newline at end of file diff --git a/src/components/main/sidebar/rightsidebar.tsx b/src/components/main/sidebar/rightsidebar.tsx index e4da858..75a3851 100644 --- a/src/components/main/sidebar/rightsidebar.tsx +++ b/src/components/main/sidebar/rightsidebar.tsx @@ -8,7 +8,7 @@ import {Check, LogOut, Mail, MailPlus, X} from "lucide-react"; import {Button} from "@/components/ui/button"; import {GearIcon} from "@radix-ui/react-icons"; import Link from "next/link"; -import {useRealtime} from "@/components/main/realtime/threads"; +import {useRealtime} from "@/components/main/realtime"; import {useUser} from "@/contexts/user"; import {usePathname} from "next/navigation"; import {useSharedState} from "@/hooks/shared-states"; @@ -25,7 +25,7 @@ export default function RightSidebarContent( const [copied, setCopied] = useState(false); const {threads, setThreads} = useSharedState(); - useRealtime({setThreads, threads}); + useRealtime({setThreads}); const {user} = useUser(); const {username, suuid, requests = []} = user; @@ -46,7 +46,7 @@ export default function RightSidebarContent( console.log(error); setThreads([]) } - }, []); + }, [setThreads]); useEffect(() => { fetchThreads(); @@ -92,7 +92,7 @@ export default function RightSidebarContent(
- +