Merge pull request 'feat(licenses): platform enforcement, key deletion, expired key cleanup' (#340) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m4s
Deploy MokoGitea / deploy (push) Failing after 3m4s
This commit was merged in pull request #340.
This commit is contained in:
@@ -155,6 +155,20 @@ func UpdateLicenseKey(ctx context.Context, key *LicenseKey) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteLicenseKey permanently removes a license key by ID.
|
||||
func DeleteLicenseKey(ctx context.Context, id int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(id).Delete(new(LicenseKey))
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteExpiredKeys removes keys that expired more than the given duration ago.
|
||||
func DeleteExpiredKeys(ctx context.Context, olderThanDays int) (int64, error) {
|
||||
cutoff := timeutil.TimeStampNow() - timeutil.TimeStamp(int64(olderThanDays)*86400)
|
||||
return db.GetEngine(ctx).
|
||||
Where("expires_unix > 0 AND expires_unix < ? AND is_internal = ?", cutoff, false).
|
||||
Delete(new(LicenseKey))
|
||||
}
|
||||
|
||||
// TouchHeartbeat updates the last heartbeat timestamp for a key.
|
||||
func TouchHeartbeat(ctx context.Context, keyID int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(keyID).
|
||||
|
||||
@@ -2686,6 +2686,9 @@
|
||||
"repo.licenses.desc": "License packages and keys for gating update feeds.",
|
||||
"repo.licenses.custom_key_placeholder": "Custom key (optional)",
|
||||
"repo.licenses.custom_key_help": "Leave empty to auto-generate. Site admins and org owners can set a custom key value.",
|
||||
"repo.licenses.delete_key": "Delete Key",
|
||||
"repo.licenses.confirm_delete_key": "Permanently delete this license key? This cannot be undone.",
|
||||
"repo.licenses.key_deleted": "License key deleted.",
|
||||
"repo.release.draft": "Draft",
|
||||
"repo.release.prerelease": "Pre-Release",
|
||||
"repo.release.stable": "Stable",
|
||||
|
||||
@@ -437,3 +437,18 @@ func LicensesRenewKey(ctx *context.Context) {
|
||||
ctx.Flash.Success(ctx.Tr("repo.licenses.key_renewed", days))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
||||
}
|
||||
|
||||
// LicensesDeleteKey permanently deletes a license key. Site admin only.
|
||||
func LicensesDeleteKey(ctx *context.Context) {
|
||||
if !ctx.IsUserSiteAdmin() {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
keyID := ctx.PathParamInt64("id")
|
||||
if err := licenses.DeleteLicenseKey(ctx, keyID); err != nil {
|
||||
ctx.ServerError("DeleteLicenseKey", err)
|
||||
return
|
||||
}
|
||||
ctx.Flash.Success(ctx.Tr("repo.licenses.key_deleted"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
||||
}
|
||||
|
||||
@@ -440,3 +440,18 @@ func LicensesRenewKey(ctx *context.Context) {
|
||||
ctx.Flash.Success(ctx.Tr("repo.licenses.key_renewed", days))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
||||
}
|
||||
|
||||
// LicensesDeleteKey permanently deletes a license key. Site admin only.
|
||||
func LicensesDeleteKey(ctx *context.Context) {
|
||||
if !ctx.IsUserSiteAdmin() {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
keyID := ctx.PathParamInt64("id")
|
||||
if err := licenses.DeleteLicenseKey(ctx, keyID); err != nil {
|
||||
ctx.ServerError("DeleteLicenseKey", err)
|
||||
return
|
||||
}
|
||||
ctx.Flash.Success(ctx.Tr("repo.licenses.key_deleted"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
||||
}
|
||||
|
||||
@@ -81,6 +81,13 @@ func validateUpdateKey(ctx *context.Context) (allowedChannels []string, ok bool)
|
||||
// ServeUpdatesXML generates and serves a Joomla-compatible updates.xml
|
||||
// from the repository's releases.
|
||||
func ServeUpdatesXML(ctx *context.Context) {
|
||||
// Block if platform doesn't include joomla.
|
||||
platform := ctx.Data["RepoUpdatePlatform"]
|
||||
if platform == "dolibarr" {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
allowedChannels, ok := validateUpdateKey(ctx)
|
||||
if !ok {
|
||||
// Return empty updates XML for invalid keys (Joomla-compatible).
|
||||
@@ -109,6 +116,13 @@ func ServeUpdatesXML(ctx *context.Context) {
|
||||
// from the repository's releases. Uses the same license key validation as the
|
||||
// Joomla XML feed — all platforms share the same licensing system.
|
||||
func ServeDolibarrJSON(ctx *context.Context) {
|
||||
// Block if platform doesn't include dolibarr.
|
||||
platform := ctx.Data["RepoUpdatePlatform"]
|
||||
if platform == "joomla" || platform == "" {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
allowedChannels, ok := validateUpdateKey(ctx)
|
||||
if !ok {
|
||||
// Return empty updates for invalid keys.
|
||||
|
||||
@@ -1116,6 +1116,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
m.Post("/keys/{id}/edit", org.LicensesEditKeyPost)
|
||||
m.Post("/keys/{id}/revoke", org.LicensesRevokeKey)
|
||||
m.Post("/keys/{id}/renew", org.LicensesRenewKey)
|
||||
m.Post("/keys/{id}/delete", org.LicensesDeleteKey)
|
||||
}, reqUnitAccess(unit.TypeLicenses, perm.AccessModeWrite, true))
|
||||
}, reqUnitAccess(unit.TypeLicenses, perm.AccessModeRead, true))
|
||||
|
||||
@@ -1535,6 +1536,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
m.Post("/keys/{id}/edit", repo.LicensesEditKeyPost)
|
||||
m.Post("/keys/{id}/revoke", repo.LicensesRevokeKey)
|
||||
m.Post("/keys/{id}/renew", repo.LicensesRenewKey)
|
||||
m.Post("/keys/{id}/delete", repo.LicensesDeleteKey)
|
||||
}, optSignIn, context.RepoAssignment)
|
||||
// end "/{username}/{reponame}": licenses
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ import (
|
||||
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models"
|
||||
git_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
|
||||
licenses_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
||||
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/webhook"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git/gitcmd"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/updatechecker"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/auth"
|
||||
@@ -158,6 +160,24 @@ func registerCleanupPackages() {
|
||||
})
|
||||
}
|
||||
|
||||
func registerCleanupExpiredLicenseKeys() {
|
||||
RegisterTaskFatal("cleanup_expired_license_keys", &BaseConfig{
|
||||
Enabled: true,
|
||||
RunAtStart: false,
|
||||
Schedule: "@weekly",
|
||||
}, func(ctx context.Context, _ *user_model.User, config Config) error {
|
||||
// Delete non-internal keys that expired more than 365 days ago.
|
||||
deleted, err := licenses_model.DeleteExpiredKeys(ctx, 365)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if deleted > 0 {
|
||||
log.Info("Cleaned up %d expired license keys (expired >1 year)", deleted)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func registerSyncRepoLicenses() {
|
||||
RegisterTaskFatal("sync_repo_licenses", &BaseConfig{
|
||||
Enabled: false,
|
||||
@@ -185,6 +205,7 @@ func initBasicTasks() {
|
||||
registerCleanupPackages()
|
||||
}
|
||||
registerSyncRepoLicenses()
|
||||
registerCleanupExpiredLicenseKeys()
|
||||
if setting.UpdateChecker.Enabled {
|
||||
registerUpdateChecker()
|
||||
}
|
||||
|
||||
@@ -168,6 +168,11 @@
|
||||
<button class="ui tiny red button link-action" data-url="{{$.Org.HomeLink}}/-/licenses/keys/{{.ID}}/revoke" data-modal-confirm="{{ctx.Locale.Tr "repo.licenses.confirm_revoke_key"}}" title="{{ctx.Locale.Tr "repo.licenses.revoke"}}">
|
||||
{{svg "octicon-x" 14}}
|
||||
</button>
|
||||
{{if $.IsSiteAdmin}}
|
||||
<button class="ui tiny red button link-action" data-url="{{$.Org.HomeLink}}/-/licenses/keys/{{.ID}}/delete" data-modal-confirm="{{ctx.Locale.Tr "repo.licenses.confirm_delete_key"}}" title="{{ctx.Locale.Tr "repo.licenses.delete_key"}}">
|
||||
{{svg "octicon-trash" 14}}
|
||||
</button>
|
||||
{{end}}
|
||||
</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
|
||||
@@ -174,6 +174,11 @@
|
||||
<button class="ui tiny red button link-action" data-url="{{$.RepoLink}}/licenses/keys/{{.ID}}/revoke" data-modal-confirm="{{ctx.Locale.Tr "repo.licenses.confirm_revoke_key"}}" title="{{ctx.Locale.Tr "repo.licenses.revoke"}}">
|
||||
{{svg "octicon-x" 14}}
|
||||
</button>
|
||||
{{if $.IsSiteAdmin}}
|
||||
<button class="ui tiny red button link-action" data-url="{{$.RepoLink}}/licenses/keys/{{.ID}}/delete" data-modal-confirm="{{ctx.Locale.Tr "repo.licenses.confirm_delete_key"}}" title="{{ctx.Locale.Tr "repo.licenses.delete_key"}}">
|
||||
{{svg "octicon-trash" 14}}
|
||||
</button>
|
||||
{{end}}
|
||||
</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
@@ -184,10 +189,12 @@
|
||||
{{end}}
|
||||
|
||||
{{/* ── Update Feed URLs ── */}}
|
||||
{{if .LicensingEnabled}}
|
||||
<h4 class="ui top attached header tw-mt-4">
|
||||
{{svg "octicon-rss" 16}} {{ctx.Locale.Tr "repo.licenses.update_feeds"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
{{if or (eq .RepoUpdatePlatform "joomla") (eq .RepoUpdatePlatform "both") (eq .RepoUpdatePlatform "")}}
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.feed_joomla_updates"}}</label>
|
||||
<div class="ui action input tw-w-full">
|
||||
@@ -195,6 +202,8 @@
|
||||
<button class="ui button" data-clipboard-target=".js-feed-url-joomla" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">{{svg "octicon-copy" 14}}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if or (eq .RepoUpdatePlatform "dolibarr") (eq .RepoUpdatePlatform "both")}}
|
||||
<div class="field tw-mt-2">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.feed_dolibarr_updates"}}</label>
|
||||
<div class="ui action input tw-w-full">
|
||||
@@ -202,7 +211,9 @@
|
||||
<button class="ui button" data-clipboard-target=".js-feed-url-dolibarr" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">{{svg "octicon-copy" 14}}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
|
||||
Reference in New Issue
Block a user