@@ -2,20 +2,21 @@ class Events < Application
22 base " /api/staff/v1/events"
33
44 # Skip scope check for relevant routes
5- skip_action :check_jwt_scope , only: [:show , :get_metadata , :patch_metadata , :guest_checkin , :add_attendee , :delete_attendee ]
5+ skip_action :check_jwt_scope , only: [:index , : show , :get_metadata , :patch_metadata , :guest_checkin , :add_attendee , :delete_attendee ]
66
77 # Skip actions that requres login
88 # If a user is logged in then they will be run as part of
99 # #set_tenant_from_domain
10- skip_action :determine_tenant_from_domain , only: [:add_attendee , :delete_attendee ]
10+ skip_action :determine_tenant_from_domain , only: [:index , :show , : add_attendee , :delete_attendee ]
1111
1212 # Set the tenant based on the domain
1313 # This allows unauthenticated requests through
1414 # (for public bookings, further checks are done later)
15- @[AC ::Route ::Filter (:before_action , only: [:add_attendee , :delete_attendee ])]
15+ @[AC ::Route ::Filter (:before_action , only: [:index , :show , : add_attendee , :delete_attendee ])]
1616 private def set_tenant_from_domain
1717 if auth_token_present?
18- check_jwt_scope
18+ # guest-scoped tokens have their own access controls, skip the scope check
19+ check_jwt_scope unless user_token.guest_scope?
1920 determine_tenant_from_domain
2021 else
2122 domain = request.hostname.as?(String )
@@ -134,7 +135,16 @@ class Events < Application
134135 period_start = Time .unix(starting)
135136 period_end = Time .unix(ending)
136137
137- calendars = matching_calendar_ids(calendars, zone_ids, system_ids, allow_default: true )
138+ # For unauthenticated (public) access, require system_ids or zone_ids
139+ # and use the resource calendar identity for calendar API calls.
140+ # Raw calendar IDs are ignored to prevent arbitrary calendar access.
141+ unless auth_token_present?
142+ raise AC ::Route ::Param ::MissingError .new(" system_ids or zone_ids required for public event listing" , " system_ids" , " String" ) unless system_ids.presence || zone_ids.presence
143+ calendars = nil
144+ end
145+ user_email = user.email if auth_token_present?
146+
147+ calendars = matching_calendar_ids(calendars, zone_ids, system_ids, allow_default: auth_token_present?)
138148
139149 Log .context.set(calendar_size: calendars.size.to_s)
140150 return [] of PlaceCalendar ::Event unless calendars.size > 0
@@ -143,7 +153,7 @@ class Events < Application
143153 requests = [] of HTTP ::Request
144154 mappings = calendars.map { |calendar_id , system |
145155 request = client.list_events_request(
146- user.email ,
156+ user_email || calendar_id ,
147157 calendar_id,
148158 period_start: period_start,
149159 period_end: period_end,
@@ -156,7 +166,7 @@ class Events < Application
156166 {request, calendar_id, system}
157167 }
158168
159- responses = client.batch(user.email , requests)
169+ responses = client.batch(user_email || calendars.keys.first , requests)
160170
161171 # Process the response (map requests back to responses)
162172 errors = 0
@@ -166,7 +176,7 @@ class Events < Application
166176 results = [] of Tuple (String , PlaceOS ::Client ::API ::Models ::System ?, PlaceCalendar ::Event )
167177 mappings.each do |(request , calendar_id , system )|
168178 begin
169- results.concat client.list_events(user.email , responses[request]).map { |event | {calendar_id, system, event} }
179+ results.concat client.list_events(user_email || calendar_id , responses[request]).map { |event | {calendar_id, system, event} }
170180 rescue error
171181 (error.message =~ /MailboxConcurrency/i ? calendar_rate_limit : calendar_other_error) << calendar_id
172182 errors += 1
@@ -310,6 +320,9 @@ class Events < Application
310320 parent_meta = true if metadata
311321 end
312322
323+ # For unauthenticated requests, only return public events
324+ next unless auth_token_present? || metadata.try(& .permission.public?)
325+
313326 if system.nil?
314327 if cal_id = event_resources[event.id.as(String )]?
315328 system = system_emails[cal_id]?
@@ -1399,7 +1412,7 @@ class Events < Application
13991412 event_id = original_id
14001413
14011414 # Guest access
1402- if user_token.guest_scope?
1415+ if auth_token_present? && user_token.guest_scope?
14031416 guest_event_id, guest_system_id = user.roles
14041417 system_id ||= guest_system_id
14051418 raise Error ::Forbidden .new(" guest #{ user_token.id } attempting to view an event they are not associated with" ) unless system_id == guest_system_id
@@ -1409,7 +1422,7 @@ class Events < Application
14091422 # Need to grab the calendar associated with this system
14101423 system = placeos_client.systems.fetch(system_id)
14111424 cal_id = system.email
1412- user_email = user_token.guest_scope? ? cal_id : user.email
1425+ user_email = (!auth_token_present? || user_token.guest_scope?) ? cal_id : user.email
14131426 raise AC ::Route ::Param ::ValueError .new(" system '#{ system.name } ' (#{ system_id } ) does not have a resource email address specified" , " system_id" ) unless cal_id
14141427
14151428 raise Error ::InconsistentState .new(" user_email must be present" ) unless user_email
@@ -1427,14 +1440,23 @@ class Events < Application
14271440 end
14281441 end
14291442
1430- if user_token.guest_scope?
1443+ if auth_token_present? && user_token.guest_scope?
14311444 raise Error ::Forbidden .new(" guest #{ user_token.id } attempting to view an event they are not associated with" ) unless guest_event_id.in?({original_id, event_id, event.recurring_event_id}) && system_id == guest_system_id
14321445 end
14331446
14341447 metadata = get_event_metadata(event, system_id, search_recurring: true )
1448+
1449+ # For unauthenticated requests, only allow access to public events
1450+ unless auth_token_present?
1451+ raise Error ::Forbidden .new(" event is not publicly accessible" ) unless metadata.try(& .permission.public?)
1452+ end
1453+
14351454 parent_meta = ! metadata.try & .for_event_instance?(event, client.client_id)
14361455 StaffApi ::Event .augment(event, cal_id, system, metadata, parent_meta)
14371456 else
1457+ # Personal calendar access requires authentication
1458+ raise Error ::Forbidden .new(" system_id is required for public event access" ) unless auth_token_present?
1459+
14381460 # Need to confirm the user can access this calendar
14391461 user_cal = user_cal.try & .downcase
14401462 if user_cal == user.email
0 commit comments