ServeAttachment checks perm.CanRead(unitType) which fails for
licensed read-only access on private repos. Now skips the check
when LicensedReadOnly is set in context (from RepoAssignment).
This allows Joomla/WordPress clients with valid dlid= params to
download release files from private licensed repos.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RepoAssignment now checks for dlid/key/download_key query params
when licensing is enabled. Anonymous Joomla/WordPress clients with
valid license keys can access release download routes on private
repos without being signed in.
Access flow for licensed private repos:
- Anonymous + no key → 403 (styled page)
- Anonymous + valid dlid → access granted (CheckDownloadGating validates)
- Signed in + no membership → access granted (releases visible, downloads hidden)
- Org member → full access
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add templates/status/403.tmpl mirroring the 404 page design with
a centered error message and sign-in button for anonymous users.
New Forbidden() method on Context renders the styled 403 template
for HTML requests, falls back to plain text for API/non-HTML.
RepoAssignment now calls ctx.Forbidden() instead of raw HTTPError
for both anonymous and signed-in users without access.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both anonymous and signed-in users now get 403 Access Denied when
accessing a private repo they lack permission for. Previously
anonymous users got 404 which hid the repo's existence.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When licensing is enabled on a private repo, signed-in users who
are not repo members can now view the releases page (with downloads
hidden). The RepoAssignment permission check detects licensing and
grants read-only access instead of returning 403.
This enables the commercial pattern: private source code, but
release notes visible to any authenticated user. Download files
are gated by license key via HideReleaseDownloads.
Anonymous users still get 404 (no information leak).
Non-licensed private repos still return 403 for non-members.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update feed endpoints (updates.xml, dolibarr.json, wordpress.json,
packages.json, prestashop.xml, drupal.xml, whmcs.json, changelog.xml)
now use RepoAssignmentPublicFeed instead of the full RepoAssignment.
The lightweight loader fetches the repo by owner/name without checking
user permissions. Feed handlers gate access via license keys, not repo
membership. This allows private repos to serve update feeds to
anonymous Joomla/WordPress/Composer clients with valid license keys.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-in users who lack permission to a private repo now see a
403 "You do not have permission" instead of a misleading 404.
Anonymous users still get 404 to prevent repo enumeration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PrestaShop: GET /updates/prestashop.xml — module update XML with
name, version, download URL, author, SHA256. Serves stable only.
Drupal: GET /updates/drupal.xml — update status XML per Drupal API
spec. Includes project metadata, all releases with status, download
links, SHA256. Uses TargetVersion config for api_version field.
WHMCS: GET /updates/whmcs.json — simple JSON with latest stable
version, download URL (with dlid), changelog, author. License key
embedded in download URL when provided.
All three use ResolveReleaseStream for manual/auto stream mapping,
readSHA256FromSidecar for integrity hashes, and extractVersion with
stream-name tag fallback.
Routes registered under the update server group alongside Joomla,
Dolibarr, WordPress, and Composer feeds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Composer feed: new endpoint GET /updates/packages.json serving
Composer/Packagist-compatible packages.json. Includes version,
dist URL with SHA256, authors, PHP requirement. License key
embedded in download URL when provided.
Menu visibility: Actions and Licenses tabs in repo header now
require .IsSigned — anonymous users no longer see tabs they
can't access. Previously the tabs were visible but clicking
redirected to login (confusing UX).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#406: Add KeyPrefix field to UpdateStreamConfig. GenerateKeyString
now accepts a prefix parameter, looked up from org config. Default
remains MOKO if not set. Auto-uppercased, max 20 chars.
#408: Move "New Package" button into the packages header bar,
right-aligned. Uses details/summary pattern — clicking the button
expands the create form below. Cleaner layout on both repo and org.
#409: Add open-in-new-tab button (external link icon) next to every
copy button on feed URLs. All four feeds: Joomla XML, Dolibarr JSON,
WordPress JSON, Changelog XML.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Release gating: HideReleaseDownloads now checks download_gating
setting in addition to feed_visibility. When licensing is enabled
and download_gating != "none", anonymous users see "Sign in to
download" instead of download links on the release page.
Actions: changed from optSignIn to reqSignIn on the repo actions
route group. Anonymous users can no longer view CI/CD runs, logs,
or artifacts. This is a MokoGitea policy override — upstream Gitea
allows public actions on public repos.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AI migration 339: replaced with noopMigration placeholder
- feed/file.go: add missing comma in struct literal
- license_key.go: remove unused org_model import
These were being applied as server-side hotfixes on every deploy.
Now committed to dev so they persist through merges.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The repo licenses route used optSignIn (login optional), allowing
anonymous users to view license packages and keys. Changed to
reqSignIn to require authentication.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ParentOrgID field to User model for org hierarchy. Parent orgs
can have child orgs, enabling enterprise structures like
MokoConsulting → client orgs.
Model changes:
- ParentOrgID int64 on User (INDEX, DEFAULT 0)
- GetChildOrgs, GetAncestorOrgIDs, GetParentOrg helpers
- Max 10 hierarchy levels with cycle detection
License integration:
- ListLicensePackagesWithAncestors — shows packages from parent orgs
- ListLicenseKeysWithAncestors — shows keys from parent orgs
- SearchLicenseKeysWithAncestors — searches across hierarchy
- Master keys from parent orgs validate for child org repos
UI:
- Parent org dropdown in org settings (owners/admins only)
- Shows all orgs user owns except self
Migration v341: adds parent_org_id column to user table.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add release_stream_map table for explicitly assigning releases to
update streams. When a mapping exists, it overrides automatic tag
detection. When absent, falls back to tag name/suffix matching.
New model: ReleaseStreamMap with SetReleaseStream, GetReleaseStream,
ResolveReleaseStream (manual first, auto fallback).
UI: stream selector dropdown on release create/edit page, shown when
licensing is enabled. Options: auto-detect (default) or any
configured stream (stable, release-candidate, beta, etc.).
All three feed generators (Joomla, Dolibarr, WordPress) now use
ResolveReleaseStream instead of MatchStreamFromTag.
Migration v340 updated with release_stream_map table creation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
isStreamName was checking the extracted version (empty for stream
tags) instead of the original tag name. Now checks rel.TagName
directly, and also falls through when extractVersion returns empty.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When tags are stream names, extractVersion falls back to finding a
version pattern (digits.digits.digits) anywhere in the release title.
Handles titles like "Package - MokoWaaS (VERSION: 02.31.00)".
Previously the full title was used as the version, producing invalid
entries like "Package - MokoWaaS (VERSION: 02.31.00)" in the XML.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MatchStreamFromTag now checks if the tag name directly matches a
stream name (e.g. "stable", "release-candidate", "development")
before falling back to suffix matching. Supports both conventions:
1. Stream-name tags: tag IS the stream (MokoWaaS style)
2. Version tags: tag has version + suffix (v1.0.0-rc1 style)
When a stream-name tag is detected, the version number is extracted
from the release title instead of the tag. Falls back to tag name
if no version found in title.
Applied across all feed generators: Joomla XML, Dolibarr JSON,
WordPress JSON, and Changelog XML.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PaymentRef UNIQUE constraint causes Error 1062 when creating keys
without a payment reference — empty strings collide. Remove the
DB constraint; idempotency is enforced in code via
GetLicenseKeyByPaymentRef which already filters empty strings.
Also replace inline style with tw-max-w-lg class on search box.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The search input was spanning full width (tw-w-full) making it
disproportionately large. Cap at 400px to match other search
inputs in the Gitea UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SECURITY: Add verifyPackageOwnership/verifyKeyOwnership checks to
all API handlers that accept ID parameters. Prevents cross-org
access where an admin of org A could modify org B's license data.
FIX: RepoScope validation now properly parses JSON arrays using
json.Unmarshal instead of strings.Contains. The old approach matched
substrings (repo ID "2" matched inside "12"). Now uses typed int64
comparison.
FIX: Add {{$.CsrfTokenHtml}} to both delete confirmation modal
forms (package and key) in repo and org templates. Without CSRF
tokens, the form-fetch-action POST requests would be rejected.
FIX: HTML-escape release notes in WordPress changelog to prevent
XSS via malicious release note content reaching WP admin dashboards.
FIX: Parse AllowedChannels JSON format before comma-split fallback
to avoid garbage values from splitting JSON arrays by comma.
FIX: Add missing third return value (false) on error path in
validateUpdateKey to prevent compile error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add changelog entry for full license management system release.
Update wiki with WordPress feed status, feed visibility modes,
and revision 1.4.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In "no-download" feed visibility mode, anonymous users can browse
the release listing page and read release notes, but the download
section (attachments, source archives) is replaced with a "Sign in
to download" message.
Only "hidden" mode redirects anonymous users to login entirely.
"no-download" keeps the page public — release notes, changelogs,
and version info are all visible. Just the actual files are gated.
This matches the commercial pattern: let users see what's available
to motivate purchase, while protecting the actual deliverables.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add FeedVisibility field to UpdateStreamConfig with three modes:
- public: full feed with download URLs (default)
- no-download: version info visible but download URLs stripped
- hidden: empty feed returned without a valid license key
The "no-download" mode is the key commercial pattern — customers
see updates exist (motivating purchase/renewal) but cannot download
without a valid key. Joomla shows "update available" in admin.
Applied consistently across all update feed endpoints (Joomla XML,
Dolibarr JSON, WordPress JSON) via the shared validateUpdateKey()
which now returns a stripDownloads flag.
Also: when licensing is enabled, the release listing page requires
login. Anonymous users are redirected to the login page. This
prevents browsing release notes and download links without auth.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use the existing shared/combolist.tmpl component for channel
selection in create and edit package forms. This mimics the
issue label picker UI — a searchable dropdown with checkmarks
and a selected-items list below.
Replaces raw checkboxes (repo + org templates) and Fomantic
multiple selection dropdowns (edit templates) with the
combo-multiselect component that has proper JS for toggle,
search, and clear functionality.
Handler parsing updated: combolist sends comma-separated values
in a single hidden input (vs multiple checkbox form values).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Repo settings now include extension metadata fields (element name,
display name, type, target version, maintainer, PHP minimum) under
the Licensing section. These override org-level defaults per-repo.
Empty fields inherit from the organization's update stream config.
Extension type dropdown includes: package, component, module,
plugin, template, library — plus an "(inherit from org)" option.
Also adds form fields to the RepoSettingsForm struct for all
metadata fields.
Closes#335
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New endpoint: GET /{owner}/{repo}/updates/wordpress.json
Generates JSON compatible with the YahnisElsts plugin-update-checker
library — the standard for commercial WordPress plugin self-hosted
updates. Returns name, slug, version, download_url, homepage,
requires_php, author, sections (changelog HTML), icons, and banners.
License key validation: reads from ?license_key=, ?dlid=, or ?key=
query params (PUC sends these via addQueryArgFilter). When RequireKey
is enabled, returns minimal empty response without download_url.
Changelog section built from release notes (last 10 stable releases),
converting markdown list items to HTML <ul>/<li> elements.
Icon/banner URLs point to conventional paths in the repo:
assets/icon-128x128.png, assets/icon-256x256.png
assets/banner-772x250.png, assets/banner-1544x500.png
Route registered at /updates/wordpress.json alongside existing
/updates.xml (Joomla) and /updates/dolibarr.json.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SECURITY: ValidateLicenseKeyForRepo() now checks the package's
RepoScope field against the requesting repo ID. A package scoped
to repo A will reject keys when accessed from repo B's update feed.
Update server and download gating both use the new function.
Master/internal keys bypass repo scope checks.
RepoScope supports: "all" (any repo), single repo ID string,
or JSON array of repo IDs like ["1","5","12"].
Also adds POST /license-keys/{id}/revoke API endpoint that was
missing from the API but existed in web handlers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace link-action delete buttons with show-modal pattern that opens
a Fomantic UI modal with a form. Package deletion requires typing
the package name in a required field before the submit button works.
Key deletion shows a warning modal with confirmation.
Uses Gitea's existing form-fetch-action + modal_actions_confirm
pattern, matching how repo deletion works in settings.
Both repo and org templates updated with matching modals.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Read the .sha256 sidecar attachment (generated by
GenerateReleaseChecksums) and populate the <sha256> element in the
update XML. This matches the pattern used by Akeeba (sha512) and
JCE (sha256 + sha384 + sha512) for integrity verification.
Also fix zip attachment filter to skip .sha256 sidecar files when
selecting the download URL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Joomla only recognizes: dev, alpha, beta, rc, stable. Our internal
stream names use longer forms (development, release-candidate).
Add joomlaTagName() to map between conventions in the <tags><tag>
XML element.
Without this, Joomla's update channel filter ignores entries with
non-standard tag values like "release-candidate" or "development".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Domain lock timer: add DomainLockHours to LicensePackage and
FirstUsedUnix to LicenseKey. During the grace period after first
use, any domain is accepted and auto-added to the restriction list.
After the grace period, only listed domains are allowed. Set 0 for
immediate lock-on-first-use (default).
Fix infourl: default to /releases listing page instead of specific
tag page. Falls back to SupportURL or InfoURL if configured.
Match Akeeba Backup Pro XML format: downloadkey prefix is "dlid="
(not "&dlid="), matching how Joomla stores extra_query. Verified
against production Akeeba/JCE/AdminTools manifests via SSH.
Update migration v340 with FirstUsedUnix and DomainLockHours columns.
Add DomainLockHours field to create/edit package forms for both
repo and org levels with help text.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migration v340: sync all missing columns (key_raw, payment_ref,
last_heartbeat_unix, is_archived, licensing_enabled, download_gating,
support_url, and all extension metadata fields).
Package archiving (#384): add IsArchived field with archive/unarchive
handlers and collapsible "Archived Packages" section in templates.
Existing keys from archived packages continue to work.
Expanded delete permissions (#385): org owners and site admins can
permanently delete packages and keys (previously site admin only).
Search (#392): server-side search across key_prefix, key_raw,
licensee_name, licensee_email, domain_restriction, and payment_ref
via ?q= query parameter on both repo and org licenses pages.
Sortable tables (#390): Fomantic UI sortable class on keys table
with new Domain column showing DomainRestriction per key.
Download gating (#347): three modes — none, prerelease-only, and
all downloads. CheckDownloadGating() intercepts both release
attachment and git archive download handlers.
Support URL (#393): configurable SupportURL field on
UpdateStreamConfig for wiki or external site links.
Changelog XML (#343): ServeChangelogXML endpoint at /changelog.xml
generates Joomla-compatible changelog from release notes. Parses
Keep-a-Changelog markdown sections into <security>, <fix>,
<addition>, <change>, <remove>, <note> XML elements.
API renew (#387): POST /license-keys/{id}/renew endpoint extends
key expiration by package duration.
Closes#384, #385, #386, #387, #389, #390, #392, #393
Refs #343, #346, #347
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- chore: remove stale mustNotBeArchived FIXME (CanEnableEditor no longer exists)
- fix(routes): remove dead /cherry-pick/{sha} route — replaced by /_cherrypick/
- fix(feed): use full ref name instead of ShortName for file feed revision
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove TODO from OAuth2 regenerate secret template — the
functionality was already fully implemented
- Remove stub TODO test comments for TestMerge, TestNewPullRequest,
TestAddTestPullRequestTask — stale for years
- Remove stale GetProjectsMode TODO — method is needed by templates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the branch/tag selector from the issue sidebar and new issue
form. The Issue.Ref field was added 8 years ago and provides minimal
value — it only saves a branch name and optionally restricts which
branch's commits can auto-close the issue.
Removed:
- Branch selector template (branch_selector_field.tmpl)
- Sidebar and new-form includes
- Ref badge from issue lists
- POST /{index}/ref web route and UpdateIssueRef handler
- GetRefEndNamesAndURLs calls from list renderers
- JS handler for branch selector dropdown
Preserved:
- DB column (Issue.Ref) — still used by commit-close logic
- API response still includes ref for backward compatibility
Closes#307
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bake the noop script directly into the image layer so openssh never
starts. We use external SSH (port 2222) via host networking, not the
container's sshd.
Fixes: #372
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In Go 1.23+, maps.Values returns iter.Seq not a slice.
Use slices.Collect() to materialize the iterator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fix(css): use calc(infinity * 1px) for --border-radius-full instead
of magic 99999px value (supported in all modern browsers)
- fix(css): remove legacy .center class from 2015, replace 6 usages
with tw-text-center utility class
Refs #318
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fix(ts): parseIssueHref now uses URL pathname and trims appSubUrl
for correct issue link parsing with sub-path deployments
- fix(actions): enforce MaxJobNumPerRun (256) limit when creating jobs,
rejecting workflows that exceed the GitHub-compatible limit
- chore: remove stale TODO comment on OAuth redirect route
Refs #325, #334
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add configurable fields: element name, display name, description,
extension type, maintainer, maintainer URL, info URL, target version,
PHP minimum
- Add platform dropdown: joomla, dolibarr, wordpress, prestashop,
drupal, composer, both
- Update Joomla XML generator to use metadata from config (falls back
to repo-derived values when not set)
- Add GetEffectiveConfig() for resolving repo → org → nil config chain
- Add locale keys for all new settings fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Uses LicensingEnabled (org config) instead of EnableLicenses/IsRepoAdmin.
Tab is now hidden when the org hasn't enabled the licensing system.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fix(templates): add required validation to issue dropdown fields
- refactor(ts): remove redundant `handled` field from MarkdownHandleIndentionResult
- refactor(go): rename HasOrgOrUserVisible to IsOwnerVisibleToDoer for clarity
Refs #311, #334
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix downloadkey prefix: "&dlid=" → "dlid=" (Joomla handles the separator)
- Reorder XML fields to match Akeeba/Joomla convention
- Add sha512 and php_minimum optional fields to update XML struct
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full namespace migration: update the Go module path and all import
statements from git.mokoconsulting.tech to code.mokoconsulting.tech.
Also updates all URL references in templates, workflows, configs,
tests, and documentation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix downloadkey prefix: "&dlid=" → "dlid=" (Joomla handles the separator)
- Reorder XML fields to match Akeeba/Joomla convention
- Add sha512 and php_minimum optional fields to update XML struct
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fix(blame): set HasSourceRenderedToggle for renderable files so that
permalinks include ?display=source for correct line-number linking
- fix(settings): translate team permission strings using data-locale
attributes instead of raw API values
- fix(dropzone): use relative path for non-image attachment markdown
links for consistency with image attachments
Refs #318, #334
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
License keys are domain-locked so hashing is unnecessary. Store the
full key in KeyRaw column for permanent visibility. Keys table now
shows the complete key with a clipboard copy button per row.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Block update feed endpoints based on repo platform setting:
Joomla-only repos return 404 on /updates/dolibarr.json and vice versa
- Show feed URLs section only when licensing is enabled
- Add delete button for license keys (site admin only)
- Add weekly cron job to purge expired keys older than 1 year
- Add DeleteLicenseKey and DeleteExpiredKeys model functions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only show Joomla XML URL when platform is joomla/both/empty.
Only show Dolibarr JSON URL when platform is dolibarr/both.
Also gate the entire feed URLs section behind LicensingEnabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The licenses feature is gated by org-level LicensingEnabled config,
not by per-repo unit enablement. Requiring TypeLicenses unit on repos
caused 404s since it wasn't in DefaultRepoUnits.
Write permissions are still enforced in handlers via
CanWrite(TypeLicenses). Org routes retain reqUnitAccess for
team-level permission control.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update all URLs in manifest.xml and updates.xml to use the new
code.mokoconsulting.tech domain.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update all URLs in manifest.xml and updates.xml to use the new
code.mokoconsulting.tech domain.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Admin-level teams (team.authorize >= Admin) now implicitly get admin
access to all unit types in UnitMaxAccess(), even without explicit
TeamUnit records. This resolves the long-standing TEAM-UNIT-PERMISSION
issue where adding new units (like TypeLicenses) left existing admin
teams without access.
Resolves: #304
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace confirm() with Gitea modal system (link-action + data-modal-confirm)
- Add confirmation modal to revoke key action
- Fix clipboard copy to use data-clipboard-target with tooltip feedback
- Localize all hardcoded English strings (feed labels, "unlimited", "Master")
- Improve key creation flash with security-focused message + copy button
- Add count badge to org licenses nav tab
- Add icon to org settings navbar for update streams
- Add help text to "Active" checkboxes explaining deactivation impact
- Fix empty state message to reference UI creation (not just API)
- Compact tables for denser license data display
- Add orange "Master" label to master package rows
- Conditional feed buttons on release page (only when licensing enabled)
- Add TypeLicenses unit type with Read/Write/Admin team permissions
- Route-level permission enforcement via RequireUnitReader/Writer
- Add "Renew" action for license keys (extends by package duration)
- Auto-associate domain on first heartbeat (lock-on-first-use)
- Enforce max_sites limit during domain auto-association
- Allow site admins and org owners to set custom license key values
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add key editing, domain enforcement, purchase webhooks, public
validation API, channels multiselect, Joomla downloadkey element,
licensing feature toggle, unified update system, release tag
enforcement, heartbeat tracking, and improved settings UX.
Phase 1: Full key display with AbsoluteShort dates, master package
protection (hide edit/delete in UI, reject in handlers).
Phase 2: Key edit page with template, handlers, and routes for both
repo and org levels. Master keys redirect away.
Phase 3: Domain restriction checking against CSV allowlist,
MaxSites enforcement via CountUniqueDomainsByKey and
IsDomainKnownForKey, dlid query param support for Joomla.
Phase 4: Purchase webhook (POST /license-keys/purchase) with
PaymentRef idempotency. Public validation endpoint
(POST /license-keys/validate) outside auth middleware.
PATCH /license-keys/{id} for API key editing.
Phase 5: Channels multiselect using org UpdateStreamConfig streams
rendered as checkboxes, stored as JSON arrays.
Additional: downloadkey XML element, LicensingEnabled toggle on
UpdateStreamConfig, Dolibarr endpoint unified with key validation,
release tag suffix enforcement, LastHeartbeatUnix field with
TouchHeartbeat, and cleaned-up settings pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace broken JavaScript onclick toggle with native HTML
<details>/<summary> element. Works without JS, accessible,
and styled as a Fomantic UI button.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete button only visible to site admins (super admins)
- Delete handler checks ctx.IsUserSiteAdmin() and returns 404 otherwise
- Repo admins can still create, edit, revoke — but not delete
- IsSiteAdmin set in both repo and org context data
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add edit and delete actions for license packages:
- Edit button (pencil icon) opens edit form with all package fields
- Delete button (trash icon) with confirmation dialog
- Edit form includes active/inactive toggle
- Routes: GET/POST /licenses/packages/{id}/edit, POST /licenses/packages/{id}/delete
Ref #239
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use .Repository.HTMLURL instead of AppSubUrl+RepoLink so the
copyable update feed URLs include the full domain name.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major licensing UI improvements:
- Org-level Licenses tab in org menu (visible to org members)
- Org-level Licenses page with full CRUD (packages, keys, revoke)
- Auto-created master key: when admin first visits Licenses page,
a Master (Internal) package + key is auto-generated
- Master keys marked with orange "Master" badge in key list
- Revoking a master key auto-creates a new one on next visit
- Fixed "New Package" button toggle (was using tw-hidden class
that didn't work, now uses style.display)
- IsRepoAdmin set as context data for template access
- Master keys have IsInternal=true, lifetime, all channels
Ref #239
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an admin first visits the Licenses page, a master license package
and key are automatically created:
- Master package: lifetime, unlimited, all channels, all repos
- Master key: IsInternal=true, never expires
- Raw key shown once with copy instructions
- If master key is revoked, a new one is created on next visit
The master key is always present — revoking it and revisiting the page
generates a fresh one.
Ref #239
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The template couldn't call .Permission.IsAdmin() directly. Set
IsRepoAdmin as a context data variable so the template can use it.
Licenses tab now shows for repo admins even without packages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Licenses tab was hidden when no packages existed, making it
impossible for admins to find the page to create their first package.
Now shows for repo admins always, and for everyone when packages exist.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add full license management web forms to the Licenses page:
- "New Package" form: name, description, duration, max sites, channels
- "Generate Key" button per package: creates key with auto-expiry
- "Revoke" button per key: deactivates the key
- New key display: shows raw key once with copy instructions
- Update Feed URLs section: copyable Joomla/Dolibarr endpoint URLs
- Admin-only controls: forms only visible to repo admins
Ref #239
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The licenses page was using reqSignIn which blocks API token access
and redirects to login. Use optSignIn so the page is accessible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix template error: function "DateTime" not defined. Use
DateUtils.TimeSince which is the correct template function.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a Licenses tab in the repo header that shows when license packages
exist for the repo's owner. The tab displays:
- License packages with name, duration, allowed channels, key count
- Issued keys with prefix, licensee, expiry, and status
Also includes:
- Org-level default update streams with per-repo override (#265)
- Full Joomla channel names in update feeds
- Update Feed button on releases page
- DB migration v336 for update_stream_config table
The Licenses tab appears after Packages in the repo header, gated by
whether any license packages exist for the owner.
Ref #239, #265
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use the full Joomla convention for update stream tag names:
- dev → development
- rc → release-candidate
- alpha, beta, stable unchanged
Add NormalizeChannel() helper that maps shorthand names (dev, rc)
to full names so license key allowed_channels work with either
format. Applied in XML generation, JSON generation, and key
validation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add an "Update Feed" button next to the RSS link on the releases page
that links to the repo's updates.xml endpoint. Only shown on the
releases view (not tags).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same bug as keys — packages were created with is_active=false
causing all key validation to reject even valid keys.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keys were created with is_active=false (DB default) because the API
handler didn't explicitly set IsActive. This caused all key validation
to fail.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clarify that update feeds (updates.xml, dolibarr.json) are always
accessible regardless of the releases visibility setting. The
visibility dropdown controls whether the releases page is browsable
by anonymous visitors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add the same inline visibility control (Private / Public) to the
issues section on the repo settings page. Now wiki, releases, and
issues all have visibility dropdowns.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-unit visibility dropdown (Private / Public) directly on the
repo settings page next to each unit's enable checkbox. This replaces
the need to navigate to a separate Public Access settings page.
Supported units: Code, Wiki, Issues, Releases. Each gets a dropdown
that controls AnonymousAccessMode — when set to Public, the unit is
readable by anonymous visitors even on private repos.
Closes#238
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 19:35:56 -05:00
2343 changed files with 18619 additions and 12743 deletions
// ComputeTaskTokenPermissions computes the effective permissions for a job token against the target repository.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.