Merge pull request 'feat(licenses): plaintext key storage with copy buttons' (#342) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m35s

This commit was merged in pull request #342.
This commit is contained in:
2026-05-31 15:08:02 +00:00
4 changed files with 20 additions and 7 deletions
+5 -4
View File
@@ -24,7 +24,8 @@ type LicenseKey struct {
ID int64 `xorm:"pk autoincr"`
PackageID int64 `xorm:"INDEX NOT NULL"` // FK to license_package
OwnerID int64 `xorm:"INDEX NOT NULL"` // org or user that issued it
KeyHash string `xorm:"UNIQUE NOT NULL"` // SHA-256 of the raw key
KeyHash string `xorm:"UNIQUE NOT NULL"` // SHA-256 of the raw key (for fast lookup)
KeyRaw string `xorm:"TEXT"` // plaintext key (viewable by admins)
KeyPrefix string `xorm:"NOT NULL"` // first 8 chars for display
LicenseeName string `xorm:""` // customer name
LicenseeEmail string `xorm:""` // customer email
@@ -60,8 +61,7 @@ func HashKey(rawKey string) string {
return hex.EncodeToString(h[:])
}
// CreateLicenseKey generates a new key, hashes it, stores it, and returns the raw key.
// The raw key is only available at creation time.
// CreateLicenseKey generates a new key, stores it in plaintext and hashed, and returns the raw key.
func CreateLicenseKey(ctx context.Context, key *LicenseKey) (rawKey string, err error) {
rawKey, err = GenerateKeyString()
if err != nil {
@@ -69,6 +69,7 @@ func CreateLicenseKey(ctx context.Context, key *LicenseKey) (rawKey string, err
}
key.KeyHash = HashKey(rawKey)
key.KeyRaw = rawKey
key.KeyPrefix = rawKey[:12] + "..."
if _, err := db.GetEngine(ctx).Insert(key); err != nil {
@@ -78,9 +79,9 @@ func CreateLicenseKey(ctx context.Context, key *LicenseKey) (rawKey string, err
}
// CreateLicenseKeyCustom stores a key with a user-provided raw key string.
// The raw key is hashed and stored — it will not be recoverable after this.
func CreateLicenseKeyCustom(ctx context.Context, key *LicenseKey, rawKey string) error {
key.KeyHash = HashKey(rawKey)
key.KeyRaw = rawKey
if len(rawKey) > 12 {
key.KeyPrefix = rawKey[:12] + "..."
} else {
+1 -1
View File
@@ -2650,7 +2650,7 @@
"repo.licenses.package_created": "License package created successfully.",
"repo.licenses.generate_key": "Generate Key",
"repo.licenses.key_created": "License Key Created",
"repo.licenses.key_created_copy": "This key is hashed before storage and cannot be retrieved later. Copy and store it securely now.",
"repo.licenses.key_created_copy": "Your new license key is shown below. You can also view and copy it from the keys table at any time.",
"repo.licenses.revoke": "Revoke",
"repo.licenses.edit_package": "Edit License Package",
"repo.licenses.delete_package": "Delete Package",
+7 -1
View File
@@ -150,7 +150,13 @@
<tbody>
{{range .LicenseKeys}}
<tr>
<td><code>{{.KeyPrefix}}</code>{{if .IsInternal}} <span class="ui tiny orange label">{{ctx.Locale.Tr "repo.licenses.master_label"}}</span>{{end}}</td>
<td>
<div class="tw-flex tw-items-center tw-gap-1">
<code class="js-license-key-{{.ID}}">{{if .KeyRaw}}{{.KeyRaw}}{{else}}{{.KeyPrefix}}{{end}}</code>
{{if .KeyRaw}}<button class="ui tiny icon button" data-clipboard-target=".js-license-key-{{.ID}}" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">{{svg "octicon-copy" 12}}</button>{{end}}
{{if .IsInternal}} <span class="ui tiny orange label">{{ctx.Locale.Tr "repo.licenses.master_label"}}</span>{{end}}
</div>
</td>
<td>{{.LicenseeName}}{{if .LicenseeEmail}} <small>({{.LicenseeEmail}})</small>{{end}}</td>
<td>{{if eq .ExpiresUnix 0}}{{ctx.Locale.Tr "repo.licenses.never"}}{{else}}{{DateUtils.AbsoluteShort .ExpiresUnix}}{{end}}</td>
<td>{{if eq .LastHeartbeatUnix 0}}{{ctx.Locale.Tr "repo.licenses.never"}}{{else}}{{DateUtils.AbsoluteShort .LastHeartbeatUnix}}{{end}}</td>
+7 -1
View File
@@ -156,7 +156,13 @@
<tbody>
{{range .LicenseKeys}}
<tr>
<td><code>{{.KeyPrefix}}</code>{{if .IsInternal}} <span class="ui tiny orange label">{{ctx.Locale.Tr "repo.licenses.master_label"}}</span>{{end}}</td>
<td>
<div class="tw-flex tw-items-center tw-gap-1">
<code class="js-license-key-{{.ID}}">{{if .KeyRaw}}{{.KeyRaw}}{{else}}{{.KeyPrefix}}{{end}}</code>
{{if .KeyRaw}}<button class="ui tiny icon button" data-clipboard-target=".js-license-key-{{.ID}}" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">{{svg "octicon-copy" 12}}</button>{{end}}
{{if .IsInternal}} <span class="ui tiny orange label">{{ctx.Locale.Tr "repo.licenses.master_label"}}</span>{{end}}
</div>
</td>
<td>{{.LicenseeName}}{{if .LicenseeEmail}} <small>({{.LicenseeEmail}})</small>{{end}}</td>
<td>{{if eq .ExpiresUnix 0}}{{ctx.Locale.Tr "repo.licenses.never"}}{{else}}{{DateUtils.AbsoluteShort .ExpiresUnix}}{{end}}</td>
<td>{{if eq .LastHeartbeatUnix 0}}{{ctx.Locale.Tr "repo.licenses.never"}}{{else}}{{DateUtils.AbsoluteShort .LastHeartbeatUnix}}{{end}}</td>