Skip to content

fix: Issue #898 - allow anonymous public profile viewing and redesign…#918

Open
dotrwt wants to merge 1 commit into
Sachinchaurasiya360:mainfrom
dotrwt:fix-issue-898
Open

fix: Issue #898 - allow anonymous public profile viewing and redesign…#918
dotrwt wants to merge 1 commit into
Sachinchaurasiya360:mainfrom
dotrwt:fix-issue-898

Conversation

@dotrwt
Copy link
Copy Markdown

@dotrwt dotrwt commented Jun 1, 2026

Pull Request

Description

This pull request resolves issue #898 by introducing anonymous viewing of public profiles, implementing elegant fallback/empty states for incomplete profiles, and polishing layout responsiveness and visual continuity.

Key Changes:

  • Server Side:
    • Replaced authMiddleware with optionalAuthMiddleware on GET /api/auth/profile/:id to allow guest access.
    • Implemented authorization checks: guest/unauthenticated users are barred with a 403 Forbidden ("Profile is private") if the requested profile is private. Profile owners, Admins, and Recruiters bypass this block.
    • Disabled caching for private profile data to prevent data leakage.
  • Client Side:
    • Redesigned PublicProfilePage.tsx with dynamic glassmorphism aesthetics, visual continuity, global Navbar, and global Footer components.
    • Built elegant placeholder blocks (dashed borders, descriptive icons, and centered message text) for missing profile sections (Skills, Resumes, Projects, Achievements, and Coding Activity) to keep empty pages structurally balanced.
    • Extracted and memoized child components (StatCard, ProjectCard, AchievementCard) using React.memo to optimize rendering performance.
    • Upgraded the Stats Cards row to use a dynamic grid layout that automatically spans the full container width (sm:grid-cols-3 or sm:grid-cols-4) depending on whether the ATS Score is available.

Related Issue

Fixes #898

Type of Change

  • Bug Fix
  • Feature
  • Enhancement
  • Documentation

Testing

  • Verified all client-side and server-side TypeScript compilation (npm run build and npx tsc --noEmit both complete with zero errors).
  • Tested layout alignment and anonymous access paths on both desktop and mobile viewports using the browser subagent.

Screenshots

Please refer to the embedded images for desktop layouts:
PublicProfileView (1)
PublicProfileView (2)

Checklist

  • Code follows project guidelines
  • No new compile/type errors
  • Tested manually (include steps above)
  • No .env, credentials, or node_modules committed
  • Docs updated (if needed)
  • Screenshots added for UI changes (if applicable)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added public student profile viewing with dedicated route /student/profile/public/:id
    • Private profile screen with sign-in option for non-authenticated viewers
  • UI/UX Improvements

    • Redesigned profile layout with responsive hero section, stats grid, and content sections
    • New reusable card components for projects and achievements
    • Updated student status styling
  • Other

    • Enhanced SEO metadata on public profiles (description, OG image, canonical URL)
    • Improved error handling distinguishing between private and missing profiles
    • Profile endpoint now accessible without authentication while respecting privacy settings

@github-actions github-actions Bot added quality:exceptional Exceptional implementation quality gssoc:approved Approved for GSSoC scoring labels Jun 1, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Hi @dotrwt, thanks for contributing to InternHack! 🎉

I have automatically:

  • 👤 Assigned this PR to you.
  • 🏷️ Applied the gssoc:approved label.

Our workflows will now analyze your changes to classify:

  • 📈 PR Difficulty: level:*
  • 🧩 PR Type: type:*
  • 🌟 PR Quality: quality:*

Tip

Ensure your PR description references the issue it resolves (e.g. Closes #123). This allows the bot to inherit any additional labels from that issue!

Happy coding! 🚀

@github-actions github-actions Bot added bug Something isn't working gssoc type:bug Bug fixes type:design UI/UX or design-related updates level:advanced Complex implementation or logic labels Jun 1, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR fixes public shareable student profile URLs by removing auth requirements on the backend and refactoring the client UI. The server now allows unauthenticated access to public profiles while enforcing private-profile restrictions; the client adds error handling for private profiles and refactors the profile display with reusable components.

Changes

Public Student Profile Access & Display

Layer / File(s) Summary
Server-Side Public Profile Authorization
server/src/module/auth/auth.service.ts, server/src/module/auth/auth.controller.ts, server/src/module/auth/auth.routes.ts
Service method now accepts optional viewer (id/role) and enforces role-based access: public profiles accessible to anyone, private profiles restricted to ADMIN/RECRUITER/self. Controller extracts optional viewer from request and passes to service. Routes middleware switched from required authMiddleware to optionalAuthMiddleware, enabling unauthenticated requests. Cache write made conditional on isProfilePublic.
Client Routing & Component Setup
client/src/App.tsx, client/src/module/student/profile/PublicProfilePage.tsx
Route /student/profile/public/:id added to App.tsx. Lock icon imported alongside existing icons. Navbar, Footer, and useAuthStore imported for layout chrome and authentication state detection.
Private Profile Error Screen
client/src/module/student/profile/PublicProfilePage.tsx
HTTP 403 responses render lock-themed "Profile is Private" screen with SEO noIndex tag, Footer, and conditional Sign In navigation based on viewer's logged-in state. HTTP 404 restyled to include Footer.
Reusable Profile Card Components
client/src/module/student/profile/PublicProfilePage.tsx
Memoized StatCard for numeric stats display, exported ProjectCard with optional build date and live/repo links, exported AchievementCard with optional date. All cards use shared Tailwind styling patterns.
Main Profile Layout & Rendering
client/src/module/student/profile/PublicProfilePage.tsx
Hero section with cover image and profile picture fallbacks. Expanded SEO metadata (description, OG image, canonical URL). Responsive grid layout with stats section using StatCard. Projects and achievements rendered via exported cards. Updated job status styling (Tailwind classes for LOOKING/OPEN_TO_OFFER/NO_OFFER). Conditional sections for GitHub activity, resumes, skills, and empty-state placeholders.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Sachinchaurasiya360/InternHack#553: Both PRs modify PublicProfilePage.tsx's SEO/head metadata (title/description/canonical/OG/Twitter tags) for the public student profile.
  • Sachinchaurasiya360/InternHack#746: Both PRs refactor project/featured-project card rendering in PublicProfilePage.tsx with component extraction and card display enhancements.

Suggested labels

bug, level:intermediate, type:design

Suggested reviewers

  • Sachinchaurasiya360

Poem

🐰 A shareable link now hops without walls,
Public profiles bloom when the unauthenticated calls,
With locks for privacy and cards standing tall,
The frontend dances, accepting one and all! 🎨✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: enabling anonymous public profile viewing and UI redesign, directly addressing issue #898.
Linked Issues check ✅ Passed The PR successfully implements all acceptance criteria from issue #898: allows unauthenticated access to public profiles, returns 403 with clear message for private profiles, and removes auth/role blocks for public profile access.
Out of Scope Changes check ✅ Passed All changes are directly related to resolving issue #898: server-side middleware and authorization updates, client-side profile UI redesign, and component extraction align with the stated objectives.
Description check ✅ Passed The PR description comprehensively covers all required template sections with detailed explanations of key changes, related issue, testing approach, and supporting screenshots.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
client/src/module/student/profile/PublicProfilePage.tsx (3)

289-294: ⚡ Quick win

Replace generic avatar icon fallback with first-letter initial.

Fallback currently renders a generic User icon instead of the required initial-in-neutral box.

Suggested fix
-                ) : (
-                  <User className="w-12 h-12 text-stone-400 dark:text-stone-500" />
-                )}
+                ) : (
+                  <span className="text-3xl font-bold text-stone-700 dark:text-stone-200">
+                    {profile.name?.trim()?.charAt(0).toUpperCase() || "U"}
+                  </span>
+                )}

As per coding guidelines, "Use company avatars with first-letter initial in neutral box, not generic icon."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/module/student/profile/PublicProfilePage.tsx` around lines 289 -
294, The fallback currently renders a generic <User /> icon when
profile.profilePic is absent; replace that with a first-letter initial in a
neutral box by deriving the initial from profile.name (e.g., const initial =
profile.name?.trim()?.[0]?.toUpperCase() || '?') and rendering a styled <span>
(matching the surrounding avatar container: centered, same font-size, neutral
background/text color classes) instead of <User />; keep the existing img
onError handler and ensure the fallback element includes an accessible label/alt
via aria-label or title (e.g., `aria-label={`Profile initial ${initial}`}`) so
screen readers get the initial.

263-270: ⚡ Quick win

Use the shared Button component for the new Back action.

This new interactive control is implemented as a native motion.button instead of the reusable Button API.

Suggested direction
-        <motion.button
+        <motion.div
           initial={{ opacity: 0, x: -10 }}
           animate={{ opacity: 1, x: 0 }}
-          onClick={() => navigate(-1)}
-          className="flex items-center gap-2 text-sm text-stone-500 dark:text-stone-400 hover:text-stone-900 dark:hover:text-white mb-6 transition-colors font-medium border border-stone-200/80 dark:border-stone-800/80 bg-white/70 dark:bg-stone-900/50 backdrop-blur-xs px-3.5 py-1.5 rounded-xl shadow-2xs hover:scale-[1.01]"
         >
-          <ArrowLeft className="w-4 h-4" /> Back
-        </motion.button>
+          <Button
+            variant="secondary"
+            onClick={() => navigate(-1)}
+            className="mb-6"
+          >
+            <ArrowLeft className="w-4 h-4" /> Back
+          </Button>
+        </motion.div>

As per coding guidelines, "Use the reusable Button component from client/src/components/ui/button.tsx for all new buttons."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/module/student/profile/PublicProfilePage.tsx` around lines 263 -
270, Replace the native motion.button with the shared Button component: import
Button from client/src/components/ui/button.tsx and use motion(Button) (e.g.,
const MotionButton = motion(Button) or inline motion(Button)) so you can keep
the initial/animate props, preserve onClick={() => navigate(-1)}, the ArrowLeft
child, and the existing className. Ensure you update imports (Button and motion
from 'framer-motion'), remove the direct motion.button usage, and render
<MotionButton ...>Back</MotionButton> to maintain animation and reuse the Button
API.

186-188: ⚡ Quick win

Avoid gradient background on the lock icon container.

The lock icon wrapper uses a gradient (bg-linear-to-br ...) instead of a flat/neutral icon background.

As per coding guidelines, "Do not use gradient backgrounds on icons, use flat color or bare colored icons instead."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/module/student/profile/PublicProfilePage.tsx` around lines 186 -
188, The lock icon container currently applies a gradient via the classes
"bg-linear-to-br from-amber-400 to-orange-500" around the inner white circle;
change this to a flat/neutral background by removing the gradient classes and
using a single flat color utility (e.g., replace with a neutral/amber solid like
"bg-amber-100" and an appropriate dark mode class such as "dark:bg-stone-800")
while keeping the existing structure (the outer div with classes "w-16 h-16
rounded-2xl p-[1px] shadow-lg mb-6" and the inner container and <Lock /> icon)
so the icon background is flat per guidelines.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@client/src/module/student/profile/PublicProfilePage.tsx`:
- Around line 252-258: The canonicalUrl passed to the SEO component is using the
private route pattern currently (`/student/profile/${profile.id}`) which doesn't
match the public route; update the canonicalUrl prop in PublicProfilePage (the
SEO usage) to point to the public route pattern used in App
(`/student/profile/public/${profile.id}`) so crawlers and previews use the
correct public profile URL.

In `@server/src/module/auth/auth.controller.ts`:
- Around line 145-147: The controller currently returns profile JSON without
cache headers; update the auth controller around the req.user /
this.authService.getPublicProfile call to set Cache-Control: no-store for
private views—detect using the returned profile's privacy flag (e.g.,
profile.isPrivate or profile.private) or by checking if viewer (req.user) is the
owner/privileged role, and call res.setHeader('Cache-Control', 'no-store') (or
res.set) before returning res.status(200).json({ profile }) so private profile
responses are not cached by browsers/proxies.

In `@server/src/module/auth/auth.service.ts`:
- Around line 520-522: The cached public profile may be returned even if the
user just toggled privacy; change the early-return logic so you only return
cache when the profile is still public: after reading cached via
cacheGet(cacheKey) verify the cached object's isProfilePublic flag (or re-check
the current isProfilePublic state from the authoritative source) and only return
the cached value when isProfilePublic is true; otherwise continue and fetch
fresh data. Also ensure cache entries are written only for public profiles and
note that updateProfile currently invalidates profile:public:${userId} after DB
and signing work—keep that invalidation but prevent returning stale private data
by guarding the cached return with the isProfilePublic check.

---

Nitpick comments:
In `@client/src/module/student/profile/PublicProfilePage.tsx`:
- Around line 289-294: The fallback currently renders a generic <User /> icon
when profile.profilePic is absent; replace that with a first-letter initial in a
neutral box by deriving the initial from profile.name (e.g., const initial =
profile.name?.trim()?.[0]?.toUpperCase() || '?') and rendering a styled <span>
(matching the surrounding avatar container: centered, same font-size, neutral
background/text color classes) instead of <User />; keep the existing img
onError handler and ensure the fallback element includes an accessible label/alt
via aria-label or title (e.g., `aria-label={`Profile initial ${initial}`}`) so
screen readers get the initial.
- Around line 263-270: Replace the native motion.button with the shared Button
component: import Button from client/src/components/ui/button.tsx and use
motion(Button) (e.g., const MotionButton = motion(Button) or inline
motion(Button)) so you can keep the initial/animate props, preserve onClick={()
=> navigate(-1)}, the ArrowLeft child, and the existing className. Ensure you
update imports (Button and motion from 'framer-motion'), remove the direct
motion.button usage, and render <MotionButton ...>Back</MotionButton> to
maintain animation and reuse the Button API.
- Around line 186-188: The lock icon container currently applies a gradient via
the classes "bg-linear-to-br from-amber-400 to-orange-500" around the inner
white circle; change this to a flat/neutral background by removing the gradient
classes and using a single flat color utility (e.g., replace with a
neutral/amber solid like "bg-amber-100" and an appropriate dark mode class such
as "dark:bg-stone-800") while keeping the existing structure (the outer div with
classes "w-16 h-16 rounded-2xl p-[1px] shadow-lg mb-6" and the inner container
and <Lock /> icon) so the icon background is flat per guidelines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 70346d14-e3a9-4e23-ab70-d8e23c6b3445

📥 Commits

Reviewing files that changed from the base of the PR and between 6effc94 and 2cb1147.

📒 Files selected for processing (5)
  • client/src/App.tsx
  • client/src/module/student/profile/PublicProfilePage.tsx
  • server/src/module/auth/auth.controller.ts
  • server/src/module/auth/auth.routes.ts
  • server/src/module/auth/auth.service.ts

Comment on lines 252 to +258
<SEO
title={`${profile.name} — InternHack Profile`}
description={`${profile.name}'s skills: ${profile.skills.slice(0, 5).join(", ")}${profile.skills.length > 5 ? " and more" : ""}. ${profile.bio ? profile.bio.slice(0, 100) : "View their projects, achievements, and verified skills on InternHack."}`}
ogImage={profile.profilePic || undefined}
ogType="profile"
canonicalUrl={`https://internhack.xyz/student/profile/${profile.id}`}
/>

{/* Back button */}
<motion.button
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
onClick={() => navigate(-1)}
className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white mb-6 transition-colors"
>
<ArrowLeft className="w-4 h-4" /> Back
</motion.button>

{/* ── Hero Card with Cover Image ── */}
<motion.div custom={0} variants={fadeInUp} initial="hidden" animate="visible"
className="bg-white dark:bg-gray-900 rounded-2xl border border-gray-100 dark:border-gray-800 overflow-hidden mb-6">
{/* Cover / Banner */}
<div className="h-36 relative">
{profile.coverImage ? (
<img src={profile.coverImage} alt="" className="w-full h-full object-cover" />
) : (
<div className="w-full h-full bg-linear-to-br from-indigo-500 via-violet-500 to-purple-500">
<div className="absolute inset-0 opacity-15" style={{ backgroundImage: "radial-gradient(circle at 1px 1px, rgba(255,255,255,0.3) 1px, transparent 0)", backgroundSize: "20px 20px" }} />
</div>
)}
</div>
title={`${profile.name} — InternHack Profile`}
description={`${profile.name}'s skills: ${profile.skills.slice(0, 5).join(", ")}${profile.skills.length > 5 ? " and more" : ""}. ${profile.bio ? profile.bio.slice(0, 100) : "View their projects, achievements, and verified skills on InternHack."}`}
ogImage={profile.profilePic || undefined}
ogType="profile"
canonicalUrl={`https://internhack.xyz/student/profile/${profile.id}`}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix canonical URL to the actual public profile route.

Line 257 currently sets canonical to /student/profile/${profile.id}, but the public route added in App is /student/profile/public/:id. This produces a wrong canonical for crawlers and shared previews.

Suggested fix
-        canonicalUrl={`https://internhack.xyz/student/profile/${profile.id}`}
+        canonicalUrl={`https://internhack.xyz/student/profile/public/${profile.id}`}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<SEO
title={`${profile.name} — InternHack Profile`}
description={`${profile.name}'s skills: ${profile.skills.slice(0, 5).join(", ")}${profile.skills.length > 5 ? " and more" : ""}. ${profile.bio ? profile.bio.slice(0, 100) : "View their projects, achievements, and verified skills on InternHack."}`}
ogImage={profile.profilePic || undefined}
ogType="profile"
canonicalUrl={`https://internhack.xyz/student/profile/${profile.id}`}
/>
{/* Back button */}
<motion.button
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
onClick={() => navigate(-1)}
className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white mb-6 transition-colors"
>
<ArrowLeft className="w-4 h-4" /> Back
</motion.button>
{/* ── Hero Card with Cover Image ── */}
<motion.div custom={0} variants={fadeInUp} initial="hidden" animate="visible"
className="bg-white dark:bg-gray-900 rounded-2xl border border-gray-100 dark:border-gray-800 overflow-hidden mb-6">
{/* Cover / Banner */}
<div className="h-36 relative">
{profile.coverImage ? (
<img src={profile.coverImage} alt="" className="w-full h-full object-cover" />
) : (
<div className="w-full h-full bg-linear-to-br from-indigo-500 via-violet-500 to-purple-500">
<div className="absolute inset-0 opacity-15" style={{ backgroundImage: "radial-gradient(circle at 1px 1px, rgba(255,255,255,0.3) 1px, transparent 0)", backgroundSize: "20px 20px" }} />
</div>
)}
</div>
title={`${profile.name} — InternHack Profile`}
description={`${profile.name}'s skills: ${profile.skills.slice(0, 5).join(", ")}${profile.skills.length > 5 ? " and more" : ""}. ${profile.bio ? profile.bio.slice(0, 100) : "View their projects, achievements, and verified skills on InternHack."}`}
ogImage={profile.profilePic || undefined}
ogType="profile"
canonicalUrl={`https://internhack.xyz/student/profile/${profile.id}`}
/>
<SEO
title={`${profile.name} — InternHack Profile`}
description={`${profile.name}'s skills: ${profile.skills.slice(0, 5).join(", ")}${profile.skills.length > 5 ? " and more" : ""}. ${profile.bio ? profile.bio.slice(0, 100) : "View their projects, achievements, and verified skills on InternHack."}`}
ogImage={profile.profilePic || undefined}
ogType="profile"
canonicalUrl={`https://internhack.xyz/student/profile/public/${profile.id}`}
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/module/student/profile/PublicProfilePage.tsx` around lines 252 -
258, The canonicalUrl passed to the SEO component is using the private route
pattern currently (`/student/profile/${profile.id}`) which doesn't match the
public route; update the canonicalUrl prop in PublicProfilePage (the SEO usage)
to point to the public route pattern used in App
(`/student/profile/public/${profile.id}`) so crawlers and previews use the
correct public profile URL.

Comment on lines +145 to 147
const viewer = req.user ? { id: req.user.id, role: req.user.role } : undefined;
const profile = await this.authService.getPublicProfile(id, viewer);
return res.status(200).json({ profile });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Private 200 responses still need explicit no-store headers.

This endpoint now serves anonymous public data and authenticated private data from the same URL. When an owner, recruiter, or admin gets a 200 for a private profile, the response is still left to browser/proxy cache defaults, so the "disable caching for private profile data" objective is incomplete.

🛡️ Suggested fix
       const viewer = req.user ? { id: req.user.id, role: req.user.role } : undefined;
       const profile = await this.authService.getPublicProfile(id, viewer);
+      if (!profile.isProfilePublic) {
+        res.set("Cache-Control", "private, no-store, max-age=0");
+      }
       return res.status(200).json({ profile });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const viewer = req.user ? { id: req.user.id, role: req.user.role } : undefined;
const profile = await this.authService.getPublicProfile(id, viewer);
return res.status(200).json({ profile });
const viewer = req.user ? { id: req.user.id, role: req.user.role } : undefined;
const profile = await this.authService.getPublicProfile(id, viewer);
if (!profile.isProfilePublic) {
res.set("Cache-Control", "private, no-store, max-age=0");
}
return res.status(200).json({ profile });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/module/auth/auth.controller.ts` around lines 145 - 147, The
controller currently returns profile JSON without cache headers; update the auth
controller around the req.user / this.authService.getPublicProfile call to set
Cache-Control: no-store for private views—detect using the returned profile's
privacy flag (e.g., profile.isPrivate or profile.private) or by checking if
viewer (req.user) is the owner/privileged role, and call
res.setHeader('Cache-Control', 'no-store') (or res.set) before returning
res.status(200).json({ profile }) so private profile responses are not cached by
browsers/proxies.

Comment on lines 520 to 522
const cacheKey = `profile:public:${userId}`;
const cached = await cacheGet(cacheKey);
if (cached) return cached as never;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stale public cache can outlive a privacy toggle.

Line 522 returns the cached profile before the new isProfilePublic check runs. In this same file, updateProfile only deletes profile:public:${userId} after the database write and the awaited URL-signing work, so a profile that was just made private can still leak from cache during that window.

🔐 Suggested fix
   const user = await prisma.user.update({
     where: { id: userId },
     data: updateData,
     select: this.profileSelect,
   });

+  // Bust the public cache before any further awaited work so privacy
+  // changes take effect immediately.
+  await cacheDel(`profile:public:${userId}`);
+
   // Check profile_complete badge (fire-and-forget)
   badgeService.checkAndAwardBadges(userId, "profile_complete").catch(() => {});
@@
   // Bust cached profile so next GET /auth/me returns fresh data
   await cacheDel(`profile:me:${userId}`);
-  await cacheDel(`profile:public:${userId}`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const cacheKey = `profile:public:${userId}`;
const cached = await cacheGet(cacheKey);
if (cached) return cached as never;
const user = await prisma.user.update({
where: { id: userId },
data: updateData,
select: this.profileSelect,
});
// Bust the public cache before any further awaited work so privacy
// changes take effect immediately.
await cacheDel(`profile:public:${userId}`);
// Check profile_complete badge (fire-and-forget)
badgeService.checkAndAwardBadges(userId, "profile_complete").catch(() => {});
// ... other code ...
// Bust cached profile so next GET /auth/me returns fresh data
await cacheDel(`profile:me:${userId}`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/module/auth/auth.service.ts` around lines 520 - 522, The cached
public profile may be returned even if the user just toggled privacy; change the
early-return logic so you only return cache when the profile is still public:
after reading cached via cacheGet(cacheKey) verify the cached object's
isProfilePublic flag (or re-check the current isProfilePublic state from the
authoritative source) and only return the cached value when isProfilePublic is
true; otherwise continue and fetch fresh data. Also ensure cache entries are
written only for public profiles and note that updateProfile currently
invalidates profile:public:${userId} after DB and signing work—keep that
invalidation but prevent returning stale private data by guarding the cached
return with the isProfilePublic check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working gssoc:approved Approved for GSSoC scoring gssoc level:advanced Complex implementation or logic quality:exceptional Exceptional implementation quality type:bug Bug fixes type:design UI/UX or design-related updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: public profile page shows not available when visiting shareable URL

1 participant