diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 93ac9f8..de38149 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -4,7 +4,7 @@ A self-hosted CalDAV, CardDAV, and WebDAV server written in Go, backed by a filesystem store, with calendar/address book sharing grants tracked in a small SQLite database. HTTP Basic Auth (bcrypt) with per-user isolated collections. A small server-rendered web UI (templ + Tailwind + htmx) at -`/ui/` lets users log in and manage their shares. +`/web/` lets users log in and manage their shares. ## Build, test, lint @@ -120,10 +120,13 @@ Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`, the caldav/carddav backends query the shares tables on every request (no caching), changes take effect immediately without restarting the server. -- `internal/web` — the web UI, mounted at `/ui/` in `cmd/server/main.go` - (`web.NewServer(cfg, st, dbase, logger).Handler(webstatic.FS())`), - entirely separate from `internal/auth`'s Basic Auth: logins go through - `/ui/login` (username/password checked against `cfg.Users` the same way +- `internal/web` — the web UI, mounted at `/web/` in `cmd/server/main.go` + (`mux.Handle("/web/", http.StripPrefix("/web", web.NewServer(cfg, st, + dbase, logger).Handler(webstatic.FS())))`, so `Server.Handler`'s own + routes are all unprefixed — `/login`, `/`, `/shares/...` — and only the + outer mux adds the `/web` prefix), entirely separate from `internal/auth`'s + Basic Auth: logins go through + `/web/login` (username/password checked against `cfg.Users` the same way Basic Auth does, via bcrypt) and issue an opaque random session token stored in the `web_sessions` SQLite table (`db.CreateSession`/ `SessionUser`/`DeleteSession`, see `internal/db/sessions.go`), set as an @@ -135,7 +138,7 @@ Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`, (`SharesOfCalendar`/`SharesOfAddressBook`) and what's shared with them (`CalendarsSharedWith`/`AddressBooksSharedWith`). `internal/web/shares.go` handles POST (create/update share) and DELETE (revoke) at - `/ui/shares/{calendar,addressbook}`, re-rendering just the affected + `/web/shares/{calendar,addressbook}`, re-rendering just the affected resource card for htmx's `hx-swap="outerHTML"`; it always checks `ownsResource` first so a user can only share resources actually configured for their own account (never someone else's, even via a diff --git a/.gitignore b/.gitignore index c9a3462..d3a5bd5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ data/ config.yaml web/node_modules/ +/bin/ diff --git a/README.md b/README.md index ebca317..0315381 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A self-hosted **CalDAV**, **CardDAV**, and **WebDAV** server written in Go. - Per-user isolated collections - **Calendar/address book sharing** — grant other users read or write access to your calendars/address books -- **Web UI** — a small dashboard (login, manage shares) at `/ui/`, built +- **Web UI** — a small dashboard (login, manage shares) at `/web/`, built with templ + Tailwind + htmx - Auto-discovery via `/.well-known/caldav` and `/.well-known/carddav` - Optional **TLS** (or use a reverse proxy) @@ -177,17 +177,17 @@ Read-only shares reject any write (PUT/DELETE) with `403 Forbidden`. ## Web UI -A small server-rendered dashboard is served at `/ui/` (separate from the +A small server-rendered dashboard is served at `/web/` (separate from the DAV endpoints, which stay on HTTP Basic Auth): -- **Login** (`/ui/login`) — cookie-based session, stored server-side in +- **Login** (`/web/login`) — cookie-based session, stored server-side in `nidus.db` (`web_sessions` table), independent of DAV Basic Auth. -- **Dashboard** (`/ui/`) — lists your own calendars/address books, who +- **Dashboard** (`/web/`) — lists your own calendars/address books, who they're shared with, and any resources other users have shared with you. - **Share management** — add/remove shares directly from the dashboard (same effect as `nidusctl`); updates happen in place via [htmx](https://htmx.org/) without a full page reload. -- **Logout** (`/ui/logout`). +- **Logout** (`/web/logout`). Implementation: [templ](https://templ.guide/) for type-safe Go HTML templates, [Tailwind CSS v4](https://tailwindcss.com/) for styling, and diff --git a/cmd/server/main.go b/cmd/server/main.go index 240c663..f2cdcfd 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -137,8 +137,9 @@ func buildMux( mux := http.NewServeMux() // Web UI (own cookie-based auth, not Basic Auth) — dashboard, login, - // share management. - mux.Handle("/ui/", webUI.Handler(webstatic.FS())) + // share management. Mounted at "/web/"; /cal/, /card/, /files/ keep + // their own dedicated prefixes. + mux.Handle("/web/", http.StripPrefix("/web", webUI.Handler(webstatic.FS()))) // /.well-known/ redirects for auto-discovery mux.HandleFunc("/.well-known/caldav", func(w http.ResponseWriter, r *http.Request) { @@ -256,6 +257,6 @@ const welcomePage = `
%s/.well-known/carddavAuthentication: HTTP Basic Auth
- +