Add license key data model and Dolibarr update feed endpoint:
License key system:
- license_package table: subscription tiers with duration, max sites,
repo scope (org-wide or specific repos), and allowed update channels
- license_key table: individual keys with SHA-256 hashed storage,
domain restriction, custom start/end dates, internal/master key flag
- license_key_usage table: tracks update check activity per key
- DB migration v335 creates all three tables
Update server enhancements:
- Dolibarr JSON endpoint at /{owner}/{repo}/updates/dolibarr.json
- License key validation on update endpoints via ?key=MOKO-XXXX param
- Channel filtering: packages restrict which update streams keys access
- Invalid keys get empty XML response (Joomla-compatible "no updates")
- Usage tracking records domain, IP, user agent, version on each check
Key design decisions:
- Org-level master keys: IsInternal=true, package RepoScope="all"
- Keys stored as SHA-256 hashes, raw key only shown at creation
- Packages define allowed channels (e.g. ["stable","rc"] for Pro tier)
- MOKO-XXXX-XXXX-XXXX-XXXX format for license keys
Ref #239
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add GET /{owner}/{repo}/updates.xml that dynamically generates a
Joomla-compatible updates.xml from the repository's releases.
Features:
- Automatically maps release tags to channels (stable/rc/beta/alpha/dev)
- Finds .zip attachments for download URLs, falls back to archive URL
- Emits one entry per channel (latest release wins)
- Extracts version from tag names, strips common prefixes
- Publicly accessible (no auth required) for Joomla update clients
This is Phase 1 of #239 — the core dynamic update feed generation.
Future phases will add license key gating, Dolibarr support, and
repo settings UI.
Ref #239
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix three gaps that prevented per-unit public access from working on
private repositories:
1. Git HTTP handler (githttp.go): allow anonymous git pull for private
repos when the target unit (code or wiki) has AnonymousAccessMode
set to read. Previously only checked repo.IsPrivate.
2. Permission engine (repo_permission.go): call
finalProcessRepoUnitPermission for anonymous users on private repos
so that unit-level anonymous access modes are populated. Previously
returned early with AccessModeNone, skipping anonymous mode setup.
3. Search/explore (repo_list.go): include private repos that have at
least one unit with anonymous_access_mode > 0 in search results,
so anonymous users can discover repos with public sections.
The existing settings UI at /settings/public_access already allows
configuring per-unit visibility. The home page redirect to the first
readable unit (e.g. wiki) also already works via checkHomeCodeViewable.
Closes#238
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a dynamic config option to set the default landing page for
unauthenticated visitors from Site Administration > Settings. Options:
- Home (default explore page)
- Explore (repository explore)
- Organizations (org explore)
- Login (redirect to login)
- Custom path (any internal URL like /MokoConsulting)
The setting takes effect immediately without restart, using the same
dynamic config system as maintenance mode and web banner. Falls back
to the static LANDING_PAGE setting from app.ini if not configured.
Closes#240
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rebrand the built-in actions bot user from upstream Gitea naming to
MokoGitea branding:
- Name: gitea-actions → mokogitea-actions
- FullName: Gitea Actions → MokoGitea Actions
- Email: teabot@gitea.io → mokogitea-actions[bot]@mokoconsulting.tech
Add backward-compatible name recognition so all three bot name variants
(mokogitea-actions, gitea-actions, github-actions) with optional [bot]
suffix resolve to the same system user.
Add WhitelistActionsUser, MergeWhitelistActionsUser, and
ForcePushAllowlistActionsUser toggles to branch protection rules,
allowing CI/CD workflows to push to protected branches when explicitly
enabled. Previously the actions bot (virtual user ID -2) could never be
added to whitelist because updateUserWhitelist() only validates real
database users.
Closes#233
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add runtime ${APP_NAME} placeholder substitution in locale strings so
all user-facing text reflects the configured APP_NAME from app.ini.
Replace 52 hardcoded locale strings, template literals, HTTP auth
realm headers, and Swagger API titles/descriptions with the
configurable value.
Closes#1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backport #37588 by @pandareen
## Summary
Fixes
[go-gitea/gitea#37564](https://github.com/go-gitea/gitea/issues/37564):
when an OIDC provider returns a `picture` claim, Gitea is supposed to
download that image as the user's avatar (if `[oauth2_client]
UPDATE_AVATAR = true`). Two latent bugs prevented this from working
consistently:
1. **Default Go User-Agent rejected by some image hosts.**
`oauth2UpdateAvatarIfNeed` used `http.Get`, which sends `User-Agent:
Go-http-client/1.1`. Hosts like `upload.wikimedia.org` reject that UA
with `403`, and every error path silently returned, so the user was left
with an identicon and **no log line** to diagnose the issue.
2. **Link-account *register* path skipped avatar sync.** First-time OIDC
sign-ins where auto-registration is disabled (or required a
username/password retype) go through `LinkAccountPostRegister`, which
created the user but never called `oauth2SignInSync`. So the avatar /
full name / SSH keys from the IdP were dropped on the floor for those
users, even though the existing-account-link path (`oauth2LinkAccount`)
and the auto-register path (`handleOAuth2SignIn`) both already did the
sync.
## Changes
- `routers/web/auth/oauth.go` — `oauth2UpdateAvatarIfNeed` now uses
`http.NewRequest` + `http.DefaultClient.Do`, sets `User-Agent: Gitea
<version>`, and logs every failure path at `Warn` (invalid URL, fetch
error, non-200, body read error, oversize body, upload error). No silent
failures.
- `routers/web/auth/linkaccount.go` — `LinkAccountPostRegister` now
calls `oauth2SignInSync` after a successful user creation, mirroring the
auto-register and link-existing-account flows.
- `tests/integration/oauth_avatar_test.go` — new
`TestOAuth2AvatarFromPicture` integration test with five sub-cases:
- `AutoRegister_FetchesAvatarFromPictureWithGiteaUA` — happy path,
asserts `use_custom_avatar=true`, an avatar hash is set, exactly one
HTTP request was made, and the request carried a `Gitea ` UA. The mock
server enforces the UA prefix to mirror real-world hosts that reject
Go's default UA.
- `AutoRegister_NonOK_DoesNotUpdateAvatar` — server returns 403; user's
avatar must remain unset.
- `AutoRegister_EmptyPicture_NoFetch` — empty `picture` claim must not
trigger any HTTP request.
- `AutoRegister_UpdateAvatarFalse_NoFetch` — `UPDATE_AVATAR=false` must
not trigger any HTTP request.
- `LinkAccountRegister_FetchesAvatarFromPicture` — guards the
`linkaccount.go` fix; without the new `oauth2SignInSync` call this
assertion fails.
## Test plan
- [x] `go test -tags 'sqlite sqlite_unlock_notify' -run
'^TestOAuth2AvatarFromPicture$' ./tests/integration/ -v` — 5/5 sub-tests
pass.
- [x] Manual: log in as a Keycloak user with `picture` claim pointing at
`https://avatars.githubusercontent.com/u/9919?v=4` — Gitea avatar is
replaced with the GitHub picture.
- [x] Manual: same flow with `https://upload.wikimedia.org/...` —
request now succeeds (or returns a clearly logged `Warn` line if
rate-limited with `429`); previously it silently 403'd.
- [x] Manual: `UPDATE_AVATAR=false` — user keeps the identicon, no
outbound request in container logs.
- [ ] Reviewer: please double-check that no other call sites of
`oauth2UpdateAvatarIfNeed` rely on the old `http.Get` behaviour.
## Related
- Upstream issue: go-gitea/gitea#37564
--------------------------------------------
AI Editor was used in this PR
---------
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: pandareen <7270563+pandareen@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Nicolas <bircni@icloud.com>
Adds a Require2FA toggle to organization settings. When enabled,
org members without 2FA are redirected to the security settings
page with a warning flash message.
Changes:
- New Require2FA field on User model (migration v333)
- Org settings UI checkbox with shield-lock icon
- Check2FARequirement middleware on member-required org routes
- UpdateOptions extended with Require2FA field
Closes#208
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a user signs in, sends notifications with username, IP address,
user agent, and timestamp. Notifications go through:
- Email to the user's registered address
- ntfy push to the configured topic
Enabled by default, configurable via app.ini:
[login_notification]
ENABLED = true
The notification fires asynchronously (goroutine) so it doesn't
block the login redirect. Hooks into handleSignInFull which is the
single choke point for all auth methods (password, 2FA, OAuth).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Each branding image row now has a Reset button when custom image exists
- Reset removes the custom file, reverting to built-in default
- All admin sidebar menu items now have octicon icons
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Help URL: knowledge base / documentation link
- Support URL: ticket system / email for user support
Both saved to app.ini and applied in-memory immediately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace built-in logo.png, favicon.png, logo-small.png with Moko
Consulting branding images
- Remove SVG overrides so PNG takes priority
- Set apple-touch-icon.png to favicon for iOS home screen
- Update site manifest to include favicon.png as 256x256 PWA icon
so installed apps show the square brand icon
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The org home page template expects .Members, .NumMembers, .Teams,
.IsOrganizationMember, .IsOrganizationOwner, and .IsPublicMember
but the handler only set OrgOverviewMembers and OrgOverviewTeams
(different key names). The sidebar rendered empty because the
template variables were undefined.
Fixed by adding all required data bindings and capturing the
membersIsPublic map (previously discarded) as a callable function.
Closes#183
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a Branding section to Site Administration where admins can upload
custom images for three separate slots:
- Nav icon (logo-small.png) — top-left corner, 30x30px
- Login logo (logo.png) — login page and homepage
- Favicon (favicon.png) — browser tab icon
Changes:
- New admin route: /-/admin/branding with upload forms
- Templates use AssetUrlPrefix instead of hardcoded external URLs
- Nav bar uses logo-small.png with fallback to logo.png
- Uploads save to custom/public/assets/img/ (persists across restarts)
- SVG overrides auto-removed when PNG is uploaded
- Added logo-small.png as default built-in asset
Closes#181
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a dropdown on the admin dashboard to switch between update streams
(stable, rc, beta, alpha, development) matching the Joomla pattern.
Changes:
- Admin dashboard shows channel selector with descriptions
- POST handler validates and applies channel change in-memory
- Triggers immediate re-check against updates.xml after switch
- updates.xml has all 5 standard channels with descriptions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename the Go module path from code.gitea.io/gitea to
git.mokoconsulting.tech/MokoConsulting/MokoGitea across the entire
codebase.
Scope:
- go.mod module declaration
- 2,235 Go source files (import paths)
- Dockerfile WORKDIR and COPY paths
- Swagger API templates
- golangci.yml linter config
External dependencies (code.gitea.io/gitea-vet, code.gitea.io/sdk/gitea,
gitea.com/gitea/act, etc.) are intentionally NOT renamed — they are
separate upstream modules.
Closes#132
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace JSON API-based update checking with Joomla-style updates.xml
that supports multiple update streams (stable, dev, security).
Changes:
- Add updates.xml at repo root with stable/dev/security channels
following the same XML structure as MokoOnyx and other Joomla repos
- Rewrite updatechecker module to parse XML with channel filtering
- Add CHANNEL setting to [update_checker] config (default: stable)
- Show channel name and docker pull command in admin dashboard banner
Config example:
[update_checker]
ENABLED = true
CHANNEL = stable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backport #37704
This PR hardens OAuth token exchange validation by binding exchanged
credentials to the client and redirect URI that originally obtained
them.
What it changes:
- reject refresh token exchanges when the refresh token belongs to a
different OAuth application
- reject authorization code exchanges when the `redirect_uri` in the
token request differs from the `redirect_uri` stored with the
authorization code
- add integration coverage for:
- authorization code exchange with a mismatched redirect URI
- refresh token reuse across two different dynamically created OAuth
applications
Why:
OAuth authorization codes and refresh tokens must remain bound to the
client context that originally received them. Without those checks:
- a valid authorization code can be redeemed against a different
registered redirect URI of the same client
- a refresh token can be replayed by a different OAuth client
---------
Co-authored-by: Nicolas <bircni@icloud.com>
Backport #37695 by @lunny
This PR fixes two permission-checking gaps in Git and LFS request
handling.
## What it changes
- keep wiki Git HTTP pushes on the normal write-permission path, even
when proc-receive support is enabled
- revalidate LFS bearer token requests against the current user state
and current repository permissions before allowing access
- add regression coverage for unauthorized wiki HTTP pushes
- add LFS tests for blocked users, revoked repository access, read-only
upload attempts, and valid write access
## Why
- wiki repositories should not inherit the relaxed refs/for handling
used for normal code repositories
- LFS authorization tokens should not remain usable after a user is
disabled or loses repository access
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Replace removed upstream Gitea update checker with MokoGitea-native
version that checks our own releases API.
- New module: modules/updatechecker/ — fetches latest release from
git.mokoconsulting.tech, compares semver, caches result
- Cron task: runs every 24h (and at startup)
- Admin dashboard: shows green banner when update available
- Configurable via [update_checker] in app.ini
Closes#74
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix all 9 remaining files with CurrentRefSubURL field name
- Fix unused 'attempt' variable in action.go
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- gocron v2.19.1 uses LastRun() not LastRunStartedAt()
- renderhelper.RepoFileOptions uses CurrentRefPath not CurrentRefSubURL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace 9 more files still using gitea.com/gitea/runner/act/model
with github.com/nektos/act/pkg/model (resolved via replace directive)
- Fix ToActionWorkflowRun call: args were (ctx, run, nil) but
signature is (ctx, repo, run)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MokoGitea now recognizes .mokogitea/ as a first-class directory for:
- Workflow files (.mokogitea/workflows/) with highest priority
- README rendering from .mokogitea/ directory
- Repository template files (.mokogitea/template)
- Vendor path exclusion
The .gitea and .github directories remain supported for compatibility.
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When viewing repo-level Actions secrets or variables, also display
inherited org-level entries as read-only with an "overridden by
repository" badge when a repo entry shadows an org entry. Includes
a link to org settings for management.
Closes#78
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove upstream Gitea update checker, replace all docs.gitea.com references
with configurable HelpURL, rebrand default APP_NAME to MokoGitea, enforce
dot-prefixed repo privacy at creation time (create, fork, push-create), and
add system repo explanation in settings UI.
Closes#75, closes#76
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Repositories with names starting with "." are now treated as system
repositories that are always private and cannot be made public. This is
enforced at every code path: API create, web create, migrate, template
create, push-to-create, API edit, web settings, and public access
settings. On creation paths, privacy is silently forced. On edit paths,
a clear error is returned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add HELP_URL and SUPPORT_URL settings to app.ini (defaults to docs.gitea.com)
- Replace hardcoded docs.gitea.com in navbar with configurable HelpURL
- Expose HelpURL/SupportURL template functions
- Show Help URL and Support URL in Site Admin > Configuration
- Add locale strings for new admin config entries
- Create VERSION file (gitignored) for local builds with 1261.0.0 convention
The 1261.xx.xx version convention marks the fork starting point from
upstream Gitea. Set via VERSION file locally or GITEA_VERSION env in CI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fixes adding collaborative owners in Actions settings when the user or
organization name contains capital letters.
Fixes#37548
---------
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1. Fix ugly commit form "warning" message
2. Use JSONError for "Update PR Branch" response
3. Remove useless "timeline" class
4. Make timeline review default to "comment" to avoid icon missing
5. Align PR's "command line instructions" UI
6. Simply "Update PR branch" button logic
And then some TODOs are fixed.
---------
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Almost done
`pull_merge_box.tmpl` only has about 80 lines now, and (almost) all
variable accesses are strictly typed.
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Co-authored-by: Nicolas <bircni@icloud.com>
After the Webpack-to-Vite migration (#37002), mCaptcha stopped working
entirely on the registration page, throwing an error:
`TypeError: setting getter-only property "INPUT_NAME"`
This fix stops trying to mutate the read-only INPUT_NAME export. Instead
it probes for the Widget constructor at module.default (direct) or
module.default.default (CJS-wrapped), constructs the widget, and then
renames the hidden input element it creates to m-captcha-response which
is the field name Gitea's backend reads from the submitted form.
Generative AI was used to help with making this PR.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>