refactor: use combined shareId (locationId + checksum) in URL

Changes:
- Add generateShareId() and extractShareId() helpers
- Share URLs now use single parameter: /share/location/{shareId}
- shareId = locationId (24 chars) + checksum (16 chars) = 40 chars total
- Update validateShareAccess() to extract locationId from shareId
- Update uploadProofOfPayment() to accept combined shareId
- Update LocationViewPage to validate and extract locationId from shareId

Benefits:
- Simpler URL structure (one parameter instead of two)
- Checksum extraction by length (deterministic, no parsing needed)
- Same security properties (HMAC-SHA256 validation)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Knee Cola
2025-12-08 00:22:59 +01:00
parent e497ad1da6
commit 844e386e18
5 changed files with 104 additions and 29 deletions

View File

@@ -1,14 +1,28 @@
import { ViewLocationCard } from '@/app/ui/ViewLocationCard';
import { fetchLocationById, setSeenByTenantAt } from '@/app/lib/actions/locationActions';
import { fetchLocationById, setSeenByTenantAt, validateShareAccess } from '@/app/lib/actions/locationActions';
import { getUserSettingsByUserId } from '@/app/lib/actions/userSettingsActions';
import { notFound } from 'next/navigation';
import { myAuth } from '@/app/lib/auth';
export default async function LocationViewPage({ locationId }: { locationId:string }) {
export default async function LocationViewPage({ shareId }: { shareId: string }) {
// Validate share access (checks checksum + TTL, extracts locationId)
const accessValidation = await validateShareAccess(shareId);
if (!accessValidation.valid || !accessValidation.locationId) {
return (
<div className="alert alert-error">
<p>{accessValidation.error || 'This content is no longer shared'}</p>
</div>
);
}
const locationId = accessValidation.locationId;
// Fetch location
const location = await fetchLocationById(locationId);
if (!location) {
return(notFound());
return notFound();
}
// Fetch user settings for the location owner
@@ -23,5 +37,11 @@ export default async function LocationViewPage({ locationId }: { locationId:stri
await setSeenByTenantAt(locationId);
}
return (<ViewLocationCard location={location} userSettings={userSettings} />);
return (
<ViewLocationCard
location={location}
userSettings={userSettings}
shareId={shareId}
/>
);
}

View File

@@ -3,12 +3,11 @@ import LocationViewPage from './LocationViewPage';
import { Main } from '@/app/ui/Main';
import { LocationEditFormSkeleton } from '@/app/ui/LocationEditForm';
export default async function Page({ params:{ id } }: { params: { id:string } }) {
export default async function Page({ params: { id } }: { params: { id: string } }) {
return (
<Main>
<Suspense fallback={<LocationEditFormSkeleton />}>
<LocationViewPage locationId={id} />
<LocationViewPage shareId={id} />
</Suspense>
</Main>
);