Add a read_all scope and explicit permanent tokens

An integration token used to be bound to the resource list that existed when it
was minted, so every new basemap needed a fresh token. read_all grants read on
every basemap and vector, including ones catalogued later, which is what an
internal consumer of the whole catalogue actually wants.

Tokens also gain a way to opt out of the default lifetime: expires_at accepts
never (or 0) in the API and the console, producing a token with no expiry.

The console shows blanket access as a badge instead of an empty grant list, and
the token form exposes both new options.
This commit is contained in:
2026-10-16 12:19:36 +08:00
parent 8e0fa11f8f
commit 0a2ecef667
6 changed files with 145 additions and 19 deletions
+21 -4
View File
@@ -95,6 +95,14 @@ func New(store *basemap.Store, system basemap.SystemInfo, options Options) (*Ser
}
return strings.Join(parts, ", ")
},
"hasScope": func(scopes []string, want string) bool {
for _, scope := range scopes {
if scope == want {
return true
}
}
return false
},
"vectorTokenGrants": func(grants []basemap.VectorTokenGrant) string {
if len(grants) == 0 {
return "-"
@@ -1118,7 +1126,11 @@ func decodeTokenInput(r *http.Request) (basemap.CreateTokenInput, error) {
VectorCodes: payload.VectorCodes,
Scopes: payload.Scopes,
}
if strings.TrimSpace(payload.ExpiresAt) != "" {
switch strings.ToLower(strings.TrimSpace(payload.ExpiresAt)) {
case "never", "0":
input.NoExpiry = true
case "":
default:
parsed, err := time.Parse(time.RFC3339, payload.ExpiresAt)
if err != nil {
return basemap.CreateTokenInput{}, fmt.Errorf("parse expires_at: %w", err)
@@ -1139,9 +1151,14 @@ func decodeTokenInputFromForm(r *http.Request) (basemap.CreateTokenInput, error)
Scopes: splitCSV(r.FormValue("scopes")),
}
if value := strings.TrimSpace(r.FormValue("expires_at")); value != "" {
if minutes, err := strconv.Atoi(value); err == nil && minutes > 0 {
expiresAt := time.Now().UTC().Add(time.Duration(minutes) * time.Minute)
input.ExpiresAt = &expiresAt
switch strings.ToLower(value) {
case "never", "0":
input.NoExpiry = true
default:
if minutes, err := strconv.Atoi(value); err == nil && minutes > 0 {
expiresAt := time.Now().UTC().Add(time.Duration(minutes) * time.Minute)
input.ExpiresAt = &expiresAt
}
}
}
return input, nil