From 9364d80e1b7be7e4c1bb2ea0713f71c7ecc8963f Mon Sep 17 00:00:00 2001 From: Roberto Milla Martinez Date: Sat, 9 Nov 2024 23:03:41 +0100 Subject: [PATCH 1/6] feat: add auth to address endpoint --- package-lock.json | 9 +++++++++ src/app/api/address/route.ts | 29 ++++++++++++++++++++++++++++- src/components/AddressMap.tsx | 14 ++++++++++++-- src/context/SessionProvider.tsx | 12 +++++++----- src/lib/service.ts | 5 +++-- 5 files changed, 59 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 30d7b4e6..70d175ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@supabase/ssr": "^0.5.1", "@supabase/supabase-js": "^2.46.1", "@tanstack/react-query": "^5.59.19", + "babel-plugin-react-compiler": "19.0.0-beta-63b359f-20241101", "deck.gl": "^9.0.34", "leaflet": "^1.9.4", "lucide-react": "^0.454.0", @@ -3497,6 +3498,14 @@ "npm": ">=6" } }, + "node_modules/babel-plugin-react-compiler": { + "version": "19.0.0-beta-63b359f-20241101", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.0.0-beta-63b359f-20241101.tgz", + "integrity": "sha512-qrmTHJP3O2kGbtL7kuySX3Lmk+5/4ZR1rHr8QhKa0GzKbmpAUGrTeWOg0NCI5t+QUfKKizxIO1+t0HsZW9x4vQ==", + "dependencies": { + "@babel/types": "^7.19.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "dev": true, diff --git a/src/app/api/address/route.ts b/src/app/api/address/route.ts index 43d17dc8..7a147481 100644 --- a/src/app/api/address/route.ts +++ b/src/app/api/address/route.ts @@ -1,4 +1,5 @@ import { NextRequest } from 'next/server'; +import { supabase } from '../../../lib/supabase/client'; const mapsTranslationToDbTowns: { [key: string]: string } = { Aldaya: 'Aldaia', @@ -22,6 +23,26 @@ const GOOGLE_URL = `https://maps.googleapis.com/maps/api/geocode/json?key=${proc export type AddressAndTown = { address: string; town: string }; +async function checkAuthentication(request: NextRequest) { + // Extract the Supabase token from the authorization header + const authHeader = request.headers.get('authorization') || ''; + const token = authHeader.split('Bearer ')[1]; + + if (!token) { + return Response.json({ + error: 'Unauthorized: No token provided in authorization headers!', + }); + } + + // Validate the token + const { error } = await supabase.auth.getUser(token); + if (error) { + return Response.json({ + error: "Unauthorized: Couldn't decode the token correctly!", + }); + } +} + function normalizeData({ address, town }: AddressAndTown): AddressAndTown { const normalizedTown = Object.keys(mapsTranslationToDbTowns).includes(town) ? mapsTranslationToDbTowns[town] : town; const normalizedAddress = address.replace(town, normalizedTown); @@ -58,6 +79,12 @@ function extractAddressAndTown(googleResponse: any) { } export async function POST(request: NextRequest) { + // will return Response object on error + const response = await checkAuthentication(request); + if (response) { + return response; + } + const body = await request.json(); if (!body.latitude || !body.longitude) { return Response.json({ @@ -74,7 +101,7 @@ export async function POST(request: NextRequest) { if (response.error_message) { return Response.json({ - error: response.error_message, + error: `Error de google: ${response.error_message}`, }); } diff --git a/src/components/AddressMap.tsx b/src/components/AddressMap.tsx index 0c89fbab..c345139d 100644 --- a/src/components/AddressMap.tsx +++ b/src/components/AddressMap.tsx @@ -1,12 +1,13 @@ 'use client'; import GeoLocationMap, { LngLat } from '@/components/map/GeolocationMap'; -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { locationService } from '../lib/service'; import GooglePlacesAutocomplete from 'react-google-places-autocomplete'; import { useDebouncedFunction, useThrottledFunction } from '../helpers/hooks'; import { OnChangeValue } from 'react-select'; +import { supabase } from '../lib/supabase/client'; export type AddressMapProps = { onNewAddressDescriptor: (onNewAddressDescriptor: AddressDescriptor) => void; @@ -31,6 +32,7 @@ const THROTTLE_MS = 2000; const DEBOUNCE_MS = 400; export default function AddressMap({ onNewAddressDescriptor, initialAddressDescriptor, titulo }: AddressMapProps) { + const [sessionToken, setSessionToken] = useState(''); const isEdit = useRef(Boolean(initialAddressDescriptor)); const [status, setStatus] = useState('unknown'); const [lngLat, setLngLat] = useState(initialAddressDescriptor?.coordinates ?? undefined); @@ -40,6 +42,13 @@ export default function AddressMap({ onNewAddressDescriptor, initialAddressDescr coordinates: null, }); + useEffect(() => { + supabase.auth.getSession().then(({ data: { session } }: any) => { + debugger; + setSessionToken(session?.access_token || ''); + }); + }, []); + const handleSelect = async (newValue: OnChangeValue) => { if (newValue && newValue.value) { const placeId = newValue.value.place_id; @@ -65,9 +74,10 @@ export default function AddressMap({ onNewAddressDescriptor, initialAddressDescr const { address, town, error } = await locationService.getFormattedAddress( String(coordinates.lng), String(coordinates.lat), + sessionToken, ); if (error) { - throw { message: `Error inesperado con la api de google: ${error}` }; + throw { message: `Error inesperado con el geocoding endpoint: ${error}` }; } const newAddressDescriptor: AddressDescriptor = { diff --git a/src/context/SessionProvider.tsx b/src/context/SessionProvider.tsx index 9629d3a6..b8aa6502 100644 --- a/src/context/SessionProvider.tsx +++ b/src/context/SessionProvider.tsx @@ -3,28 +3,30 @@ import React, { createContext, ReactNode, useContext, useEffect, useState } from import { User } from '@supabase/auth-js'; import { supabase } from '@/lib/supabase/client'; -const SessionContext = createContext({ user: null }); +const SessionContext = createContext({ user: null, token: null }); -type UserSession = { user: User } | { user: null }; +type UserSession = { user: User | null; token: string | null }; type SessionProviderProps = { children: ReactNode; }; export const SessionProvider: React.FC = ({ children }) => { - const [session, setSession] = useState(() => ({ user: null })); + const [session, setSession] = useState(() => ({ user: null, token: null })); useEffect(() => { // Fetch initial session const fetchSession = async () => { const { data } = await supabase.auth.getUser(); - setSession(data); + const session = await supabase.auth.getSession(); + + setSession({ user: data.user, token: session?.data?.session?.access_token ?? null }); }; fetchSession(); // Subscribe to session changes const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => { - setSession(() => ({ user: session?.user ?? null })); // Update the session in state + setSession(() => ({ user: session?.user ?? null, token: session?.access_token ?? null })); // Update the session in state }); // Clean up listener on component unmount diff --git a/src/lib/service.ts b/src/lib/service.ts index 4064a24c..78c479a3 100644 --- a/src/lib/service.ts +++ b/src/lib/service.ts @@ -194,10 +194,11 @@ export const helpRequestService = { }; export const locationService = { - async getFormattedAddress(longitude: string, latitude: string) { + // if token is not provided it will get it from session + async getFormattedAddress(longitude: string, latitude: string, token: string) { return await fetch('/api/address', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { 'Content-Type': 'application/json', authorization: `Bearer ${token}` }, body: JSON.stringify({ longitude, latitude, From 8f60af2ec5978df9a5190ac2a346037165f4a75c Mon Sep 17 00:00:00 2001 From: Roberto Milla Martinez Date: Sun, 10 Nov 2024 00:09:58 +0100 Subject: [PATCH 2/6] feat: authed request address --- src/app/api/address/route.ts | 24 +++++++----------------- src/lib/service.ts | 4 ++-- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/app/api/address/route.ts b/src/app/api/address/route.ts index 7a147481..b36f2e3f 100644 --- a/src/app/api/address/route.ts +++ b/src/app/api/address/route.ts @@ -1,5 +1,5 @@ import { NextRequest } from 'next/server'; -import { supabase } from '../../../lib/supabase/client'; +import { createClient } from '@/lib/supabase/server'; const mapsTranslationToDbTowns: { [key: string]: string } = { Aldaya: 'Aldaia', @@ -23,22 +23,12 @@ const GOOGLE_URL = `https://maps.googleapis.com/maps/api/geocode/json?key=${proc export type AddressAndTown = { address: string; town: string }; -async function checkAuthentication(request: NextRequest) { - // Extract the Supabase token from the authorization header - const authHeader = request.headers.get('authorization') || ''; - const token = authHeader.split('Bearer ')[1]; - - if (!token) { - return Response.json({ - error: 'Unauthorized: No token provided in authorization headers!', - }); - } - - // Validate the token - const { error } = await supabase.auth.getUser(token); - if (error) { +async function checkAuthentication() { + const supabase = await createClient(); + const { data, error } = await supabase.auth.getUser(); + if (error || !data?.user) { return Response.json({ - error: "Unauthorized: Couldn't decode the token correctly!", + error: 'Unauthenticated: User must be logged in', }); } } @@ -80,7 +70,7 @@ function extractAddressAndTown(googleResponse: any) { export async function POST(request: NextRequest) { // will return Response object on error - const response = await checkAuthentication(request); + const response = await checkAuthentication(); if (response) { return response; } diff --git a/src/lib/service.ts b/src/lib/service.ts index 78947359..ccdd9c29 100644 --- a/src/lib/service.ts +++ b/src/lib/service.ts @@ -194,10 +194,10 @@ export const helpRequestService = { export const locationService = { // if token is not provided it will get it from session - async getFormattedAddress(longitude: string, latitude: string, token: string) { + async getFormattedAddress(longitude: string, latitude: string) { return await fetch('/api/address', { method: 'POST', - headers: { 'Content-Type': 'application/json', authorization: `Bearer ${token}` }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ longitude, latitude, From a45c76dd25f44805bf34bd3ea0e67f2726db03b0 Mon Sep 17 00:00:00 2001 From: Roberto Milla Martinez Date: Sun, 10 Nov 2024 00:17:36 +0100 Subject: [PATCH 3/6] fix: build error --- src/components/AddressMap.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/AddressMap.tsx b/src/components/AddressMap.tsx index e871f166..16168499 100644 --- a/src/components/AddressMap.tsx +++ b/src/components/AddressMap.tsx @@ -31,7 +31,6 @@ const THROTTLE_MS = 2000; const DEBOUNCE_MS = 400; export default function AddressMap({ onNewAddressDescriptor, initialAddressDescriptor, titulo }: AddressMapProps) { - const [sessionToken, setSessionToken] = useState(''); const isEdit = useRef(initialAddressDescriptor?.address !== ''); const [status, setStatus] = useState('unknown'); const [lngLat, setLngLat] = useState(initialAddressDescriptor?.coordinates ?? undefined); @@ -41,13 +40,6 @@ export default function AddressMap({ onNewAddressDescriptor, initialAddressDescr coordinates: null, }); - useEffect(() => { - supabase.auth.getSession().then(({ data: { session } }: any) => { - debugger; - setSessionToken(session?.access_token || ''); - }); - }, []); - const handleSelect = async (newValue: OnChangeValue) => { if (newValue && newValue.value) { const placeId = newValue.value.place_id; @@ -73,7 +65,6 @@ export default function AddressMap({ onNewAddressDescriptor, initialAddressDescr const { address, town, error } = await locationService.getFormattedAddress( String(coordinates.lng), String(coordinates.lat), - sessionToken, ); if (error) { throw { message: `Error inesperado con el geocoding endpoint: ${error}` }; From cb63db00a9d4faa57e321d7cd85fffce70f6c886 Mon Sep 17 00:00:00 2001 From: Roberto Milla Martinez Date: Sun, 10 Nov 2024 00:23:04 +0100 Subject: [PATCH 4/6] fix: code styling --- src/app/casos-activos/solicitudes/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/casos-activos/solicitudes/page.tsx b/src/app/casos-activos/solicitudes/page.tsx index 80d8ae0e..9981f6d6 100644 --- a/src/app/casos-activos/solicitudes/page.tsx +++ b/src/app/casos-activos/solicitudes/page.tsx @@ -190,7 +190,7 @@ function Solicitudes() { ))} -{/*
+ {/*
Date: Sun, 10 Nov 2024 00:26:36 +0100 Subject: [PATCH 5/6] fix: remove unnecesary changes --- src/context/SessionProvider.tsx | 12 +++++------- src/lib/service.ts | 1 - 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/context/SessionProvider.tsx b/src/context/SessionProvider.tsx index b8aa6502..9629d3a6 100644 --- a/src/context/SessionProvider.tsx +++ b/src/context/SessionProvider.tsx @@ -3,30 +3,28 @@ import React, { createContext, ReactNode, useContext, useEffect, useState } from import { User } from '@supabase/auth-js'; import { supabase } from '@/lib/supabase/client'; -const SessionContext = createContext({ user: null, token: null }); +const SessionContext = createContext({ user: null }); -type UserSession = { user: User | null; token: string | null }; +type UserSession = { user: User } | { user: null }; type SessionProviderProps = { children: ReactNode; }; export const SessionProvider: React.FC = ({ children }) => { - const [session, setSession] = useState(() => ({ user: null, token: null })); + const [session, setSession] = useState(() => ({ user: null })); useEffect(() => { // Fetch initial session const fetchSession = async () => { const { data } = await supabase.auth.getUser(); - const session = await supabase.auth.getSession(); - - setSession({ user: data.user, token: session?.data?.session?.access_token ?? null }); + setSession(data); }; fetchSession(); // Subscribe to session changes const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => { - setSession(() => ({ user: session?.user ?? null, token: session?.access_token ?? null })); // Update the session in state + setSession(() => ({ user: session?.user ?? null })); // Update the session in state }); // Clean up listener on component unmount diff --git a/src/lib/service.ts b/src/lib/service.ts index ccdd9c29..6ae341cd 100644 --- a/src/lib/service.ts +++ b/src/lib/service.ts @@ -193,7 +193,6 @@ export const helpRequestService = { }; export const locationService = { - // if token is not provided it will get it from session async getFormattedAddress(longitude: string, latitude: string) { return await fetch('/api/address', { method: 'POST', From 53609140d17cd603d6071d4cb545cad5b25f97cf Mon Sep 17 00:00:00 2001 From: Roberto Milla Martinez Date: Sun, 10 Nov 2024 00:51:32 +0100 Subject: [PATCH 6/6] fix: return status codes --- src/app/api/address/route.ts | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/src/app/api/address/route.ts b/src/app/api/address/route.ts index b36f2e3f..5646c6f2 100644 --- a/src/app/api/address/route.ts +++ b/src/app/api/address/route.ts @@ -23,14 +23,14 @@ const GOOGLE_URL = `https://maps.googleapis.com/maps/api/geocode/json?key=${proc export type AddressAndTown = { address: string; town: string }; -async function checkAuthentication() { +async function checkAuthentication(): Promise { const supabase = await createClient(); const { data, error } = await supabase.auth.getUser(); + if (error || !data?.user) { - return Response.json({ - error: 'Unauthenticated: User must be logged in', - }); + return false; } + return true; } function normalizeData({ address, town }: AddressAndTown): AddressAndTown { @@ -68,31 +68,22 @@ function extractAddressAndTown(googleResponse: any) { return { address, town }; } -export async function POST(request: NextRequest) { +export async function POST(request: NextRequest, response: any) { // will return Response object on error - const response = await checkAuthentication(); - if (response) { - return response; + if (!(await checkAuthentication())) { + return Response.json({ error: 'Unauthenticated: User must be logged in' }, { status: 401 }); } const body = await request.json(); if (!body.latitude || !body.longitude) { - return Response.json({ - error: 'Latitude and longitude are mandatory fields!', - }); + return Response.json({ error: 'Latitude and longitude are mandatory fields!' }, { status: 401 }); } try { - const response = await fetch(`${GOOGLE_URL}${body.latitude},${body.longitude}`, { - headers: { - Referer: request.headers.get('Referer') ?? '', - }, - }).then((value) => value.json()); + const response = await fetch(`${GOOGLE_URL}${body.latitude},${body.longitude}`).then((value) => value.json()); if (response.error_message) { - return Response.json({ - error: `Error de google: ${response.error_message}`, - }); + return Response.json({ error: `Error de google: ${response.error_message}` }, { status: 502 }); } const extractedData = extractAddressAndTown(response); @@ -100,8 +91,6 @@ export async function POST(request: NextRequest) { return Response.json(extractedData); } catch (exception) { console.error(exception); - return Response.json({ - error: 'An error occured calling google - check logs', - }); + return Response.json({ error: 'An error occured calling google - check logs' }, { status: 500 }); } }