# go-nc-exapp Shared Go library for Nextcloud ExApp Services: AppAPI authentication, OCS JSON calls, per-user ExApp preferences, Notifications, Users and Groups, an optional Required Groups Access Gate, an optional App navigation shell, and a Dialog. Import: `gitea.neitzel.de/konrad/go-nc-exapp` (package `gonexapp`). ## Scope **Included** - **Credentials** — Nextcloud base URL, AppAPI secret, and requesting user identity - **AuthHeaders** — outbound AppAPI authorization for OCS and other Nextcloud calls - **WithUser** — credentials scoped to a specific requesting user - **UserFromRequest** — extract the requesting user from inbound AppAPI-proxied requests - **OCSClient** — authenticated OCS calls that always append `format=json` - **AppAPIPreferences** — parameterized get/set of a string ExApp preference (caller supplies app id and key) - **AppAPINotifications** — `Send` (Recipient = Credentials user) and `SendTo` (explicit Recipient); Subject required; Message, Link, and rich-object params optional. AppAPI’s notification OCS is limited (no actions, no custom icon) - **Groups** — Users and Groups reads: `UserGroups`, `GroupMembers`, `ListGroups` (no search/paging). Directory calls (`GroupMembers` / `ListGroups`) run as the Credentials user and need an admin or subadmin - **Access Gate** — optional Required Groups enforcement (`Wrap` + `Check`), English denied HTML for browsers (200 + `frame-ancestors 'self'`), positive membership cache; default skip for lifecycle paths and **`/js/`** top-menu scripts; env helpers for `REQUIRED_GROUPS` / `REQUIRED_GROUPS_CACHE_SECONDS` - **Top Menu visibility** — `TopMenuAdminRequired` helper for deploy env `TOP_MENU_ADMIN_REQUIRED` (`0` / `1` for AppAPI top-menu OCS) - **App navigation** — optional Files-style shell (`AppNavigation.Handler`). The ExApp supplies the tree, the page for each item, and an optional header. The selected item is the `item` query parameter (override with `SelectKey`). Other query parameters, including a Visit folder, are left in place. By default the shell loads the Nextcloud theme stylesheets, paints `--image-background`, and copies the surrounding page’s `data-theme-*` markers; set `DisableTheme` to skip the stylesheets. An item may set `Icon` to a same-origin image URL. `NextcloudIcons` names the core SVGs Nextcloud already serves (`NextcloudIcons.Folder`, `NextcloudIcons.Password`, and the rest). The shell includes the Dialog. Not mounting the handler keeps the ExApp’s own page. Lifecycle routes, HaRP startup, and Top Menu registration stay in the ExApp. - **Dialog** — `DialogHTML()` is the modal for a Message, a Confirm, or a Prompt. Insert it on a page the ExApp renders itself; App navigation already includes it. The page calls `exappDialog.message`, `exappDialog.confirm`, or `exappDialog.prompt` and waits for the choice. Fixed buttons are the English words OK and Cancel. The agreeing button’s word may be replaced. The choice stays in the page. **Excluded** - ExApp lifecycle HTTP routes (`/heartbeat`, `/enabled`, …) — the Gate *skips* these by default but does not implement them - HaRP listen / `serve()` and unix-socket bootstrap - Top-menu, script, and iframe UI registration - WebDAV and file storage (see **go-nc-files**) - **Visit** folder resolution (see **go-nc-files**) - Fan-out Notifications (`SendToGroup` / `SendToAdmins`) - A Library default privileged / admin user for directory OCS (callers who need that use `WithUser` themselves) - CheckDNS (or any ExApp) wiring for Notifications or Groups — products opt in separately ## Usage ```go cred := gonexapp.Credentials{ BaseURL: "https://nextcloud.example", AppID: "myexapp", AppVersion: "0.1.0", AAVersion: "1.0.0", AppSecret: os.Getenv("APP_SECRET"), UserID: "alice", } prefs := gonexapp.NewAppAPIPreferences(cred, "myexapp", "savedDefault") value, err := prefs.Get() err = gonexapp.NewAppAPINotifications(cred).Send(gonexapp.Notification{Subject: "Job finished"}) members, err := gonexapp.NewGroups(cred).GroupMembers("CheckDNS") groupsEnv, groupsSet := os.LookupEnv("REQUIRED_GROUPS") groups := gonexapp.ResolveRequiredGroups(groupsEnv, groupsSet, nil) ttl := gonexapp.ParseCacheSeconds(os.Getenv("REQUIRED_GROUPS_CACHE_SECONDS"), gonexapp.DefaultCacheSeconds) handler := gonexapp.AccessGate{Cred: cred, Groups: groups, CacheTTL: ttl}.Wrap(inner) ``` Each ExApp chooses its own preference keys; this library does not hardcode product-specific names. Declare `REQUIRED_GROUPS` and `REQUIRED_GROUPS_CACHE_SECONDS` in the ExApp `info.xml` so Deploy options can set them. The Gate skips `/heartbeat`, `/enabled`, `/init`, and any path under **`/js/`** (AppAPI top-menu bootstrap). Serve the registered top-menu script under `/js/…` so a non-member still loads it and can show Denied UI in the Nextcloud shell. API routes stay gated. Denied HTML is **200** with `Content-Security-Policy: … frame-ancestors 'self'`. Without that header AppAPI’s proxy defaults to `frame-ancestors 'none'` and a denied iframe stays blank. Non-HTML denials remain **403**. ### Top Menu visibility (`TOP_MENU_ADMIN_REQUIRED`) Declare in `info.xml` under ``. At enable time the ExApp reads the env and passes `"0"` or `"1"` to AppAPI’s top-menu OCS `adminRequired`. Only `0` and `1` are valid; anything else falls back to `DefaultTopMenuAdminRequired` (`true` → admins only). ```go adminRequired := gonexapp.TopMenuAdminRequired( os.Getenv(gonexapp.EnvTopMenuAdminRequired), gonexapp.DefaultTopMenuAdminRequired, ) // use adminRequired in POST …/ui/top-menu when registering the menu entry ``` **Applying a change:** AppAPI registers the top menu when the ExApp receives `PUT /enabled?enabled=1`. Changing the deploy env alone does not update the menu entry. 1. Set the new value in Deploy options (UI) or `occ app_api:app:register … --env TOP_MENU_ADMIN_REQUIRED=…` / update deploy config. 2. Recreate or restart the ExApp container so the new env is present. 3. Re-run lifecycle: disable then enable the ExApp (UI or `occ app_api:app:disable` / `app_api:app:enable`), or `occ app_api:app:update … -e` after an image/info update. Route `access_level` in `info.xml` is separate and only changes when AppAPI re-reads `info.xml` on register/update — not via this env. Runnable package examples: `go test -run Example`. ## Domain language See [CONTEXT.md](./CONTEXT.md) for AppAPI credentials, Requesting user, Recipient, ExApp preference, Notification, OCS, Required Groups, Users and Groups, Access Gate, App navigation, and Top Menu visibility terminology. ## Testing Unit tests use `httptest` fake OCS servers. No live Nextcloud is required for Library CI. ## Related - **go-nc-files** — WebDAV, Working Folder, Saved Default, Visit resolution - Workspace ADR 0013 — extraction from CheckDNS - Workspace ADR `docs/adr/go-nc-exapp/0001-required-groups-access-gate.md` — Access Gate decisions - Workspace ADR `docs/adr/go-nc-exapp/0002-users-and-groups-as-requesting-user.md` — directory OCS as Requesting user - Workspace ADR `docs/adr/go-nc-exapp/0003-app-navigation-shell.md` — App navigation shell