fix: keep the device polling token off the QR code

golem15.user.device_auth.auth_url gains a {short_code} placeholder and
defaults to {frontend_url}/device/scan/{short_code}; POST
/devices/authorize accepts the short code in its token field, so a scan
page that posts what its URL carries keeps working. The status poll hands
the session token over only to a client with the User-Agent the device
initiated with; another client sees the session confirmed and takes
nothing.
This commit is contained in:
2026-10-26 16:12:08 +01:00
parent 557c03c595
commit 07196a4bcb
6 changed files with 142 additions and 24 deletions

View File

@@ -15,8 +15,11 @@ import (
// recovery-code alphabet: no 0, 1, I, L or O).
const DeviceShortCodeAlphabet = "23456789ABCDEFGHJKMNPQRSTUVWXYZ"
// defaultDeviceAuthURL is golem15.user.device_auth.auth_url when unset.
const defaultDeviceAuthURL = "{frontend_url}/device/scan/{token}"
// defaultDeviceAuthURL is golem15.user.device_auth.auth_url when unset. It
// carries the short code, never the device token: the token is the secret
// the device polls the session token with, and a QR code, a scan URL in a
// browser history or a Referer must not hand it to an onlooker.
const defaultDeviceAuthURL = "{frontend_url}/device/scan/{short_code}"
// defaultDeviceAuthTTL is golem15.user.device_auth.ttl_minutes when unset.
const defaultDeviceAuthTTL = 5 * time.Minute
@@ -88,12 +91,21 @@ func DeviceAuthTTL(app *backpack.App) time.Duration {
return defaultDeviceAuthTTL
}
// DeviceAuthURL is the auth_url a new device shows as its QR code: the
// template golem15.user.device_auth.auth_url (default
// {frontend_url}/device/scan/{token}) with {frontend_url} replaced by
// FrontendURL (golem15.user.frontend_url, else app.url, no trailing slash)
// and {token} by the device token.
// DeviceAuthURL is DeviceAuthURLFor without a short code: {short_code}
// is filled with the device token, as the template's only key.
func DeviceAuthURL(app *backpack.App, token string) string {
return DeviceAuthURLFor(app, token, "")
}
// DeviceAuthURLFor is the auth_url a new device shows as its QR code: the
// template golem15.user.device_auth.auth_url (default
// {frontend_url}/device/scan/{short_code}) with {frontend_url} replaced by
// FrontendURL (golem15.user.frontend_url, else app.url, no trailing slash),
// {short_code} by the session's short code and {token} by the device
// token. POST /devices/authorize accepts the short code in its token field
// too, so a page that posts what its URL carries as token works with
// either template. An empty shortCode fills {short_code} with the token.
func DeviceAuthURLFor(app *backpack.App, token, shortCode string) string {
tpl := ""
if app != nil && app.Config != nil {
tpl = strings.TrimSpace(app.Config.String("golem15.user.device_auth.auth_url"))
@@ -101,7 +113,10 @@ func DeviceAuthURL(app *backpack.App, token string) string {
if tpl == "" {
tpl = defaultDeviceAuthURL
}
return strings.NewReplacer("{frontend_url}", FrontendURL(app), "{token}", token).Replace(tpl)
if shortCode == "" {
shortCode = token
}
return strings.NewReplacer("{frontend_url}", FrontendURL(app), "{short_code}", shortCode, "{token}", token).Replace(tpl)
}
// DeviceDisplayName is the name a device list shows (PHP's device_name