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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,13 @@ the marketplace where it is:
termcade publish https://github.com/you/mygame v1.0.0 mygame.tcade
```

For automated publishing, log in and run `termcade keys new <name> <username>`.
The API asks Termcade's Clerk tenant to create an `ak_…` key restricted to that
handle. Set it as `TERMCADE_TOKEN` when running `termcade publish`. Manage keys
with `termcade keys list` and `termcade keys revoke <id>`. Existing `tck_…` keys
remain usable during the transition. Publishing keys cannot sign in or manage
accounts; browser-approved `termcade login` continues to use separate CLI sessions.

The registry fetches that asset once, validates it against the same manifest
rules the arcade enforces, reads the game's identity and version out of the
manifest inside it, and records its digest. Nothing you pass here asserts
Expand Down
3 changes: 1 addition & 2 deletions account.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ func cmdKeys(args []string) error {
if err != nil {
return err
}
// Printed once because it exists once. The registry stores a hash and
// cannot produce this again, so anything that loses it needs a new key.
// Reveal the Clerk-issued secret once; lists never return secrets.
fmt.Printf("created %q, publishing as %s\n\n %s\n\n", key.Name, key.Username, key.Token)
fmt.Fprintln(os.Stderr,
"that token is shown once and cannot be recovered — put it somewhere safe now")
Expand Down
4 changes: 2 additions & 2 deletions internal/registry/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func (c *Client) doContext(ctx context.Context, method, path string, body, out a
}

func (c *Client) authorizationValue() string {
if strings.HasPrefix(c.token, "tcc_") || strings.HasPrefix(c.token, "tck_") {
if strings.HasPrefix(c.token, "tcc_") || strings.HasPrefix(c.token, "tck_") || strings.HasPrefix(c.token, "ak_") {
return "Bearer " + c.token
}
return c.token
Expand Down Expand Up @@ -621,7 +621,7 @@ type Key struct {
Token string `json:"token,omitempty"`
}

// CreateKey mints a publish key scoped to one handle.
// CreateKey requests a Clerk publishing key scoped to one handle.
func (c *Client) CreateKey(name, username string) (Key, error) {
var out Key
body := map[string]string{"name": name, "username": username}
Expand Down
18 changes: 18 additions & 0 deletions internal/registry/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,21 @@ func TestResolveSendsTheABIAndNoVersion(t *testing.T) {
t.Errorf("resolve still asks for a version: %v", stub.resolveQuery)
}
}

func TestClerkPublishingKeyUsesBearerWithoutBecomingLogin(t *testing.T) {
token := "ak_" + strings.Repeat("A", 48)
if !IsPublishKey(token) || IsCLIToken(token) {
t.Fatal("publishing key classified as login or refused")
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/publish" || r.Header.Get("Authorization") != "Bearer "+token {
t.Error("publishing key sent incorrectly")
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":"owner/game","version":"1.0.0"}`))
}))
defer server.Close()
if _, err := New(server.URL, token).Publish("https://github.com/owner/game", "v1.0.0", "game.tcade"); err != nil {
t.Fatal(err)
}
}
2 changes: 1 addition & 1 deletion internal/registry/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ var (
userCodeRE = regexp.MustCompile(`^[ABCDEFGHJKMNPQRSTUVWXYZ23456789]{4}-[ABCDEFGHJKMNPQRSTUVWXYZ23456789]{4}$`)
deviceCodeRE = regexp.MustCompile(`^tcd_[A-Za-z0-9_-]{43}$`)
cliTokenRE = regexp.MustCompile(`^tcc_[A-Za-z0-9_-]{43}$`)
publishKeyRE = regexp.MustCompile(`^tck_[A-Za-z0-9_-]{43}$`)
publishKeyRE = regexp.MustCompile(`^(tck_[A-Za-z0-9_-]{43}|ak_[A-Za-z0-9_-]{20,512})$`)
)

func IsCLIToken(value string) bool { return cliTokenRE.MatchString(value) }
Expand Down
Loading