mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-10-09 17:01:53 +02:00
83b59874a8a52d64e70e8fdff7b8ea440f6e5d6a
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
83dd74dd52 |
feat(auth): API keys for programmatic access (#380)
* feat(auth): API keys for programmatic access Adds the @better-auth/api-key plugin so scripts and CI pipelines can call the existing endpoints with an x-api-key header instead of a session cookie. Keys are owned by a user, hashed at rest, prefixed gm_, never expire unless an expiry is chosen, and are not rate limited. A small guard plugin refuses key management calls that arrive with a key, so a leaked key cannot mint or revoke keys. New API Keys section on the Authentication tab with create, show once, copy and revoke. Migration 0017 adds the api_keys table with the validator fixture. docs/API.md documents the header and the calls automation needs. An e2e spec covers create, use, refuse and revoke over HTTP. bun.nix regenerated for the new package. Closes #314 Claude-Session: https://claude.ai/code/session_01Tp9pmi65a8k5jLMQFLf4JX * fix(e2e): send Origin on cookie-authenticated key management calls Better Auth rejects a cookie-authenticated POST without an Origin header (403 MISSING_OR_NULL_ORIGIN). Browsers always send one, the Playwright request context does not, so the spec sets it on the create and delete calls. Also asserts the guard's 403 code and documents the Origin requirement for scripts that manage keys with a session. Claude-Session: https://claude.ai/code/session_01Tp9pmi65a8k5jLMQFLf4JX |
||
|
|
3ff05ec7f3 |
chore(deps): upgrade better-auth family to 1.7.2 (#378)
better-auth, @better-auth/oauth-provider and @better-auth/sso move from 1.6.23 to 1.7.2. This is the first stable line with the oauth-provider fix for unbound resource indicators (Dependabot alert #55); no 1.6.x release has it. 1.7 keys accounts by (issuer, accountId) and looks the credential account up with issuer "local:credential" on sign-in, so migration 0016 adds the column and backfills existing rows: local accounts get local:credential, SSO accounts take their provider's issuer, other OAuth accounts get the local:oauth namespace. It also adds jwks.alg and jwks.crv, the 1.7 oauth-provider columns, and the oauth_resources, oauth_client_resources and oauth_client_assertions tables. A database that booted the reverted 1.7.0-rc.4 build (on main for a few minutes on 2026-08-06, and any dev database from then) already has part of that schema without a 0016 record, which makes 0016 fail on every boot. repairStrandedBetterAuth17Schema drops the leftovers before migrate runs, in the same way the #312 repair handles stranded SSO columns; the migration validator exercises it. createOAuthClient no longer takes "type"; the SSO applications route sends application_type (web or native) and reads it back with the legacy column as fallback. bun.nix is regenerated. reset-users writes the issuer. Supersedes #374. Claude-Session: https://claude.ai/code/session_01Tp9pmi65a8k5jLMQFLf4JX |
||
|
|
d211e54877 |
feat: mirror from GitLab and Gitea/Forgejo sources (#376)
Adds a Source dropdown to the configuration card (GitHub, GitLab beta, Gitea/Forgejo) backed by a source provider interface with three adapters. Discovery, mirroring, recovery and cleanup go through the provider; repositories record their source (migration 0015); clone credentials are built per host and a repository from another host is refused at the mirror sites. Issues, pull requests, releases and star lists stay GitHub only. The destination card gets a Gitea/Forgejo dropdown, and both hosts lock once repositories exist, with an explicit confirmation to change. Closes #375. Helps #371. Claude-Session: https://claude.ai/code/session_01Tp9pmi65a8k5jLMQFLf4JX |
||
|
|
0f700c7197 |
feat: per-repository and per-organization mirror option overrides (#362)
* feat: per-repository and per-organization mirror option overrides Closes the gap reported in #361: a repository whose LFS fetch fails could not be mirrored at all, because LFS was a single global switch. Gitea runs the LFS fetch inside its own migration, so a failure aborts the whole migration with nothing to salvage. The only fix available to us is to stop asking for LFS on that repository. Mirror options now resolve across three tiers, per flag, most specific first: repository override, then organization override, then the global config. NULL at a tier means inherit, so overriding LFS on one repo leaves its other options alone. Adds resolveMirrorOptions(), which replaces 24 scattered reads of config.giteaConfig.* across three near-identical blocks: the two mirror paths in gitea.ts and the sync path in gitea-enhanced.ts. The third was easy to miss and mattered: without it, overrides would have been honored on first mirror and silently ignored on every scheduled sync afterwards. The starredCodeOnly clamp is folded into the resolver and deliberately outranks explicit overrides, preserving the existing behavior that starred repos mirror code only. Editing is on the objects themselves, via the existing three-dot menus on the Repositories and Organizations pages, not in Configuration, which keeps holding the global defaults. A repositories filter and a row badge make it possible to find which repos deviate. Malformed override JSON degrades to "inherit" rather than throwing, so a bad value can never break a mirror run. * fix(ui): disable mirror toggles that cannot take effect, and explain why A toggle the runtime will ignore should not look editable. Three cases were doing exactly that, so they now share one mechanism: getMirrorOverrideGating() returns a reason string per flag, and the dialog disables the control and prints that reason underneath. Starred clamp. When a repo is starred and starredCodeOnly is set, the resolver forces every metadata flag off regardless of the override. The dialog previously let the user set them anyway. The clamped set now lives in STARRED_CLAMPED_KEYS, shared by the resolver and the gating helper so the flags the UI disables cannot drift from the ones the runtime clamps. LFS is deliberately excluded from that set, since turning LFS off per repository is the point of #361, and a test pins that. Labels. shouldMirrorLabels is `mirrorLabels && !mirrorIssues` in both mirror paths, so labels cannot take effect while issues are mirrored. That gate reacts live to the in-progress edit rather than only the saved state. Inherit hint. For a repository the hint now reflects global -> org instead of global alone, via a new name-keyed GET /api/organizations/mirror-overrides that reuses loadOrganizationMirrorOverrides. Personal repos skip the fetch, a failed fetch degrades to the global values rather than blocking the dialog, and the hint is suppressed while in flight so it is never briefly wrong. Widens the UI to mirrorLabels and mirrorMilestones. mirrorMetadata stays out: no mirror path reads it, so a per-object override would resolve correctly and then do nothing. See the report for that finding. useGiteaConfig additionally returns advancedOptions so callers can read starredCodeOnly without a second request. * fix(ui): read inherited mirror flags from mirrorOptions, not giteaConfig /api/config does not return the mirror flags on giteaConfig. On the way out, mapDbToUiConfig reshapes them into a separate mirrorOptions object using different names (mirrorLFS, not lfs) and nesting the metadata flags under metadataComponents with short names (issues, not mirrorIssues). The dialog was written against the DB shape, so every lookup returned undefined. Coerced to false, that is indistinguishable from a real "off", which produced two symptoms: every inherit hint read "currently off" regardless of the actual global config, and the labels gate never fired because the effective issues value it keys on came from the same dead source. Adds mirrorOptionsToFlags() as the single conversion point, matching the derivation in mapUiToDbConfig exactly, including that mirrorMetadata is a master switch over the metadata components while lfs and mirrorReleases sit outside it. Both dialogs now go through it, and useGiteaConfig returns mirrorOptions alongside advancedOptions. Server-side mirroring was never affected: the resolver reads config straight from the DB, where the flags really do live on giteaConfig. The existing tests could not catch this because they build a synthetic config already in DB shape, so a client/server mismatch is invisible to them. The new tests push flags through the real config-mapper in both directions and assert the derived flags equal what mapUiToDbConfig would persist, so they fail if that mapping changes again. One test pins the root cause directly: that giteaConfig in the API payload carries no flags. * fix(ui): surface the mirror-options filter on desktop, and on organizations The overrides filter only existed inside the mobile filter drawer, so on desktop the Repositories page showed a "Custom" badge on overridden rows with no way to filter to them. Being able to answer "which repos deviate from my defaults" on this page is the reason the Configuration-page listing was dropped, so desktop could not do the one job it was given. Adds the control to the desktop filter row next to status and sort, using the bare Select-with-placeholder style those use rather than the drawer's labelled markup. The mobile control is unchanged. The Organizations page had a wider version of the same gap: it renders the "Custom options" badge but never had this filter on either layout, and OrganizationsList did not filter on it at all. Added the predicate plus the control in both its layouts, so the two pages behave the same. Also folds hasOverrides into activeFilterCount on both pages and into the Organizations clear-all reset. Without that the mobile filter badge undercounted an active overrides filter, and clearing filters left it set. * fix(ui): stop double-applying the mirrorMetadata switch when reading config mirrorOptionsToFlags ANDed each metadata component with mirrorMetadata on the way in. That is a write-path rule: mapUiToDbConfig already applies it when persisting, so a config saved through the settings UI has it baked into the stored flag. Applying it again on read double-applies it. The read path does not need it. mapDbToUiConfig puts the raw stored values into metadataComponents, so metadataComponents.issues is exactly giteaConfig.mirrorIssues, which is the field gitea.ts and gitea-enhanced.ts read. Mapping the components straight through is 1:1 with runtime behavior. Double-applying was invisible while the stored state was self-consistent, since false && false is still false. It diverged when the state did not come from the UI write path, which env vars allow: MIRROR_METADATA=false with MIRROR_ISSUES=true stores mirrorMetadata:false, mirrorIssues:true. The runtime mirrors issues; the dialog reported off. Measured against a config in that state, five flags misreported. I previously described this as an unrecoverable loss in the API shape. That was wrong. Both values reach the client separately and unmerged, so the payload carries full information and the loss was one we introduced. Rewrites the test that asserted the derived flags match what mapUiToDbConfig would persist. Comparing against the write derivation is what pinned the bug in place. It now asserts the property that matters, that flags derived from the API payload equal the stored flags the runtime reads, plus a case checking agreement with resolveMirrorOptions on an inconsistent config. Both fail if the AND returns. |
||
|
|
7fc53cfad8 |
Revert "fix(security): upgrade better-auth family to 1.7.0-rc.4"
This reverts commit
|
||
|
|
2f6af22e25 |
fix(security): upgrade better-auth family to 1.7.0-rc.4
Fixes the @better-auth/oauth-provider advisory (unbound resource indicators could yield access tokens for unauthorized audiences, Dependabot #55). No patched 1.6.x exists; 1.7.0-rc.4 is the first patched line. The 1.7 oauth-provider expects a wider schema: new nullable columns on oauth_clients / oauth_access_tokens / oauth_refresh_tokens / oauth_consents (back-channel logout, DPoP, resource indicators, refresh token rotation) and three new tables (oauth_resources, oauth_client_resources, oauth_client_assertions). Migration 0014 is purely additive; validate-migrations gains the matching upgrade fixture. |
||
|
|
e862714d6a |
fix(db): self-heal sso_providers duplicate-column crash on upgrade (#312) (#313)
Migration 0013 runs as a single transaction that rebuilds `organizations` and then `ALTER TABLE sso_providers ADD saml_config` / `ADD domain_verified`. On instances where those columns were already created outside Drizzle (declared in schema.ts and added via db:push / an SSO-register round-trip on an intermediate build), the ADD throws "duplicate column name: saml_config". That rolls back the entire 0013 transaction, so 0013 is never recorded in `__drizzle_migrations` and is retried — failing identically — on every boot, crash-looping the server. Add a pre-migrate repair (mirroring the existing repairFailedMigrations() for the 0009 case): when 0013 is unrecorded but the columns already exist, preserve any real SAML provider config, drop the stranded columns so the canonical 0013 runs in full (organizations rebuild included), then restore the preserved values once the columns are re-added. No-op on fresh installs, clean upgrades, and already-migrated databases. This lets affected instances recover automatically on the next boot after upgrading — no manual SQLite surgery required. - src/lib/db/migration-repairs.ts: repairDuplicateSsoColumns + restoreSsoDataAfter0013 - src/lib/db/index.ts: wire both around migrate() - scripts/validate-migrations.ts: cover the broken-upgrade + data-preservation path |
||
|
|
66e3284898 |
fix(sso): repair SSO login bounce + migrate to @better-auth/oauth-provider (#307)
Resolves #306. SSO sign-in via OIDC (Authentik / Keycloak / etc.) now links the SSO identity to an existing email/password admin instead of bouncing to /login with `?error=UNKNOWN`. Account-linking is gated on the operator-supplied **Domain** field — cross-domain claims from a compromised IdP are refused. Also bundles the deprecated `oidcProvider` → `@better-auth/oauth-provider` migration. **Operators using the OAuth-provider feature must rotate registered client secrets after upgrade** (legacy plaintext → hashed storage; see the 0012 migration notes). Verified end-to-end on the pr-307 image against a real Authentik instance: SSO login lands on the dashboard, `accounts` table gets both `credential` and `authentik` rows for the same user. See PR description for full details. |
||
|
|
5d2462e5a0 |
feat: add notification system with Ntfy.sh and Apprise support (#238)
* feat: add notification system with Ntfy.sh and Apprise providers (#231) Add push notification support for mirror job events with two providers: - Ntfy.sh: direct HTTP POST to ntfy topics with priority/tag support - Apprise API: aggregator gateway supporting 100+ notification services Includes database migration (0010), settings UI tab, test endpoint, auto-save integration, token encryption, and comprehensive tests. Notifications are fire-and-forget and never block the mirror flow. * fix: address review findings for notification system - Fix silent catch in GET handler that returned ciphertext to UI, causing double-encryption on next save. Now clears token to "" on decryption failure instead. - Add Zod schema validation to test notification endpoint, following project API route pattern guidelines. - Mark notifyOnNewRepo toggle as "coming soon" with disabled state, since the backend doesn't yet emit new_repo events. The schema and type support is in place for when it's implemented. * fix notification gating and config validation * trim sync notification details |
||
|
|
d697cb2bc9 |
fix: prevent starred repo name collisions during concurrent mirroring (#236)
* fix: prevent starred repo name collisions during concurrent mirroring (#95) When multiple starred repos share the same short name (e.g. alice/dotfiles and bob/dotfiles), concurrent batch mirroring could cause 409 Conflict errors because generateUniqueRepoName only checked Gitea via HTTP, missing repos that were claimed in the local DB but not yet created remotely. Three fixes: - Add DB-level check in generateUniqueRepoName so it queries the local repositories table for existing mirroredLocation claims, preventing two concurrent jobs from picking the same target name. - Clear mirroredLocation on failed mirror so a failed repo doesn't falsely hold a location that was never successfully created, which would block retries and confuse the uniqueness check. - Extract isMirroredLocationClaimedInDb helper for the DB lookup, using ne() to exclude the current repo's own record from the collision check. * fix: address review findings for starred repo name collision fix - Make generateUniqueRepoName immediately claim name by writing mirroredLocation to DB, closing the TOCTOU race window between name selection and the later status="mirroring" DB update - Add fullName validation guard (must contain "/") - Make isMirroredLocationClaimedInDb fail-closed (return true on DB error) to be conservative about preventing collisions - Scope mirroredLocation clear on failure to starred repos only, preserving it for non-starred repos that may have partially created in Gitea and need the location for recovery * fix: address P1/P2 review findings for starred repo name collision P1a: Remove early name claiming from generateUniqueRepoName to prevent stale claims on early return paths. The function now only checks availability — the actual claim happens at the status="mirroring" DB write (after both idempotency checks), which is protected by a new unique partial index. P1b: Add unique partial index on (userId, mirroredLocation) WHERE mirroredLocation != '' via migration 0010. This enforces atomicity at the DB level: if two concurrent workers try to claim the same name, the second gets a constraint violation rather than silently colliding. P2: Only clear mirroredLocation on failure if the Gitea migrate call itself failed (migrateSucceeded flag). If migrate succeeded but metadata mirroring failed, preserve the location since the repo physically exists in Gitea and we need it for recovery/retry. |
||
|
|
e26ed3aa9c |
fix: rewrite migration 0009 for SQLite compatibility and add migration validation (#230)
SQLite rejects ALTER TABLE ADD COLUMN with expression defaults like DEFAULT (unixepoch()), which Drizzle-kit generated for the imported_at column. This broke upgrades from v3.12.x to v3.13.0 (#228, #229). Changes: - Rewrite migration 0009 using table-recreation pattern (CREATE, INSERT SELECT, DROP, RENAME) instead of ALTER TABLE - Add migration validation script with SQLite-specific lint rules that catch known invalid patterns before they ship - Add upgrade-path testing with seeded data and verification fixtures - Add runtime repair for users whose migration record may be stale - Add explicit migration validation step to CI workflow Fixes #228 Fixes #229 |