Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,9 @@ EdDSA owner authentication.
```

The derived address is `@user_bot@domain`. `agent` may contain letters, digits,
dots, and hyphens, but not underscores.
dots, and hyphens, but not underscores. The derived address is automatically
registered with fmsgid (with default quotas) if it doesn't already exist there
— no manual fmsgid step is needed for derived sub-accounts.

Delegated identities such as `@[email protected]` are not created by this
self-service route. They are operator-created with `api-key create-delegation`
Expand Down
8 changes: 8 additions & 0 deletions internal/handlers/subaccounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@ func (h *SubAccountHandler) Create(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "agent must be 1-64 letters/digits/dots/hyphens and contain no underscores"})
return
}
// Derived sub-accounts belong to an already-verified owner, so register
// them in fmsgid automatically rather than requiring a manual step.
// RegisterFmsgID never touches an address that already exists there.
if err := middleware.RegisterFmsgID(h.idURL, subAddr); err != nil {
log.Printf("sub-account create: fmsgid registration: addr=%s: %v", subAddr, err)
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "identity service unavailable"})
return
}
if !checkAcceptingFmsgID(c, h.idURL, subAddr) {
return
}
Expand Down
26 changes: 26 additions & 0 deletions internal/middleware/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
package middleware

import (
"bytes"
"context"
"crypto/ed25519"
"encoding/json"
"errors"
"fmt"
"log"
Expand Down Expand Up @@ -429,6 +431,30 @@ func CheckFmsgID(idURL, addr string) (int, bool, error) {
return res.code, res.acceptingNew, nil
}

// RegisterFmsgID registers addr with fmsgid using default quotas, idempotently.
// It never modifies an address that already exists there.
func RegisterFmsgID(idURL, addr string) error {
payload, err := json.Marshal(struct {
Address string `json:"address"`
}{Address: addr})
if err != nil {
return err
}

url := strings.TrimRight(idURL, "/") + "/fmsgid"
resp, err := fmsgIDClient.Post(url, "application/json", bytes.NewReader(payload)) //nolint:gosec // URL constructed from trusted config
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
return fmt.Errorf("fmsgid registration failed: status %d", resp.StatusCode)
}
fmsgIDCache.Delete(addr)
return nil
}

func fetchFmsgID(idURL, addr string) (fmsgIDResult, error) {
url := strings.TrimRight(idURL, "/") + "/fmsgid/" + addr
resp, err := fmsgIDClient.Get(url) //nolint:gosec // URL constructed from trusted config + validated addr
Expand Down
Loading