Move the calendar onto the shared session cookie
Step 4 of docs/calendar-auth-migration.md, and the close of
DEFERRED_SECURITY.md item 1: no calendar route reads sessionId/sessionKey from
the query string any more, so a live credential no longer travels through
access logs, browser history and Referer headers.
The four write routes sit behind requireAppAccess('calendar'), which also
narrows who may edit from "any activated @nachklang.art account" to an
explicit per-user permission. They answer 401 signed out and 403 without the
permission, where they previously answered 403 for both.
The three read routes cannot use the middleware: one URL serves an anonymous
visitor, an iCal subscription holding a shared password, and a signed-in
editor who should see drafts. They resolve the session optionally instead, and
a signed-in user without the calendar permission is treated as anonymous
rather than refused - so they keep the public calendar access anyone has.
That public calendar staying anonymous is load-bearing: nachklang.art reads it
to show the next upcoming event. It is now pinned at both the password-table
and the route level, and so is the rule that a shared password can never be
used to write.
credentials.service.ts loses its session half and becomes the password table
it always wanted to be. The shared passwords survive only for iCal clients,
which cannot send a cookie.
Writes record the author as an admin user id and no longer have a legacy int
to write, which is what migration 003 makes room for.
/calendar/users/* is left in place: nothing calls it and a session it mints
opens nothing, but they are still live password-accepting endpoints, so
removing them belongs with the rest of the legacy path in step 5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ import {Event} from './event.interface.js';
|
||||
import * as EventService from './events.service.js';
|
||||
import * as iCalService from './icalgenerator.service.js';
|
||||
import * as CredentialService from './credentials.service.js';
|
||||
import * as UserService from '../users/users.service.js';
|
||||
import {requireAppAccess, resolveAccess, AdminAccess} from '../../admin/admin.middleware.js';
|
||||
import {Guid} from 'guid-typescript';
|
||||
import logger from '../../../middleware/logger.js';
|
||||
|
||||
@@ -29,6 +29,44 @@ export const calendarNames = new Map<string, any>([
|
||||
['birthdays', {id: 5, name: 'Nachklang_birthday_calendar'}]
|
||||
]);
|
||||
|
||||
/**
|
||||
* The gate on everything that writes. Step 4 of
|
||||
* docs/calendar-auth-migration.md replaced a sessionId/sessionKey pair in the
|
||||
* query string (DEFERRED_SECURITY.md item 1) with the same session cookie the
|
||||
* other three apps use, and "any activated @nachklang.art account" with an
|
||||
* explicit per-user calendar permission.
|
||||
*/
|
||||
const requireCalendarAccess = requireAppAccess('calendar');
|
||||
|
||||
/** Set by requireCalendarAccess; the writer's admin identity. */
|
||||
const adminOf = (res: Response): AdminAccess => res.locals.admin as AdminAccess;
|
||||
|
||||
/**
|
||||
* Resolves a signed-in calendar user for the *read* routes, or null.
|
||||
*
|
||||
* Reads cannot use the middleware: the same URL serves an anonymous visitor
|
||||
* (the public calendar the website polls), someone holding a shared password
|
||||
* (an iCal subscription), and a signed-in editor who should see drafts. So it
|
||||
* answers "who is this, if anyone?" instead of refusing the request, and each
|
||||
* handler decides what that means.
|
||||
*
|
||||
* A failure to reach the admin database is swallowed for the same reason the
|
||||
* name lookup in events.service.ts swallows one: it must not be able to take
|
||||
* the anonymous public calendar down.
|
||||
*/
|
||||
const signedInEditor = async (req: Request): Promise<AdminAccess | null> => {
|
||||
try {
|
||||
const access = await resolveAccess(req);
|
||||
if (!access || access.disabled || !access.apps.includes('calendar')) {
|
||||
return null;
|
||||
}
|
||||
return access;
|
||||
} catch (e: any) {
|
||||
logger.warn('Calendar: could not resolve the session, continuing as anonymous: ' + e.message);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Controller Definitions
|
||||
@@ -39,7 +77,10 @@ export const calendarNames = new Map<string, any>([
|
||||
* /calendar/events/{calendar}/json:
|
||||
* get:
|
||||
* summary: Get all events from a specific calendar in JSON format
|
||||
* description: Returns all events from the specified calendar in JSON format. Authentication required.
|
||||
* description: >
|
||||
* Returns the calendar's events. The public calendar is open to everyone; the
|
||||
* others need either a signed-in account with the calendar permission - which
|
||||
* also unlocks drafts - or the calendar's shared password.
|
||||
* tags:
|
||||
* - calendar
|
||||
* parameters:
|
||||
@@ -51,20 +92,10 @@ export const calendarNames = new Map<string, any>([
|
||||
* enum: [public, members, choir, management]
|
||||
* description: The name of the calendar to get events from
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* - in: query
|
||||
* name: password
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Password for calendar access (if not using session authentication)
|
||||
* description: The calendar's shared password, for callers with no account
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
@@ -109,10 +140,7 @@ eventsRouter.get('/:calendar/json', async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Get request params
|
||||
let calendarName: string = req.params.calendar as string ?? '';
|
||||
let sessionId: string = req.query.sessionId as string ?? '';
|
||||
let sessionKey: string = req.query.sessionKey as string ?? '';
|
||||
let password: string = req.query.password as string ?? '';
|
||||
let ip: string = req.socket.remoteAddress ?? '';
|
||||
|
||||
if (calendarName.length < 1) {
|
||||
res.status(400).send({'message': 'Please state the name of the calendar you want events from.'});
|
||||
@@ -126,23 +154,19 @@ eventsRouter.get('/:calendar/json', async (req: Request, res: Response) => {
|
||||
|
||||
let calendarId: number = calendarNames.get(calendarName)!.id;
|
||||
|
||||
let user = await UserService.checkSession(sessionId, sessionKey, ip);
|
||||
const editor = await signedInEditor(req);
|
||||
|
||||
// If no user was found, check if the password gives access to the calendar
|
||||
if(user === null || !user.isActive) {
|
||||
if (! await CredentialService.hasAccess(calendarName, sessionId, sessionKey, password, ip)) {
|
||||
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
|
||||
return;
|
||||
}
|
||||
// Not signed in: fall back to the shared password for this calendar.
|
||||
if (!editor && ! await CredentialService.hasAccess(calendarName, password)) {
|
||||
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
|
||||
return;
|
||||
}
|
||||
|
||||
let events: Event[];
|
||||
|
||||
if(user?.isActive) {
|
||||
events = await EventService.getAllEventsAdmin(calendarId);
|
||||
} else {
|
||||
events = await EventService.getAllEvents(calendarId);
|
||||
}
|
||||
// Editors get the admin view (drafts included, calendar includes ignored);
|
||||
// everyone else gets published events only.
|
||||
let events: Event[] = editor
|
||||
? await EventService.getAllEventsAdmin(calendarId)
|
||||
: await EventService.getAllEvents(calendarId);
|
||||
|
||||
// Send the events back
|
||||
res.status(200).send(events);
|
||||
@@ -170,20 +194,10 @@ eventsRouter.get('/:calendar/json', async (req: Request, res: Response) => {
|
||||
* enum: [public, members, choir, management]
|
||||
* description: The name of the calendar to get the next event from
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* - in: query
|
||||
* name: password
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Password for calendar access (if not using session authentication)
|
||||
* description: The calendar's shared password, for callers with no account
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
@@ -242,10 +256,7 @@ eventsRouter.get('/:calendar/json/next', async (req: Request, res: Response) =>
|
||||
try {
|
||||
// Get request params
|
||||
let calendarName: string = req.params.calendar as string ?? '';
|
||||
let sessionId: string = req.query.sessionId as string ?? '';
|
||||
let sessionKey: string = req.query.sessionKey as string ?? '';
|
||||
let password: string = req.query.password as string ?? '';
|
||||
let ip: string = req.socket.remoteAddress ?? '';
|
||||
|
||||
if (calendarName.length < 1) {
|
||||
res.status(400).send({'message': 'Please state the name of the calendar you want events from.'});
|
||||
@@ -259,7 +270,10 @@ eventsRouter.get('/:calendar/json/next', async (req: Request, res: Response) =>
|
||||
|
||||
let calendarId: number = calendarNames.get(calendarName)!.id;
|
||||
|
||||
if (! await CredentialService.hasAccess(calendarName, sessionId, sessionKey, password, ip)) {
|
||||
// Signed in, or holding the calendar's shared password. The password path
|
||||
// is what keeps iCal subscriptions working - a calendar client cannot
|
||||
// send a cookie.
|
||||
if (!await signedInEditor(req) && ! await CredentialService.hasAccess(calendarName, password)) {
|
||||
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
|
||||
return;
|
||||
}
|
||||
@@ -302,20 +316,10 @@ eventsRouter.get('/:calendar/json/next', async (req: Request, res: Response) =>
|
||||
* enum: [public, members, choir, management]
|
||||
* description: The name of the calendar to get events from
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* - in: query
|
||||
* name: password
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Password for calendar access (if not using session authentication)
|
||||
* description: The calendar's shared password, for callers with no account
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success - returns iCal file
|
||||
@@ -365,10 +369,7 @@ eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Get request params
|
||||
let calendarName: string = req.params.calendar as string ?? '';
|
||||
let sessionId: string = req.query.sessionId as string ?? '';
|
||||
let sessionKey: string = req.query.sessionKey as string ?? '';
|
||||
let password: string = req.query.password as string ?? '';
|
||||
let ip: string = req.socket.remoteAddress ?? '';
|
||||
|
||||
if (calendarName.length < 1) {
|
||||
res.status(400).send({'message': 'Please state the name of the calendar you want events from.'});
|
||||
@@ -382,7 +383,10 @@ eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => {
|
||||
|
||||
let calendarId: number = calendarNames.get(calendarName)!.id;
|
||||
|
||||
if (! await CredentialService.hasAccess(calendarName, sessionId, sessionKey, password, ip)) {
|
||||
// Signed in, or holding the calendar's shared password. The password path
|
||||
// is what keeps iCal subscriptions working - a calendar client cannot
|
||||
// send a cookie.
|
||||
if (!await signedInEditor(req) && ! await CredentialService.hasAccess(calendarName, password)) {
|
||||
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
|
||||
return;
|
||||
}
|
||||
@@ -413,22 +417,11 @@ eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => {
|
||||
* /calendar/events:
|
||||
* post:
|
||||
* summary: Create a new event
|
||||
* description: Creates a new event in the specified calendar. Authentication required.
|
||||
* description: Creates a new event. Requires a signed-in account with the calendar permission.
|
||||
* tags:
|
||||
* - calendar
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* security:
|
||||
* - AdminSessionCookie: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
@@ -495,16 +488,32 @@ eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => {
|
||||
* message:
|
||||
* type: string
|
||||
* example: Required parameters missing
|
||||
* 403:
|
||||
* description: Forbidden - no access to create events
|
||||
* 401:
|
||||
* description: Unauthorized - not signed in
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* example: UNAUTHORIZED
|
||||
* message:
|
||||
* type: string
|
||||
* example: You do not have access to the specified calendar.
|
||||
* example: Anmeldung erforderlich.
|
||||
* 403:
|
||||
* description: Forbidden - the account lacks the calendar permission
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* example: FORBIDDEN
|
||||
* message:
|
||||
* type: string
|
||||
* example: "Für diesen Bereich fehlt dir die Berechtigung."
|
||||
* 500:
|
||||
* description: Server error
|
||||
* content:
|
||||
@@ -522,19 +531,9 @@ eventsRouter.get('/:calendar/ical', async (req: Request, res: Response) => {
|
||||
* type: string
|
||||
* example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3
|
||||
*/
|
||||
eventsRouter.post('/', async (req: Request, res: Response) => {
|
||||
eventsRouter.post('/', requireCalendarAccess, async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Get params
|
||||
let sessionId: string = req.query.sessionId as string ?? '';
|
||||
let sessionKey: string = req.query.sessionKey as string ?? '';
|
||||
let ip: string = req.socket.remoteAddress ?? '';
|
||||
|
||||
let user = await UserService.checkSession(sessionId, sessionKey, ip);
|
||||
|
||||
if (!user?.isActive) {
|
||||
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
|
||||
return;
|
||||
}
|
||||
const admin = adminOf(res);
|
||||
|
||||
if (
|
||||
req.body.calendarId === undefined ||
|
||||
@@ -556,7 +555,9 @@ eventsRouter.post('/', async (req: Request, res: Response) => {
|
||||
endDateTime: new Date(req.body.endDateTime),
|
||||
createdDate: new Date(),
|
||||
location: req.body.location ?? '',
|
||||
createdById: user.userId ?? -1,
|
||||
// LEGACY createdById is deliberately not set: there is no calendar
|
||||
// user id any more, and migration 003 made the column nullable.
|
||||
createdByUserId: admin.id,
|
||||
url: req.body.url ?? '',
|
||||
wholeDay: req.body.wholeDay ?? false,
|
||||
repeatFrequency: req.body.repeatFrequency ?? '',
|
||||
@@ -585,9 +586,11 @@ eventsRouter.post('/', async (req: Request, res: Response) => {
|
||||
* /calendar/events/{eventId}:
|
||||
* put:
|
||||
* summary: Update an existing event
|
||||
* description: Updates an existing event with the provided data. Authentication required.
|
||||
* description: Updates an existing event. Requires a signed-in account with the calendar permission.
|
||||
* tags:
|
||||
* - calendar
|
||||
* security:
|
||||
* - AdminSessionCookie: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: eventId
|
||||
@@ -595,18 +598,6 @@ eventsRouter.post('/', async (req: Request, res: Response) => {
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: The ID of the event to update
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
@@ -673,16 +664,32 @@ eventsRouter.post('/', async (req: Request, res: Response) => {
|
||||
* message:
|
||||
* type: string
|
||||
* example: Required parameters missing
|
||||
* 403:
|
||||
* description: Forbidden - no access to update events
|
||||
* 401:
|
||||
* description: Unauthorized - not signed in
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* example: UNAUTHORIZED
|
||||
* message:
|
||||
* type: string
|
||||
* example: You do not have access to the specified calendar.
|
||||
* example: Anmeldung erforderlich.
|
||||
* 403:
|
||||
* description: Forbidden - the account lacks the calendar permission
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* example: FORBIDDEN
|
||||
* message:
|
||||
* type: string
|
||||
* example: "Für diesen Bereich fehlt dir die Berechtigung."
|
||||
* 500:
|
||||
* description: Server error
|
||||
* content:
|
||||
@@ -700,19 +707,9 @@ eventsRouter.post('/', async (req: Request, res: Response) => {
|
||||
* type: string
|
||||
* example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3
|
||||
*/
|
||||
eventsRouter.put('/:eventId', async (req: Request, res: Response) => {
|
||||
eventsRouter.put('/:eventId', requireCalendarAccess, async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Get params
|
||||
let sessionId: string = req.query.sessionId as string ?? '';
|
||||
let sessionKey: string = req.query.sessionKey as string ?? '';
|
||||
let ip: string = req.socket.remoteAddress ?? '';
|
||||
|
||||
let user = await UserService.checkSession(sessionId, sessionKey, ip);
|
||||
|
||||
if (!user?.isActive) {
|
||||
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
|
||||
return;
|
||||
}
|
||||
const admin = adminOf(res);
|
||||
|
||||
if (
|
||||
req.params.eventId === undefined ||
|
||||
@@ -736,7 +733,9 @@ eventsRouter.put('/:eventId', async (req: Request, res: Response) => {
|
||||
createdDate: new Date(),
|
||||
location: req.body.location ?? '',
|
||||
createdBy: req.body.createdBy ?? '',
|
||||
createdById: user.userId ?? -1,
|
||||
// LEGACY createdById is deliberately not set: there is no calendar
|
||||
// user id any more, and migration 003 made the column nullable.
|
||||
createdByUserId: admin.id,
|
||||
url: req.body.url ?? '',
|
||||
wholeDay: req.body.wholeDay ?? false,
|
||||
repeatFrequency: req.body.repeatFrequency ?? '',
|
||||
@@ -768,9 +767,11 @@ eventsRouter.put('/:eventId', async (req: Request, res: Response) => {
|
||||
* /calendar/events/move/{eventId}:
|
||||
* put:
|
||||
* summary: Move an event to a different calendar
|
||||
* description: Moves an existing event to a different calendar. Authentication required.
|
||||
* description: Moves an event to a different calendar. Requires a signed-in account with the calendar permission.
|
||||
* tags:
|
||||
* - calendar
|
||||
* security:
|
||||
* - AdminSessionCookie: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: eventId
|
||||
@@ -778,18 +779,6 @@ eventsRouter.put('/:eventId', async (req: Request, res: Response) => {
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: The ID of the event to move
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
@@ -854,16 +843,32 @@ eventsRouter.put('/:eventId', async (req: Request, res: Response) => {
|
||||
* message:
|
||||
* type: string
|
||||
* example: Required parameters missing
|
||||
* 403:
|
||||
* description: Forbidden - no access to move events
|
||||
* 401:
|
||||
* description: Unauthorized - not signed in
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* example: UNAUTHORIZED
|
||||
* message:
|
||||
* type: string
|
||||
* example: You do not have access to the specified calendar.
|
||||
* example: Anmeldung erforderlich.
|
||||
* 403:
|
||||
* description: Forbidden - the account lacks the calendar permission
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* example: FORBIDDEN
|
||||
* message:
|
||||
* type: string
|
||||
* example: "Für diesen Bereich fehlt dir die Berechtigung."
|
||||
* 500:
|
||||
* description: Server error
|
||||
* content:
|
||||
@@ -881,19 +886,9 @@ eventsRouter.put('/:eventId', async (req: Request, res: Response) => {
|
||||
* type: string
|
||||
* example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3
|
||||
*/
|
||||
eventsRouter.put('/move/:eventId', async (req: Request, res: Response) => {
|
||||
eventsRouter.put('/move/:eventId', requireCalendarAccess, async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Get params
|
||||
let sessionId: string = req.query.sessionId as string ?? '';
|
||||
let sessionKey: string = req.query.sessionKey as string ?? '';
|
||||
let ip: string = req.socket.remoteAddress ?? '';
|
||||
|
||||
let user = await UserService.checkSession(sessionId, sessionKey, ip);
|
||||
|
||||
if (!user?.isActive) {
|
||||
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
|
||||
return;
|
||||
}
|
||||
const admin = adminOf(res);
|
||||
|
||||
if (
|
||||
req.params.eventId === undefined ||
|
||||
@@ -914,7 +909,9 @@ eventsRouter.put('/move/:eventId', async (req: Request, res: Response) => {
|
||||
createdDate: new Date(),
|
||||
location: req.body.location ?? '',
|
||||
createdBy: req.body.createdBy ?? '',
|
||||
createdById: user.userId ?? -1,
|
||||
// LEGACY createdById is deliberately not set: there is no calendar
|
||||
// user id any more, and migration 003 made the column nullable.
|
||||
createdByUserId: admin.id,
|
||||
url: req.body.url ?? '',
|
||||
wholeDay: req.body.wholeDay ?? false,
|
||||
repeatFrequency: req.body.repeatFrequency ?? '',
|
||||
@@ -944,9 +941,11 @@ eventsRouter.put('/move/:eventId', async (req: Request, res: Response) => {
|
||||
* /calendar/events/{eventId}:
|
||||
* delete:
|
||||
* summary: Delete an event
|
||||
* description: Deletes an existing event. Authentication required.
|
||||
* description: Deletes an event. Requires a signed-in account with the calendar permission.
|
||||
* tags:
|
||||
* - calendar
|
||||
* security:
|
||||
* - AdminSessionCookie: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: eventId
|
||||
@@ -954,18 +953,6 @@ eventsRouter.put('/move/:eventId', async (req: Request, res: Response) => {
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: The ID of the event to delete
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session ID for authentication
|
||||
* - in: query
|
||||
* name: sessionKey
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Session key for authentication
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Event deleted successfully
|
||||
@@ -987,16 +974,32 @@ eventsRouter.put('/move/:eventId', async (req: Request, res: Response) => {
|
||||
* message:
|
||||
* type: string
|
||||
* example: Required parameters missing
|
||||
* 403:
|
||||
* description: Forbidden - no access to delete events
|
||||
* 401:
|
||||
* description: Unauthorized - not signed in
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* example: UNAUTHORIZED
|
||||
* message:
|
||||
* type: string
|
||||
* example: You do not have access to the specified calendar.
|
||||
* example: Anmeldung erforderlich.
|
||||
* 403:
|
||||
* description: Forbidden - the account lacks the calendar permission
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* example: FORBIDDEN
|
||||
* message:
|
||||
* type: string
|
||||
* example: "Für diesen Bereich fehlt dir die Berechtigung."
|
||||
* 500:
|
||||
* description: Server error
|
||||
* content:
|
||||
@@ -1014,19 +1017,9 @@ eventsRouter.put('/move/:eventId', async (req: Request, res: Response) => {
|
||||
* type: string
|
||||
* example: 6ec1361c-4175-4e81-b2ef-a0792a9a1dc3
|
||||
*/
|
||||
eventsRouter.delete('/:eventId', async (req: Request, res: Response) => {
|
||||
eventsRouter.delete('/:eventId', requireCalendarAccess, async (req: Request, res: Response) => {
|
||||
try {
|
||||
// Get params
|
||||
let sessionId: string = req.query.sessionId as string ?? '';
|
||||
let sessionKey: string = req.query.sessionKey as string ?? '';
|
||||
let ip: string = req.socket.remoteAddress ?? '';
|
||||
|
||||
let user = await UserService.checkSession(sessionId, sessionKey, ip);
|
||||
|
||||
if (!user?.isActive) {
|
||||
res.status(403).send({'message': 'You do not have access to the specified calendar.'});
|
||||
return;
|
||||
}
|
||||
const admin = adminOf(res);
|
||||
|
||||
if (
|
||||
req.params.eventId === undefined
|
||||
@@ -1046,7 +1039,9 @@ eventsRouter.delete('/:eventId', async (req: Request, res: Response) => {
|
||||
createdDate: new Date(),
|
||||
location: '',
|
||||
createdBy: '',
|
||||
createdById: user.userId ?? -1,
|
||||
// LEGACY createdById is deliberately not set: there is no calendar
|
||||
// user id any more, and migration 003 made the column nullable.
|
||||
createdByUserId: admin.id,
|
||||
url: '',
|
||||
wholeDay: false,
|
||||
repeatFrequency: '',
|
||||
|
||||
Reference in New Issue
Block a user