mirror of
https://github.com/polaroi8d/cactoide.git
synced 2026-08-12 03:09:11 +00:00
Compare commits
2 Commits
fix/permis
...
feat/user-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdc04502f9 | ||
|
|
d18afc43da |
@@ -27,17 +27,16 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
}
|
||||
|
||||
const cactoideUserId = event.cookies.get('cactoideUserId');
|
||||
const userId = generateUserId();
|
||||
|
||||
const DAYS = 400; // practical upper bound in many browsers for cookies
|
||||
const MAX_AGE = 60 * 60 * 24 * DAYS;
|
||||
const PATH = '/';
|
||||
|
||||
if (!cactoideUserId) {
|
||||
logger.debug({ userId }, 'No cactoideUserId cookie found, generating new one');
|
||||
event.cookies.set('cactoideUserId', userId, { path: PATH, maxAge: MAX_AGE });
|
||||
logger.debug('No cactoideUserId cookie found, generating new one');
|
||||
event.cookies.set('cactoideUserId', generateUserId(), { path: PATH, maxAge: MAX_AGE });
|
||||
} else {
|
||||
logger.debug({ cactoideUserId }, 'cactoideUserId cookie found, using existing one');
|
||||
logger.debug('cactoideUserId cookie found, using existing one');
|
||||
}
|
||||
|
||||
return resolve(event);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const generateUserId = () => {
|
||||
const userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
return userId;
|
||||
};
|
||||
// This id is the only credential the app has — it must not be guessable,
|
||||
// and it must never be serialized to the client.
|
||||
export const generateUserId = () => 'user_' + randomUUID();
|
||||
|
||||
@@ -259,8 +259,6 @@
|
||||
"layout": {
|
||||
"defaultTitle": "Cactoide -",
|
||||
"defaultDescription": "Crea e gestisci gli RSVP degli eventi",
|
||||
"userIdCookieText": "Il tuo UserID memorizzato come cookie:",
|
||||
"firstTimeVisiting": "Prima visita. Generazione di un nuovo UserID...",
|
||||
"copyright": "© 2025 Cactoide"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,8 +291,6 @@
|
||||
"layout": {
|
||||
"defaultTitle": "Cactoide -",
|
||||
"defaultDescription": "Create and manage event RSVPs",
|
||||
"userIdCookieText": "Your UserID stored as a cookie:",
|
||||
"firstTimeVisiting": "First time visiting. Generating new UserID...",
|
||||
"copyright": "© 2025 Cactoide"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface Event {
|
||||
type: EventType;
|
||||
attendee_limit?: number;
|
||||
visibility: EventVisibility;
|
||||
user_id: string;
|
||||
is_creator?: boolean; // Optional: absent on events fetched from federated instances
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
federation?: boolean; // Optional: true if event is from a federated instance
|
||||
@@ -25,7 +25,7 @@ export interface RSVP {
|
||||
id: string;
|
||||
event_id: string;
|
||||
name: string;
|
||||
user_id: string;
|
||||
is_mine: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export function load({ cookies }) {
|
||||
const cactoideUserId = cookies.get('cactoideUserId');
|
||||
|
||||
return {
|
||||
cactoideUserId
|
||||
};
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
import Navbar from '$lib/components/Navbar.svelte';
|
||||
import { t } from '$lib/i18n/i18n.js';
|
||||
|
||||
let { data, children } = $props();
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -28,12 +28,6 @@
|
||||
<footer class="py-12">
|
||||
<div class="container mx-auto px-4 text-center">
|
||||
<div class="text-sm">
|
||||
<p class="mb-4 text-gray-100/80">
|
||||
{t('layout.userIdCookieText')}
|
||||
<span class="font-bold text-violet-400"
|
||||
>{data.cactoideUserId ? data.cactoideUserId : t('layout.firstTimeVisiting')}</span
|
||||
>
|
||||
</p>
|
||||
<p>{t('layout.copyright')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,6 @@ export const GET: RequestHandler = async () => {
|
||||
federation: true,
|
||||
attendee_limit: event.attendeeLimit,
|
||||
visibility: event.visibility,
|
||||
user_id: event.userId,
|
||||
created_at: event.createdAt?.toISOString() || '',
|
||||
updated_at: event.updatedAt?.toISOString() || ''
|
||||
}));
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
let errors: Record<string, string> = {};
|
||||
let isSubmitting = false;
|
||||
let currentUserId = '';
|
||||
|
||||
// Get today's date in YYYY-MM-DD format for min attribute
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
@@ -95,7 +94,6 @@
|
||||
}}
|
||||
class="space-y-6"
|
||||
>
|
||||
<input type="hidden" name="userId" value={currentUserId} />
|
||||
<input type="hidden" name="type" value={eventData.type} />
|
||||
<input type="hidden" name="visibility" value={eventData.visibility} />
|
||||
<input type="hidden" name="location_type" value={eventData.location_type} />
|
||||
|
||||
@@ -26,7 +26,6 @@ export const load: PageServerLoad = async () => {
|
||||
type: event.type,
|
||||
attendee_limit: event.attendeeLimit,
|
||||
visibility: event.visibility,
|
||||
user_id: event.userId,
|
||||
created_at: event.createdAt?.toISOString(),
|
||||
updated_at: event.updatedAt?.toISOString(),
|
||||
federation: false // Add false for local events
|
||||
|
||||
@@ -30,7 +30,6 @@ export const load = async ({ cookies }) => {
|
||||
type: event.type,
|
||||
attendee_limit: event.attendeeLimit,
|
||||
visibility: event.visibility,
|
||||
user_id: event.userId,
|
||||
created_at: event.createdAt?.toISOString() || new Date().toISOString(),
|
||||
updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
|
||||
}));
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
export let data: { events: Event[] };
|
||||
|
||||
let userEvents: Event[] = [];
|
||||
let currentUserId = '';
|
||||
let showDeleteModal = false;
|
||||
let eventToDelete: Event | null = null;
|
||||
|
||||
@@ -28,7 +27,6 @@
|
||||
// Use server-side action for deletion
|
||||
const formData = new FormData();
|
||||
formData.append('eventId', eventId);
|
||||
formData.append('userId', currentUserId);
|
||||
|
||||
const response = await fetch('?/deleteEvent', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -7,6 +7,7 @@ import { logger } from '$lib/logger';
|
||||
|
||||
export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||
const eventId = params.id;
|
||||
const userId = cookies.get('cactoideUserId');
|
||||
|
||||
if (!eventId) {
|
||||
throw error(404, 'EventId not found');
|
||||
@@ -29,7 +30,6 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||
// Check if this is an invite-only event
|
||||
if (event.visibility === 'invite-only') {
|
||||
// For invite-only events, check if user is the event creator
|
||||
const userId = cookies.get('cactoideUserId');
|
||||
if (event.userId !== userId) {
|
||||
// User is not the creator, redirect to a message about needing invite
|
||||
throw error(403, 'This event requires an invite link to view');
|
||||
@@ -48,7 +48,8 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||
type: event.type,
|
||||
attendee_limit: event.attendeeLimit,
|
||||
visibility: event.visibility,
|
||||
user_id: event.userId,
|
||||
// Never send raw user ids to the client — they are the credential
|
||||
is_creator: !!userId && event.userId === userId,
|
||||
created_at: event.createdAt?.toISOString() || new Date().toISOString(),
|
||||
updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
|
||||
};
|
||||
@@ -57,16 +58,13 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||
id: rsvp.id,
|
||||
event_id: rsvp.eventId,
|
||||
name: rsvp.name,
|
||||
user_id: rsvp.userId,
|
||||
is_mine: !!userId && rsvp.userId === userId,
|
||||
created_at: rsvp.createdAt?.toISOString() || new Date().toISOString()
|
||||
}));
|
||||
|
||||
const userId = cookies.get('cactoideUserId');
|
||||
|
||||
return {
|
||||
event: transformedEvent,
|
||||
rsvps: transformedRsvps,
|
||||
userId: userId
|
||||
rsvps: transformedRsvps
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof Response) throw err; // This is the 404 error
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import type { CalendarEvent } from '$lib/calendarHelpers.js';
|
||||
import { t } from '$lib/i18n/i18n.js';
|
||||
|
||||
export let data: { event: Event; rsvps: RSVP[]; userId: string };
|
||||
export let data: { event: Event; rsvps: RSVP[] };
|
||||
type FormDataLocal = { success?: boolean; error?: string; type?: 'add' | 'remove' | 'copy' };
|
||||
export let form: FormDataLocal | undefined;
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
// Use server-side data
|
||||
$: event = data.event;
|
||||
$: rsvps = data.rsvps;
|
||||
$: currentUserId = data.userId;
|
||||
$: isEventCreator = event.user_id === currentUserId;
|
||||
$: isEventCreator = event.is_creator ?? false;
|
||||
|
||||
// Create calendar event object when event data changes
|
||||
$: if (event && browser) {
|
||||
@@ -266,7 +265,6 @@
|
||||
}}
|
||||
class="space-y-4"
|
||||
>
|
||||
<input type="hidden" name="userId" value={currentUserId} />
|
||||
<div>
|
||||
<label for="attendeeName" class=" mb-2 block text-sm font-semibold">
|
||||
{t('event.yourNameLabel')}
|
||||
@@ -401,7 +399,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if attendee.user_id === currentUserId}
|
||||
{#if attendee.is_mine}
|
||||
<form
|
||||
method="POST"
|
||||
action="?/removeRSVP"
|
||||
|
||||
@@ -44,10 +44,13 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Strip the owner id — it is the credential, and the load above already proved ownership
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { userId: _owner, ...eventRow } = event[0];
|
||||
|
||||
return {
|
||||
event: event[0],
|
||||
inviteToken,
|
||||
userId
|
||||
event: eventRow,
|
||||
inviteToken
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -394,7 +394,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Invite Link Section (only for invite-only events and event creator) -->
|
||||
{#if eventData.visibility === 'invite-only' && inviteToken && data.event.userId === data.userId}
|
||||
{#if eventData.visibility === 'invite-only' && inviteToken}
|
||||
<div class="rounded-sm border border-amber-500/30 bg-amber-900/20 p-4">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h3 class="text-lg font-semibold text-amber-400">Invite Link</h3>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { isTokenValid } from '$lib/inviteTokenHelpers.js';
|
||||
export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||
const eventId = params.id;
|
||||
const token = params.token;
|
||||
const userId = cookies.get('cactoideUserId');
|
||||
|
||||
if (!eventId || !token) {
|
||||
throw error(404, 'Event or token not found');
|
||||
@@ -59,7 +60,8 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||
type: event.type,
|
||||
attendee_limit: event.attendeeLimit,
|
||||
visibility: event.visibility,
|
||||
user_id: event.userId,
|
||||
// Never send raw user ids to the client — they are the credential
|
||||
is_creator: !!userId && event.userId === userId,
|
||||
created_at: event.createdAt?.toISOString() || new Date().toISOString(),
|
||||
updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
|
||||
};
|
||||
@@ -68,16 +70,13 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
|
||||
id: rsvp.id,
|
||||
event_id: rsvp.eventId,
|
||||
name: rsvp.name,
|
||||
user_id: rsvp.userId,
|
||||
is_mine: !!userId && rsvp.userId === userId,
|
||||
created_at: rsvp.createdAt?.toISOString() || new Date().toISOString()
|
||||
}));
|
||||
|
||||
const userId = cookies.get('cactoideUserId');
|
||||
|
||||
return {
|
||||
event: transformedEvent,
|
||||
rsvps: transformedRsvps,
|
||||
userId: userId,
|
||||
inviteToken: {
|
||||
id: inviteToken.id,
|
||||
event_id: inviteToken.eventId,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import type { CalendarEvent } from '$lib/calendarHelpers.js';
|
||||
import { t } from '$lib/i18n/i18n.js';
|
||||
|
||||
export let data: { event: Event; rsvps: RSVP[]; userId: string; inviteToken: InviteToken };
|
||||
export let data: { event: Event; rsvps: RSVP[]; inviteToken: InviteToken };
|
||||
export let form;
|
||||
|
||||
let event: Event;
|
||||
@@ -26,8 +26,7 @@
|
||||
// Use server-side data
|
||||
$: event = data.event;
|
||||
$: rsvps = data.rsvps;
|
||||
$: currentUserId = data.userId;
|
||||
$: isEventCreator = event.user_id === currentUserId;
|
||||
$: isEventCreator = event.is_creator ?? false;
|
||||
|
||||
// Create calendar event object when event data changes
|
||||
$: if (event && browser) {
|
||||
@@ -240,7 +239,6 @@
|
||||
}}
|
||||
class="space-y-4"
|
||||
>
|
||||
<input type="hidden" name="userId" value={currentUserId} />
|
||||
<div>
|
||||
<label for="attendeeName" class=" mb-2 block text-sm font-semibold">
|
||||
{t('event.yourNameLabel')}
|
||||
@@ -374,7 +372,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if attendee.user_id === currentUserId}
|
||||
{#if attendee.is_mine}
|
||||
<form
|
||||
method="POST"
|
||||
action="?/removeRSVP"
|
||||
|
||||
@@ -14,10 +14,7 @@ const config = {
|
||||
// see "split" mode in https://github.com/sveltejs/kit/tree/main/packages/adapter-netlify
|
||||
edge: false,
|
||||
split: false
|
||||
}),
|
||||
csrf: {
|
||||
checkOrigin: false
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user