konrad 4e05fb1c6a Paint a destructive Dialog button with Nextcloud's error text color.
The theme's error background is a pale pink, so a white label looked disabled.
2026-09-29 02:53:59 +02:00

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

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 <environment-variables>. 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).

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 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.

  • 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
S
Description
Go module for nextcloud ExApps
Readme
172 KiB
Languages
Go 96.6%
JavaScript 3.4%