Skip to content

Commit 66fccc2

Browse files
committed
zupass gating and some app state
1 parent c041c96 commit 66fccc2

12 files changed

Lines changed: 115 additions & 118 deletions

File tree

devconnect-app/src/app/api/auth/middleware.ts

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { NextRequest, NextResponse } from 'next/server'
2-
import { createClient, type User } from '@supabase/supabase-js'
3-
import { jwtVerify, createRemoteJWKSet } from 'jose'
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { createClient, type User } from '@supabase/supabase-js';
3+
import { jwtVerify, createRemoteJWKSet } from 'jose';
44

5-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL
6-
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY
5+
const supabaseUrl =
6+
process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL;
7+
const supabaseAnonKey =
8+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY;
79

810
// Define Para JWKS URLs based on environment
911
const PARA_JWKS_URLS = {
@@ -43,11 +45,11 @@ interface ParaJwtPayload {
4345

4446
export type AuthResult =
4547
| { success: true; user: User }
46-
| { success: false; error: NextResponse }
48+
| { success: false; error: NextResponse };
4749

4850
export type AuthResultWithHeaders =
4951
| { success: true; user: User }
50-
| { success: false; error: string }
52+
| { success: false; error: string };
5153

5254
// Core verification logic that works with any headers-like object
5355
async function verifyAuthCore(
@@ -58,26 +60,27 @@ async function verifyAuthCore(
5860
if (!supabaseUrl || !supabaseAnonKey) {
5961
return {
6062
success: false,
61-
error: 'Supabase configuration missing'
62-
}
63+
error: 'Supabase configuration missing',
64+
};
6365
}
6466

65-
const supabase = createClient(supabaseUrl, supabaseAnonKey)
67+
const supabase = createClient(supabaseUrl, supabaseAnonKey);
6668

6769
if (!authHeader || !authHeader.startsWith('Bearer ')) {
6870
return {
6971
success: false,
70-
error: 'Authorization header required'
71-
}
72+
error: 'Authorization header required',
73+
};
7274
}
7375

74-
const token = authHeader.replace('Bearer ', '')
76+
const token = authHeader.replace('Bearer ', '');
7577

7678
if (authMethod === 'para') {
7779
// Verify as Para JWT
7880
try {
7981
// Get JWKS URL based on environment
80-
const env = (process.env.PARA_ENVIRONMENT || 'prod') as keyof typeof PARA_JWKS_URLS;
82+
const env = (process.env.PARA_ENVIRONMENT ||
83+
'prod') as keyof typeof PARA_JWKS_URLS;
8184
const jwksUrl = PARA_JWKS_URLS[env];
8285
const JWKS = createRemoteJWKSet(new URL(jwksUrl));
8386

@@ -87,7 +90,8 @@ async function verifyAuthCore(
8790
});
8891

8992
// If Para JWT verification succeeds, create a mock user object
90-
const email = payload.data.email || `${payload.data.userId}@para-fallback.com`;
93+
const email =
94+
payload.data.email || `${payload.data.userId}@para-fallback.com`;
9195
const paraUser: User = {
9296
id: payload.data.userId,
9397
email,
@@ -107,60 +111,69 @@ async function verifyAuthCore(
107111

108112
return {
109113
success: true,
110-
user: paraUser
114+
user: paraUser,
111115
};
112116
} catch (paraError) {
113117
console.log('Para JWT verification failed:', paraError);
114118
return {
115119
success: false,
116-
error: 'Invalid or expired Para JWT token'
117-
}
120+
error: 'Invalid or expired Para JWT token',
121+
};
118122
}
119123
} else {
120124
// Verify as Supabase JWT
121-
const { data: { user }, error: authError } = await supabase.auth.getUser(token)
125+
const {
126+
data: { user },
127+
error: authError,
128+
} = await supabase.auth.getUser(token);
122129

123130
if (authError || !user) {
124131
return {
125132
success: false,
126-
error: 'Invalid or expired Supabase token'
127-
}
133+
error: 'Invalid or expired Supabase token',
134+
};
128135
}
129136

130137
console.log('Supabase user:', user);
131138

132139
return {
133140
success: true,
134-
user
135-
}
141+
user,
142+
};
136143
}
137144
}
138145

139146
// For use with NextRequest (API routes, middleware)
140147
export async function verifyAuth(request: NextRequest): Promise<AuthResult> {
141-
const authHeader = request.headers.get('authorization')
142-
const authMethod = request.headers.get('x-auth-method')
148+
const authHeader = request.headers.get('authorization');
149+
const authMethod = request.headers.get('x-auth-method');
143150

144-
const result = await verifyAuthCore(authHeader, authMethod)
151+
const result = await verifyAuthCore(authHeader, authMethod);
145152

146153
if (!result.success) {
147154
return {
148155
success: false,
149-
error: NextResponse.json({ error: result.error }, {
150-
status: result.error.includes('configuration') ? 500 : 401
151-
})
152-
}
156+
error: NextResponse.json(
157+
{ error: result.error },
158+
{
159+
status: result.error.includes('configuration') ? 500 : 401,
160+
}
161+
),
162+
};
153163
}
154164

155-
return result
165+
return result;
156166
}
157167

158168
// For use with Headers from Server Components (layouts, pages)
159169
export async function verifyAuthWithHeaders(
160170
headers: Headers
161171
): Promise<AuthResultWithHeaders> {
162-
const authHeader = headers.get('authorization')
163-
const authMethod = headers.get('x-auth-method')
172+
const authHeader = headers.get('authorization');
173+
const authMethod = headers.get('x-auth-method');
174+
175+
console.log(authHeader, 'authHeader');
176+
console.log(authMethod, 'authMethod');
164177

165-
return verifyAuthCore(authHeader, authMethod)
178+
return verifyAuthCore(authHeader, authMethod);
166179
}

devconnect-app/src/app/layout.tsx

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { Toaster } from 'sonner';
77
import { WalletsProviders } from '@/context/WalletProviders';
88
import PWAProvider from '@/components/PWAProvider';
99
import { GlobalStoreProvider } from '@/app/store.provider';
10-
import { verifyAuthWithHeaders } from '@/app/api/auth/middleware';
1110
import { getAtprotoEvents } from '@/utils/atproto-events';
11+
import { unstable_cache } from 'next/cache';
12+
import { verifyAuthWithHeaders } from '@/app/api/auth/middleware';
1213
import { headers } from 'next/headers';
1314
import { ensureUser } from '@/app/api/auth/user-data/ensure-user';
1415

@@ -90,6 +91,8 @@ export async function generateMetadata(): Promise<Metadata> {
9091
};
9192
}
9293

94+
export const revalidate = 300; // 5 minutes
95+
9396
export default async function RootLayout({
9497
children,
9598
}: Readonly<{
@@ -104,24 +107,39 @@ export default async function RootLayout({
104107
// Check if Supabase is configured
105108
// const hasSupabase = !!process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
106109

107-
const headersList = await headers();
108-
const authResult = await verifyAuthWithHeaders(headersList as Headers);
109-
let userData = null;
110-
if (authResult.success) {
111-
userData = await ensureUser(authResult.user?.email || '');
112-
}
113-
const atprotoEvents = await getAtprotoEvents();
110+
// This does not work on the server side because the relevant headers are not available
111+
// We could use cookies instead so its autoincluded?
112+
// const headersList = await headers();
113+
// const authResult = await verifyAuthWithHeaders(headersList as Headers);
114+
115+
// let userData = null;
116+
117+
// if (authResult.success) {
118+
// userData = await ensureUser(authResult.user?.email || '');
119+
// }
120+
121+
// Cache the atproto events for 5 minutes - wrapped in unstable_cache to avoid re-fetching the events on every request
122+
const atprotoEvents = await unstable_cache(
123+
getAtprotoEvents,
124+
['atproto-events'],
125+
{
126+
revalidate: 300,
127+
}
128+
)();
129+
130+
// console.log(atprotoEvents, 'atprotoEvents');
131+
// console.log(userData, 'userData');
114132

115133
return (
116134
<html lang="en">
117135
<head>
118136
{/* Progressive Web App */}
119-
<link
137+
{/* <link
120138
rel="manifest"
121139
href="/manifest.json"
122140
crossOrigin="use-credentials"
123-
/>
124-
<link rel="apple-touch-icon" href="/app-icon.png" />
141+
/> */}
142+
{/* <link rel="apple-touch-icon" href="/app-icon.png" /> */}
125143

126144
{/* Meta tags now handled by generateMetadata function above */}
127145
{/*
@@ -177,7 +195,7 @@ export default async function RootLayout({
177195
>
178196
<PWAProvider>
179197
<WalletsProviders>
180-
<GlobalStoreProvider events={atprotoEvents} userData={userData}>
198+
<GlobalStoreProvider events={atprotoEvents} /*userData={userData}*/>
181199
{children}
182200
</GlobalStoreProvider>
183201
<NewDeployment />

devconnect-app/src/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default function HomePageContent() {
2424

2525
return (
2626
<PageLayout title="Ethereum World's Fair" tabs={homeTabs()}>
27-
<div className="bg-[rgba(246,250,254,1)] grow">
27+
<div className="bg-[rgba(246,250,254,1)] grow pb-8">
2828
<LoopingHeader />
2929
<WelcomeSection />
3030
<TodaysSchedule />

devconnect-app/src/app/store.hooks.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ export const useFavorites = () => {
1313
const userData = useGlobalStore(useShallow((state) => state.userData));
1414
const favorites =
1515
useGlobalStore((state) => {
16-
console.log('SELECTOR RUNNING, state:', state);
17-
1816
return state.userData?.favorite_events;
1917
}) || [];
2018
const setFavoriteEvents = useGlobalStore((state) => state.setFavoriteEvents);
@@ -79,6 +77,7 @@ export const useEnsureUserData = (isConnected: boolean) => {
7977

8078
useEffect(() => {
8179
if (isConnected) {
80+
console.log('ensuring user data');
8281
ensureUserData(setUserData);
8382
} else {
8483
setUserData(null);

devconnect-app/src/app/store.provider.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
createRef,
1010
} from 'react';
1111
import { useStore } from 'zustand';
12-
1312
import { useWalletManager } from '@/hooks/useWalletManager';
1413

1514
import {
@@ -18,6 +17,7 @@ import {
1817
createGlobalStore,
1918
initGlobalStore,
2019
} from '@/app/store';
20+
// import { ensureUserData } from './store.hooks';
2121

2222
export const GlobalStoreContext = createContext<AppStore | undefined>(
2323
undefined
@@ -26,40 +26,40 @@ export const GlobalStoreContext = createContext<AppStore | undefined>(
2626
export interface GlobalStoreProviderProps {
2727
children: ReactNode;
2828
events: AppState['events'];
29-
userData: AppState['userData'];
29+
userData?: AppState['userData'];
3030
}
3131

32-
const WalletProvider = ({ children }: { children: ReactNode }) => {
32+
const AuthProvider = ({ children }: { children: ReactNode }) => {
3333
useWalletManager();
3434

3535
return children;
3636
};
3737

38-
// const storeRef = createRef<AppStore | null>();
39-
4038
export const GlobalStoreProvider = ({
4139
events,
4240
userData,
4341
children,
4442
}: GlobalStoreProviderProps) => {
43+
// useWalletManager();
4544
const storeRef = useRef<AppStore | null>(null);
4645

4746
if (storeRef.current === null) {
48-
console.log('creating store');
4947
storeRef.current = createGlobalStore(initGlobalStore(events, userData));
50-
} else {
51-
console.log('store already created');
5248
}
5349

54-
useStore(storeRef.current, (state) => console.log('STATE', state));
50+
// useEffect(() => {
51+
// if (!storeRef.current) return;
5552

56-
useEffect(() => {
57-
console.log('MOUNTED THE PROVIDER, SHOULD RUN JUST ONCE');
58-
}, []);
53+
// if (email) {
54+
// ensureUserData(storeRef.current?.getState().setUserData);
55+
// } else {
56+
// storeRef.current?.getState().setUserData(null);
57+
// }
58+
// }, [email]);
5959

6060
return (
6161
<GlobalStoreContext.Provider value={storeRef.current}>
62-
<WalletProvider>{children}</WalletProvider>
62+
<AuthProvider>{children}</AuthProvider>
6363
</GlobalStoreContext.Provider>
6464
);
6565
};

devconnect-app/src/app/store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { createStore } from 'zustand';
44

55
export interface AppState {
6+
initializing: boolean;
67
// User data from supabase (so basically data attached to the logged in email)
78
userData: {
89
additional_ticket_emails?: string[];
@@ -23,6 +24,7 @@ export const initGlobalStore = (
2324
): Omit<AppState, 'setUserData' | 'setFavoriteEvents' | 'logout'> => ({
2425
events: events,
2526
userData: userData || null,
27+
initializing: true,
2628
});
2729

2830
export const createGlobalStore = (

0 commit comments

Comments
 (0)