fix(ui): document token validity in key verification view (#9002)
Some checks are pending
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing-integration / test-sqlite (push) Waiting to run
testing-integration / test-mariadb (v10.6) (push) Waiting to run
testing-integration / test-mariadb (v11.8) (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions

Document that the token is only valid for a minute. Add a link to get a new token.

Resolves #8048

Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9002
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: dawe <dawedawe@posteo.de>
Co-committed-by: dawe <dawedawe@posteo.de>
This commit is contained in:
dawe 2025-11-14 23:40:03 +01:00 committed by Gusted
parent bcd03d0e0d
commit efd4d2d8f5
5 changed files with 61 additions and 0 deletions

View file

@ -52,3 +52,9 @@ func MockProtect[T any](p *T) (reset func()) {
func SleepTillNextSecond() {
time.Sleep(time.Second - time.Since(time.Now().Truncate(time.Second)))
}
// When this is called, sleep until the truncated unix time to a minute was
// increased by one.
func SleepTillNextMinute() {
time.Sleep(time.Minute - time.Since(time.Now().Truncate(time.Minute)))
}

View file

@ -109,6 +109,7 @@
"feed.atom.link": "Atom feed",
"keys.ssh.link": "SSH keys",
"keys.gpg.link": "GPG keys",
"keys.verify.token.hint": "The token is only valid for 1 minute. <a href=\"%[1]s\">Get a new one if it expired</a>.",
"admin.config.moderation_config": "Moderation configuration",
"admin.moderation.moderation_reports": "Moderation reports",
"admin.moderation.reports": "Reports",

View file

@ -19,6 +19,7 @@
<div class="field">
<label for="token">{{ctx.Locale.Tr "settings.gpg_token"}}</label>
<input readonly="" value="{{.TokenToSign}}">
<span class="help">{{ctx.Locale.Tr "keys.verify.token.hint" (printf "?verify_gpg=%s" .KeyID)}}</span>
<div class="help">
<p>{{ctx.Locale.Tr "settings.gpg_token_help"}}</p>
<p><code>{{printf `echo "%s" | gpg -a --default-key %s --detach-sig` .TokenToSign .PaddedKeyID}}</code></p>
@ -88,6 +89,7 @@
<div class="field">
<label for="token">{{ctx.Locale.Tr "settings.gpg_token"}}</label>
<input readonly="" value="{{$.TokenToSign}}">
<span class="help">{{ctx.Locale.Tr "keys.verify.token.hint" (printf "?verify_gpg=%s" .KeyID)}}</span>
<div class="help">
<p>{{ctx.Locale.Tr "settings.gpg_token_help"}}</p>
<p><code>{{printf `echo "%s" | gpg -a --default-key %s --detach-sig` $.TokenToSign .PaddedKeyID}}</code></p>

View file

@ -74,6 +74,7 @@
<div class="field">
<label for="token">{{ctx.Locale.Tr "settings.ssh_token"}}</label>
<input readonly="" value="{{$.TokenToSign}}">
<span class="help">{{ctx.Locale.Tr "keys.verify.token.hint" (printf "?verify_ssh=%s" .Fingerprint)}}</span>
<div class="help">
<br>
<p>{{ctx.Locale.Tr "settings.ssh_token_help"}}</p>

View file

@ -0,0 +1,51 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package integration
import (
"fmt"
"net/http"
"net/url"
"strings"
"testing"
"forgejo.org/modules/test"
"forgejo.org/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestVerifySSHkeyPage(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// user2 has an SSH key in fixtures to test this on
session := loginUser(t, "user2")
page := NewHTMLParser(t, session.MakeRequest(t, NewRequest(t, "GET", "/user/settings/keys"), http.StatusOK).Body)
link, exists := page.Find("#keys-ssh a.button[href^='?verify_ssh=']").Attr("href")
assert.True(t, exists)
page = NewHTMLParser(t, session.MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("/user/settings/keys%s", link)), http.StatusOK).Body)
// QueryUnescape the link for selector matching
link, err := url.QueryUnescape(link)
require.NoError(t, err)
// The hint contains a link to the same page the user is at now to get it reloaded if followed
page.AssertElement(t, fmt.Sprintf("#keys-ssh form[action='/user/settings/keys'] .help a[href='%s']", link), true)
// The token changes every minute, we can avoid this sleep via timeutil and mocking.
test.SleepTillNextMinute()
// Verify that if you refresh it via the link another token is shown.
token, exists := page.Find("#keys-ssh form input[readonly]").Attr("value")
assert.True(t, exists)
link = url.QueryEscape(strings.TrimPrefix(link, "?verify_ssh="))
page = NewHTMLParser(t, session.MakeRequest(t, NewRequestf(t, "GET", "/user/settings/keys?verify_ssh=%s", link), http.StatusOK).Body)
otherToken, exists := page.Find("#keys-ssh form .field input[readonly]").Attr("value")
assert.True(t, exists)
assert.NotEqual(t, token, otherToken)
}