Skip to content

Conversation

@JW-CH
Copy link
Collaborator

@JW-CH JW-CH commented May 20, 2025

Closes #281

Summary by CodeRabbit

  • New Features

    • Added support for basic authentication per-calendar when fetching calendars.
  • Bug Fixes

    • Normalized webcal → https URLs, improved handling of invalid or unreachable feeds, and clearer error logging.
  • Chores

    • Improved HTTP connection reuse, caching of calendar loads, and enhanced diagnostics to reduce failures.
  • Documentation

    • Updated configuration guide and example env with calendar authentication examples.

✏️ Tip: You can customize this high-level summary in your review settings.

@JW-CH JW-CH added the enhancement New feature or request label May 20, 2025
@3rob3
Copy link
Collaborator

3rob3 commented May 22, 2025

Are we waiting for that person you asked to test this?

@JW-CH
Copy link
Collaborator Author

JW-CH commented May 22, 2025

Are we waiting for that person you asked to test this?

The code should be fine, at least not break anything. Feel free to merge if it looks ok to you :)

@JW-CH JW-CH force-pushed the dev/jw/calendarauth branch from 0caf4a8 to cecb3b4 Compare June 4, 2025 22:01
var appointments = new List<IAppointment>();

var icals = await GetCalendars(_serverSettings.Webcalendars);
List<(string? auth, string url)> cals = _serverSettings.Webcalendars.Select(x => x.Contains(';') ? (x.Split(';')[0], x.Split(';')[1]) : (null, x.ToString())).ToList();
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest that you use the standard auth formatting in URLs: https://<user>:<pass>@host/path, rather that semicolon delimited.

@GyroGearl00se
Copy link

I just tried the dev/jw/calendarauth branch and can confirm that using an ics from Baikal CalDAV works fine.

Here an example of the env (redacted):

Webcalendars="user:password;https://baikal.mydomain.tld/dav.php/calendars/user/default?export"

The URL returns directly the .ics file (In case you wonder why there isn't a .ics in the path 😃 )

Looking forward for it to get merged.

@Buddinski88
Copy link

@JW-CH Will this be merged into the main branch soon? I haven't shared my Google calendars, but I'd still like to be able to view them 😁

@JW-CH JW-CH force-pushed the dev/jw/calendarauth branch from cecb3b4 to 21554bf Compare January 23, 2026 08:49
@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

Warning

Rate limit exceeded

@JW-CH has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 37 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Updated IcalCalendarService to use IHttpClientFactory and ILogger, create per-call HttpClient, support Basic Auth via URL userinfo, convert webcal:// to https://, change GetCalendars to accept (auth, url) tuples, and update docs and example env to show auth-capable calendar URLs.

Changes

Cohort / File(s) Summary
Service: auth & HTTP client
ImmichFrame.Core/Services/IcalCalendarService.cs
Constructor now injects ILogger<IcalCalendarService> and IHttpClientFactory; GetCalendars signature changed to IEnumerable<(string? auth, string url)>; creates an HttpClient per call via factory; converts webcal://https://; extracts per-calendar auth; supports Basic Auth when auth present; adds debug/error logging and logs HTTP status instead of throwing.
Docs: configuration
docs/docs/getting-started/configuration.md
Documentation updated to document calendar basic-auth using URL userinfo format with explicit examples for no-auth and with-auth webcalendar (.ics) URIs.
Env example
docker/example.env
Updated commented WEBCALENDARS example to replace webcal:// sample with an https://user:pass@... example (commented).

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant IcalService as IcalCalendarService
    participant Factory as IHttpClientFactory
    participant Endpoint as CalendarEndpoint
    participant Logger

    Caller->>IcalService: GetCalendars([(auth, url), ...])
    loop per calendar
        IcalService->>Logger: Debug "loading calendar (url)"
        IcalService->>Factory: CreateClient()
        Factory-->>IcalService: HttpClient
        alt auth provided
            IcalService->>IcalService: Add Authorization header (Basic ...)
        end
        IcalService->>Endpoint: GET https://... (with headers)
        alt 200 OK
            Endpoint-->>IcalService: 200 + ICS data
        else non-200
            Endpoint-->>IcalService: Error (status)
            IcalService->>Logger: Error "failed to load (url, status)"
        end
    end
    IcalService-->>Caller: Processed calendars/results
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐇 I hopped through URLs, swapped webcal for HTTPS,
tucked user:pass neatly where headers confess.
I hummed to the logger, the client sprang bright,
calendars answered, returning the light. ✨

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding basic authentication support for calendars as an optional feature.
Linked Issues check ✅ Passed The PR successfully implements HTTP basic authentication support for calendar sources, allowing .ics files with credentials to be loaded, directly addressing issue #281's requirement.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing basic authentication for calendars: service refactoring with auth handling, documentation updates, and example configuration.

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


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

@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: 4

🤖 Fix all issues with AI agents
In `@docs/docs/getting-started/configuration.md`:
- Around line 167-168: Under the "### Metadata" heading remove the placeholder
text "Needs documentation" (or replace it with actual documentation for the
Metadata section) so the published docs don't contain the placeholder; locate
the "### Metadata" heading in the docs and either delete the placeholder line or
replace it with the intended explanatory content, examples, and any relevant
fields or configuration details.

In `@ImmichFrame.Core/Services/IcalCalendarService.cs`:
- Around line 50-51: The current debug log prints calendar.auth which contains
credentials; update the log in IcalCalendarService to never include
calendar.auth value and instead log only whether auth is present (e.g.,
"auth=present" or "auth=none") and still include calendar.url for context;
ensure no other logs print calendar.auth (note calendar.auth is later used for
Basic auth in the block around the Authorization header setup/usage), and remove
any other exposures of the raw credential string in _logger calls or exception
messages.
- Around line 12-14: The field declaration for _appointmentCache uses a generic
ApiCache<List<IAppointment>> but ApiCache is non-generic; update the
IcalCalendarService field to use the non-generic ApiCache type (remove the
generic type argument) and adjust the constructor invocation to new
ApiCache(TimeSpan.FromMinutes(15)) so _appointmentCache is declared as ApiCache
_appointmentCache and initialized accordingly; search for references to
_appointmentCache in IcalCalendarService to ensure any code treating it as
generic (e.g., expecting typed getters/setters) is updated to use the
non-generic API.
- Around line 27-29: The parsing of _serverSettings.Webcalendars into cals
currently uses Split(';') (and calls Split twice), which breaks URLs containing
';' and is inefficient; change the logic that builds List<(string? auth, string
url)> cals to for each entry find the first delimiter index via IndexOf(';') and
then use Substring(0, index) for auth and Substring(index + 1) for url (or set
auth = null and url = whole entry when no delimiter), then pass that list to
GetCalendars — this ensures only the first ';' is treated as the auth separator
and avoids multiple Split calls.

Copy link

@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: 2

🤖 Fix all issues with AI agents
In `@ImmichFrame.Core/Services/IcalCalendarService.cs`:
- Around line 59-60: The HttpRequestMessage and HttpResponseMessage created
inside the loop are not disposed; change the allocations to scoped disposals so
resources are released each iteration—replace the raw new
HttpRequestMessage(...) with a scoped using (e.g., using var request = new
HttpRequestMessage(...)) and likewise wrap the result of SendAsync into a using
(e.g., using var response = await _httpClient.SendAsync(request)) within the
same loop in IcalCalendarService so both HttpRequestMessage and
HttpResponseMessage are disposed after each iteration.
- Around line 55-57: The LogDebug call is logging raw calendar URLs
(calendar.url), which can leak credentials or tokens; update the logging in
IcalCalendarService (the spot using _logger.LogDebug and variables calendar.url,
calendar.auth and httpUrl) to first sanitize the URL by parsing calendar.url and
building a redacted form scheme://host[:port]/path (removing userinfo and
query/fragment) and then use structured logging with named parameters (e.g.,
_logger.LogDebug("Loading calendar: {AuthState} {SanitizedUrl}", authState,
sanitizedUrl)) so no sensitive parts are recorded.

Use standard https://user:pass@host/path format instead of semicolon-
delimited auth. Adds error handling for malformed URLs and properly
unescapes percent-encoded credentials.
@JW-CH JW-CH force-pushed the dev/jw/calendarauth branch from 33376e4 to 99ec031 Compare January 23, 2026 10:15
Copy link

@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: 2

🤖 Fix all issues with AI agents
In `@ImmichFrame.Core/Services/IcalCalendarService.cs`:
- Around line 74-77: The Basic auth header construction in IcalCalendarService
uses Encoding.ASCII on calendar.auth which will corrupt non-ASCII credentials;
change Encoding.ASCII to Encoding.UTF8 when getting bytes for
Convert.ToBase64String so the Authorization header
(request.Headers.Authorization set in the method that builds the request) uses
UTF-8 encoding for calendar.auth and supports international characters.
- Around line 80-88: Add per-calendar exception handling around the HTTP call in
the loop that calls client.SendAsync(request) so a failure for one calendar does
not abort processing of the rest: wrap the await client.SendAsync(request) and
subsequent response handling in a try/catch that catches HttpRequestException
and TaskCanceledException (and optionally a general Exception fallback), log the
error via _logger.LogError with httpUrl and the exception, and continue to the
next calendar; also ensure the HttpResponseMessage is disposed by using a using
(or using declaration) around the response variable when reading
response.Content and adding to icals.
♻️ Duplicate comments (3)
docs/docs/getting-started/configuration.md (1)

169-170: Remove the “Needs documentation” placeholder before release.
Leaving placeholders in published docs is confusing.

ImmichFrame.Core/Services/IcalCalendarService.cs (2)

43-44: Sanitize calendar URLs in logs to avoid leaking secrets.
Raw URLs (and invalid inputs) may include credentials or tokens. Log only a redacted scheme/host/path and use structured logging.

🔧 Proposed fix (sanitized structured logging)
-                _logger.LogError($"Invalid calendar URL: '{x}'");
+                _logger.LogError("Invalid calendar URL: {CalendarUrl}", SanitizeCalendarUrl(x));
-            _logger.LogDebug($"Loading calendar: {(calendar.auth != null ? "[authenticated]" : "no auth")} - {calendar.url}");
+            var authMode = string.IsNullOrEmpty(calendar.auth) ? "no auth" : "basic auth";
+            var safeUrl = SanitizeCalendarUrl(httpUrl);
+            _logger.LogDebug("Loading calendar: {AuthMode} - {CalendarUrl}", authMode, safeUrl);
...
-                _logger.LogError($"Failed to load calendar data from '{httpUrl}' (Status: {response.StatusCode})");
+                _logger.LogError("Failed to load calendar data from {CalendarUrl} (Status: {StatusCode})", safeUrl, response.StatusCode);
// Add helper in the class (outside methods)
private static string SanitizeCalendarUrl(string? raw)
{
    if (string.IsNullOrWhiteSpace(raw)) return "[empty-url]";
    return Uri.TryCreate(raw, UriKind.Absolute, out var uri)
        ? $"{uri.Scheme}://{uri.Host}{(uri.IsDefaultPort ? "" : $":{uri.Port}")}{uri.AbsolutePath}"
        : "[invalid-url]";
}

Also applies to: 68-88


72-83: Dispose HTTP request/response objects to prevent resource leaks.
Both HttpRequestMessage and HttpResponseMessage are IDisposable and should be scoped.

🔧 Proposed fix
-            var request = new HttpRequestMessage(HttpMethod.Get, httpUrl);
+            using var request = new HttpRequestMessage(HttpMethod.Get, httpUrl);
...
-            HttpResponseMessage response = await client.SendAsync(request);
+            using var response = await client.SendAsync(request);
🧹 Nitpick comments (1)
docs/docs/getting-started/configuration.md (1)

164-168: Add a note to URL‑encode special characters in credentials.
If usernames/passwords include @, :, or spaces, they must be percent‑encoded to keep the URL parseable.

Copy link

@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: 1

🤖 Fix all issues with AI agents
In `@docs/docs/getting-started/configuration.md`:
- Around line 164-167: Replace the misleading "With Auth" Google Calendar
example that shows basic auth in the URL
(https://username:password@calendar.google.com/...) with either a
provider-agnostic Basic Auth example (e.g.,
https://username:password@example.com/path.ics) or add a caveat next to the
Google Calendar example stating that Google Calendar does not support basic auth
in ICS URLs and requires unauthenticated secret-token URLs or OAuth; update the
example text near the "No Auth" / "With Auth" lines so it clearly distinguishes
provider support and links auth behavior to provider-specific requirements.

@JW-CH
Copy link
Collaborator Author

JW-CH commented Jan 23, 2026

@JW-CH Will this be merged into the main branch soon? I haven't shared my Google calendars, but I'd still like to be able to view them 😁

@Buddinski88 Unfortunately, Google Calendar does not support Basic Authentication for .ics feeds. This means even when this PR gets merged and enables Basic Auth for calendar URLs, it won't work with Google Calendar.

Google Calendar provides "secret" .ics URLs that work without authentication (they're not publicly shared, but anyone with the link can access them)

@Buddinski88
Copy link

@JW-CH did you test that with Google? I used the “secret” .ics link this morning and it didn't work to load the calendar. There should be several calendars possible, right?

Settings.yml

General:
  AuthenticationSecret: 'qC1Y9nicjLGgXkEM9A1kQdxqIA2waRAtORNma2VhvM'
  RenewImagesDuration: 30
  Webcalendars:
    - https://calendar.google.com/calendar/ical/**group.calendar.google.com/private-***/basic.ics
    - https://calendar.google.com/calendar/ical/**group.calendar.google.com/private-***/basic.ics
    - https://calendar.google.com/calendar/ical/**group.calendar.google.com/private-***/basic.ics
...

@JW-CH
Copy link
Collaborator Author

JW-CH commented Jan 23, 2026

@Buddinski88

yes, this is working for me:

Webcalendars:
    - https://calendar.google.com/calendar/ical/mymail%40gmail.com/private-xxxxxxxxxxxxxxxxxxxxxxxx/basic.ics

Looks like you are using a group-calendar?

@Buddinski88
Copy link

@JW-CH exactly. I use a total of five calendars, which I have shared with my wife, so probably group calendars as well. I assume that's not supported yet? 😁

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Request: Add Calendar Login

6 participants