File tree Expand file tree Collapse file tree 6 files changed +44
-27
lines changed
Expand file tree Collapse file tree 6 files changed +44
-27
lines changed Original file line number Diff line number Diff line change 1515 },
1616 "dependencies" : {
1717 "@tanstack/react-query" : " ^5.54.1" ,
18- "@vercel/blob" : " ^1.1 .1" ,
18+ "@vercel/blob" : " ^2.0 .1" ,
1919 "appwrite" : " ^15.0.0" ,
2020 "dotenv" : " ^17.2.1" ,
2121 "next" : " ^16.1.6" ,
Original file line number Diff line number Diff line change 1+ /**
2+ * Custom Next.js server for local development only.
3+ *
4+ * SECURITY NOTE: This uses HTTP (not HTTPS) which is acceptable for localhost development.
5+ * In production, this file is NOT used - Vercel handles HTTPS termination automatically.
6+ * Do not use this server in production environments.
7+ */
18const { createServer } = require ( 'http' ) ;
29const { parse } = require ( 'url' ) ;
310const next = require ( 'next' ) ;
411
12+ // This server is for LOCAL DEVELOPMENT ONLY - Vercel handles production
513const dev = process . env . NODE_ENV !== 'production' ;
614const hostname = 'localhost' ;
715const port = 3000 ;
Original file line number Diff line number Diff line change @@ -5,6 +5,7 @@ import { Speaker } from '@/types';
55import styles from './speakersList.module.css' ;
66import Image from 'next/image' ;
77import { LABELS } from '@/app/labels' ;
8+ import { sanitizeExternalUrl } from '@/utils/urlSanitizer' ;
89
910interface SpeakersListProps {
1011 selectedTopic ?: string | null ;
@@ -153,9 +154,9 @@ export default function SpeakersList({ selectedTopic }: SpeakersListProps) {
153154 { formatDate ( speaker . lastSpoke ) }
154155 </ p >
155156 ) }
156- { speaker . linkedin && (
157+ { sanitizeExternalUrl ( speaker . linkedin ) && (
157158 < a
158- href = { speaker . linkedin }
159+ href = { sanitizeExternalUrl ( speaker . linkedin ) ! }
159160 target = '_blank'
160161 rel = 'noopener noreferrer'
161162 className = { styles . linkedinLink }
Original file line number Diff line number Diff line change @@ -5,6 +5,7 @@ import { Speaker } from '@/types';
55import OptimizedImage from '@/components/ui/OptimizedImage' ;
66import styles from './teamList.module.css' ;
77import { LABELS } from '@/app/labels' ;
8+ import { sanitizeExternalUrl } from '@/utils/urlSanitizer' ;
89
910function TeamListComponent ( { peopleData } : { peopleData : Speaker [ ] } ) {
1011 return (
@@ -35,9 +36,9 @@ function TeamListComponent({ peopleData }: { peopleData: Speaker[] }) {
3536 { LABELS . teamList . admin_role }
3637 </ p >
3738 < p className = { styles . teamCompany } > { LABELS . app . orgName } </ p >
38- { person . linkedin && (
39+ { sanitizeExternalUrl ( person . linkedin ) && (
3940 < a
40- href = { person . linkedin }
41+ href = { sanitizeExternalUrl ( person . linkedin ) ! }
4142 target = '_blank'
4243 rel = 'noopener noreferrer'
4344 className = { styles . linkedinLink }
Original file line number Diff line number Diff line change 1+ /**
2+ * Sanitize a generic external URL.
3+ * Only allows HTTPS URLs to prevent javascript: and data: XSS attacks.
4+ *
5+ * @param url - The URL to sanitize
6+ * @returns The sanitized URL if valid, null otherwise
7+ */
8+ export function sanitizeExternalUrl ( url : string | undefined ) : string | null {
9+ if ( ! url ) return null ;
10+ try {
11+ const parsed = new URL ( url ) ;
12+ if ( parsed . protocol === 'https:' ) {
13+ return parsed . href ;
14+ }
15+ return null ;
16+ } catch {
17+ return null ;
18+ }
19+ }
You can’t perform that action at this time.
0 commit comments