Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove connect wallet button and improve UX #147

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open

Conversation

biwasbhandari
Copy link
Contributor

  • The sidebar is shown directly.
  • If the user is not logged in, the sidebar footer is not displayed.
  • When the user tries to access a protected route, the middleware redirects them to the /connect page, including the redirect route.
  • The useEffect automatically displays the Xverse wallet popup and allows the user to connect their wallet.
  • After connecting, the user is redirected to the route they initially requested.

@whoabuddy
Copy link
Contributor

On first pass I see some bugs if I click different menu items without completing the action on the connect page.

My original thought was something simpler at the component level, e.g.

  • if not authenticated, load a component with sign in action
  • if authenticated, load the component for the route

Given we have the middleware though maybe there's a way we can better use it to achieve something similar. It's technically a better/more secure way to do it anyway.

Have one untested idea:

Update Middleware

This would modify middleware.ts so that it can define the protected paths, test for a match, then handle some more advanced authentication logic using headers. This code wouldn't be a drop-in replacement as-is but provides something that should work with what we have now and what's coming next with the new database/services.

import { type NextRequest, NextResponse } from "next/server";                                                                                                                                                                                
 import { updateSession } from "@/utils/supabase/middleware";                                                                                                                                                                                 
                                                                                                                                                                                                                                              
 // Define routes that require authentication                                                                                                                                                                                                 
 const protectedPaths = {                                                                                                                                                                                                                     
   // These will return a special header that components can check                                                                                                                                                                            
   '/dashboard': { type: 'component' },                                                                                                                                                                                                       
   '/dashboard/:path*': { type: 'component' },                                                                                                                                                                                                
                                                                                                                                                                                                                                              
   // These will still force redirect                                                                                                                                                                                                         
   '/admin': { type: 'redirect' },                                                                                                                                                                                                            
   '/admin/:path*': { type: 'redirect' },                                                                                                                                                                                                     
                                                                                                                                                                                                                                              
   // API routes return 401                                                                                                                                                                                                                   
   '/api/protected': { type: 'api' },                                                                                                                                                                                                         
   '/api/protected/:path*': { type: 'api' }                                                                                                                                                                                                   
 } as const;                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                              
 export async function middleware(request: NextRequest) {                                                                                                                                                                                     
   const session = await updateSession(request);                                                                                                                                                                                              
   const pathname = request.nextUrl.pathname;                                                                                                                                                                                                 
                                                                                                                                                                                                                                              
   // Check if this is a protected path                                                                                                                                                                                                       
   const matchedPath = Object.entries(protectedPaths).find(([route, _]) => {                                                                                                                                                                  
     const pattern = new RegExp(`^${route.replace(/\/:path\*/, '(/.*)?').replace(/\//g, '\\/')}$`);                                                                                                                                           
     return pattern.test(pathname);                                                                                                                                                                                                           
   });                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                              
   if (!matchedPath) {                                                                                                                                                                                                                        
     return session; // Not a protected route                                                                                                                                                                                                 
   }                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                              
   const [_, config] = matchedPath;                                                                                                                                                                                                           
   const isAuthenticated = session.headers.get('x-authenticated') === 'true';                                                                                                                                                                 
                                                                                                                                                                                                                                              
   if (!isAuthenticated) {                                                                                                                                                                                                                    
     switch (config.type) {                                                                                                                                                                                                                   
       case 'redirect':                                                                                                                                                                                                                       
         // Full redirect for admin routes                                                                                                                                                                                                    
         const redirectUrl = new URL('/login', request.url);                                                                                                                                                                                  
         redirectUrl.searchParams.set('from', pathname);                                                                                                                                                                                      
         return NextResponse.redirect(redirectUrl);                                                                                                                                                                                           
                                                                                                                                                                                                                                              
       case 'api':                                                                                                                                                                                                                            
         // API routes get 401                                                                                                                                                                                                                
         return NextResponse.json(                                                                                                                                                                                                            
           { error: 'Unauthorized' },                                                                                                                                                                                                         
           { status: 401 }                                                                                                                                                                                                                    
         );                                                                                                                                                                                                                                   
                                                                                                                                                                                                                                              
       case 'component':                                                                                                                                                                                                                      
         // Add auth status header but allow the route to render                                                                                                                                                                              
         const response = NextResponse.next();                                                                                                                                                                                                
         response.headers.set('x-auth-status', 'unauthorized');                                                                                                                                                                               
         return response;                                                                                                                                                                                                                     
     }                                                                                                                                                                                                                                        
   }                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                              
   // User is authenticated, add auth status header                                                                                                                                                                                           
   const response = NextResponse.next();                                                                                                                                                                                                      
   response.headers.set('x-auth-status', 'authorized');                                                                                                                                                                                       
   return response;                                                                                                                                                                                                                           
 }                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                              
 export const config = {                                                                                                                                                                                                                      
   matcher: [                                                                                                                                                                                                                                 
     "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",                                                                                                                                                     
     "/dashboard/:path*",                                                                                                                                                                                                                     
     "/admin/:path*",                                                                                                                                                                                                                         
     "/api/protected/:path*",                                                                                                                                                                                                                 
   ],                                                                                                                                                                                                                                         
 };

I'm not saying that's how we do it but might spark a better idea. I like how we have options for full redirect for something like admin, but can still load mixed content for paths that have some data.

Modify Layout

Another piece to the puzzle may be changing the layouts so the sidebar always renders and the component loaded depends on the auth status, e.g. what happens after the case: "component" bit loads.

Copy link

cloudflare-workers-and-pages bot commented Dec 12, 2024

Deploying aibtcdev-frontend with  Cloudflare Pages  Cloudflare Pages

Latest commit: b10db32
Status: ✅  Deploy successful!
Preview URL: https://1e449bbf.aibtcdev-frontend.pages.dev
Branch Preview URL: https://ui-fix.aibtcdev-frontend.pages.dev

View logs

@biwasbhandari
Copy link
Contributor Author

I think it aligns with your vision now

  • x-authenticated: Indicates overall authentication status
  • x-auth-status: Specifies authorization level
  • If the user is not authenticated it renders the component with limited access
  • if the user is not eligible it redirects to / route for example admin

@whoabuddy
Copy link
Contributor

whoabuddy commented Dec 12, 2024

Flow-wise this looks better when testing it, although the /admin route still displays a connect pop-up right away. The info that comes up to connect a wallet is very bland too. Would want to make this stylish like the rest of the site and thinking through a couple ideas on what to do with the root route.

Edit: code structure looks good, seems like a nice simple way to protect routes and the updated auth is a good start.

@biwasbhandari
Copy link
Contributor Author

  • Removed check admin status from admin page
  • Removed the access denied logic
  • Authentication is now entirely handled by the middleware

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants