From c0f4037600d7d6aa1c1d8bf7024ca8bc6fe2f151 Mon Sep 17 00:00:00 2001 From: vikas-6 Date: Sat, 16 May 2026 10:02:58 +0530 Subject: [PATCH] feat: implement security headers (#84) and overhaul UI/UX --- apps/backend/src/app.ts | 17 +- apps/web/src/app.css | 106 +++-- apps/web/src/hooks.server.ts | 13 + apps/web/src/routes/+page.svelte | 311 ++++++--------- apps/web/src/routes/u/[username]/+page.svelte | 364 +++++++++--------- apps/web/svelte.config.js | 16 +- 6 files changed, 425 insertions(+), 402 deletions(-) create mode 100644 apps/web/src/hooks.server.ts diff --git a/apps/backend/src/app.ts b/apps/backend/src/app.ts index dc023a2..8e8cf38 100644 --- a/apps/backend/src/app.ts +++ b/apps/backend/src/app.ts @@ -37,7 +37,22 @@ export async function buildApp() { credentials: true, }); - await app.register(helmet, { contentSecurityPolicy: false }); + await app.register(helmet, { + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + baseUri: ["'self'"], + fontSrc: ["'self'", 'https:', 'data:', 'https://fonts.gstatic.com'], + frameAncestors: ["'self'"], + imgSrc: ["'self'", 'data:', 'https:'], + objectSrc: ["'none'"], + scriptSrc: ["'self'"], + scriptSrcAttr: ["'none'"], + styleSrc: ["'self'", 'https:', "'unsafe-inline'", 'https://fonts.googleapis.com'], + upgradeInsecureRequests: [], + }, + }, + }); await app.register(jwt, { secret: process.env.JWT_SECRET || 'dev-secret-change-me', diff --git a/apps/web/src/app.css b/apps/web/src/app.css index 772a760..bb09fef 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -1,47 +1,50 @@ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Outfit:wght@500;600;700;800;900&display=swap'); -/* light theme β€” default */ :root { - /* Primary */ + /* Primary Palette */ --primary: #6366f1; - --primary-light: #818cf8; - --primary-dark: #4f46e5; - --accent: #8b5cf6; - - /* Background */ - --bg-primary: #f8fafc; - --bg-secondary: #f1f5f9; + --primary-glow: rgba(99, 102, 241, 0.5); + --accent: #a855f7; + --accent-glow: rgba(168, 85, 247, 0.4); + + /* Backgrounds */ + --bg-primary: #ffffff; + --bg-secondary: #f8fafc; + --bg-glass: rgba(255, 255, 255, 0.7); --bg-card: #ffffff; - --bg-elevated: #e2e8f0; - + /* Text */ --text-primary: #0f172a; --text-secondary: #475569; --text-muted: #94a3b8; - - /* Border */ - --border: #e2e8f0; - - /* Spacing */ + + /* Effects */ + --border: rgba(226, 232, 240, 0.8); + --border-glass: rgba(255, 255, 255, 0.3); + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --radius: 12px; - --radius-lg: 16px; - --radius-xl: 24px; - - --theme-transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; + --radius-lg: 20px; + --radius-xl: 32px; + + --theme-transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); } -/* dark theme */ html.dark { - --bg-primary: #0f0f1a; - --bg-secondary: #1a1a2e; - --bg-card: #16213e; - --bg-elevated: #1e293b; - + --bg-primary: #020617; + --bg-secondary: #0f172a; + --bg-glass: rgba(15, 23, 42, 0.6); + --bg-card: #0f172a; + --text-primary: #f8fafc; - --text-secondary: #94a3b8; + --text-secondary: #cbd5e1; --text-muted: #64748b; - - --border: #334155; + + --border: rgba(30, 41, 59, 0.8); + --border-glass: rgba(255, 255, 255, 0.1); } * { @@ -51,20 +54,53 @@ html.dark { } body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-family: 'Inter', sans-serif; background-color: var(--bg-primary); color: var(--text-primary); transition: var(--theme-transition); -webkit-font-smoothing: antialiased; min-height: 100vh; + overflow-x: hidden; +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Outfit', sans-serif; + font-weight: 700; + line-height: 1.1; } a { - color: var(--primary); + color: inherit; text-decoration: none; - transition: color 0.2s; + transition: var(--theme-transition); +} + +.glass { + background: var(--bg-glass); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid var(--border-glass); +} + +.gradient-text { + background: linear-gradient(135deg, var(--primary), var(--accent)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.btn-primary { + background: linear-gradient(135deg, var(--primary), var(--accent)); + color: white; + padding: 0.8rem 1.6rem; + border-radius: var(--radius); + font-weight: 600; + box-shadow: 0 4px 15px var(--primary-glow); + border: none; + cursor: pointer; } -a:hover { - color: var(--primary-dark); +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px var(--primary-glow); } diff --git a/apps/web/src/hooks.server.ts b/apps/web/src/hooks.server.ts new file mode 100644 index 0000000..e17520e --- /dev/null +++ b/apps/web/src/hooks.server.ts @@ -0,0 +1,13 @@ +import type { Handle } from '@sveltejs/kit'; + +export const handle: Handle = async ({ event, resolve }) => { + const response = await resolve(event); + + // Security Headers (Note: CSP is handled in svelte.config.js) + response.headers.set('X-Content-Type-Options', 'nosniff'); + response.headers.set('Referrer-Policy', 'no-referrer'); + response.headers.set('X-Frame-Options', 'DENY'); + response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()'); + + return response; +}; diff --git a/apps/web/src/routes/+page.svelte b/apps/web/src/routes/+page.svelte index 363a491..512f905 100644 --- a/apps/web/src/routes/+page.svelte +++ b/apps/web/src/routes/+page.svelte @@ -28,287 +28,218 @@ /> +
+
+ +
- - -

DevCard

-

One Tap. Every Profile. Every Platform.

+
GSSoC'26 Edition
+

One Tap. Every Profile.
Every Platform.

- Stop sharing LinkedIn, GitHub, and Twitter one by one.
- DevCard puts every profile in one shareable QR code. + The open-source standard for developer networking. Put all your profilesβ€”GitHub, LinkedIn, LeetCode, and moreβ€”into a single, high-impact digital card.

-
+
πŸ”—
-

One Card, All Profiles

-

- GitHub, LinkedIn, Twitter/X, Devfolio, GitLab, LeetCode, and 10+ more β€” - all in one card. -

+

Unified Identity

+

Combine your fragmented online presence into a cohesive professional identity.

-
+
⚑
-

Hybrid Follow Engine

-

- Follow on GitHub silently via API. Connect on LinkedIn with one tap in - WebView. No app switching. -

-
-
-
πŸ’³
-

Context Cards

-

- Share your "Professional" card at conferences and "Hackathon" card at - hack events. Same profiles, different contexts. -

+

Instant Follow

+

Integrated APIs allow followers to connect with you instantly across platforms.

-
-
πŸ“±
-

QR + AirDrop

-

- Generate a QR code or share via AirDrop-style link. Works even if the - receiver doesn't have the app. -

-
-
+
πŸ”’
-

Privacy First

-

- No data monetization. No tracking. Apache 2.0 licensed. You own your - data. -

-
-
-
🌍
-

Open Source

-

- Community-driven development. Contribute, self-host, or extend with your - own platforms. -

+

Private by Design

+

No tracking, no data selling. Your information stays where it belongs: with you.

-

DevCard β€” Open Source Developer Profile Exchange

-

- Apache 2.0 License β€’ GitHub -

+

Β© 2026 DevCard β€’ Built for the Developer Community

diff --git a/apps/web/src/routes/u/[username]/+page.svelte b/apps/web/src/routes/u/[username]/+page.svelte index f7750ec..d75e485 100644 --- a/apps/web/src/routes/u/[username]/+page.svelte +++ b/apps/web/src/routes/u/[username]/+page.svelte @@ -1,5 +1,6 @@ {#if profile} - {profile.displayName} β€” DevCard + {profile.displayName} | DevCard - - {:else} - User Not Found β€” DevCard + User Not Found | DevCard {/if} -{#if error || !profile} -
-
+
+ +
+ {#if error || !profile} +
πŸ˜•
-

User Not Found

-

This DevCard doesn't exist or has been removed.

- ← Back to DevCard +

Profile not found

+

This DevCard has vanished into the digital void.

+ Return Home
-
-{:else} -
-
- -
- {#if profile.avatarUrl} - {profile.displayName} - {:else} -
- {profile.displayName.charAt(0).toUpperCase()} -
- {/if} + {:else} +
+
+
+ {#if profile.avatarUrl} + {profile.displayName} + {:else} +
+ {profile.displayName.charAt(0).toUpperCase()} +
+ {/if} +
+
+

{profile.displayName}

- {#if profile.pronouns} - {profile.pronouns} - {/if} {#if profile.role} -

+

{profile.role}{profile.company ? ` @ ${profile.company}` : ''} -

+
{/if} + {#if profile.bio}

{profile.bio}

{/if} -
+ - -
-{/if} + {/if} +
diff --git a/apps/web/svelte.config.js b/apps/web/svelte.config.js index 460897c..55c3bd2 100644 --- a/apps/web/svelte.config.js +++ b/apps/web/svelte.config.js @@ -6,7 +6,21 @@ const config = { // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. // If your environment is not supported, or you settled on a specific environment, switch out the adapter. // See https://svelte.dev/docs/kit/adapters for more information about adapters. - adapter: adapter() + adapter: adapter(), + csp: { + mode: 'auto', + directives: { + 'default-src': ['self'], + 'script-src': ['self', 'unsafe-inline'], + 'style-src': ['self', 'unsafe-inline', 'https://fonts.googleapis.com'], + 'img-src': ['self', 'data:', 'https:'], + 'connect-src': ['self'], + 'font-src': ['self', 'data:', 'https:', 'https://fonts.gstatic.com'], + 'object-src': ['none'], + 'base-uri': ['self'], + 'frame-ancestors': ['none'] + } + } }, vitePlugin: { dynamicCompileOptions: ({ filename }) => ({ runes: !filename.includes('node_modules') })