init
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
package clicmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/iceBear67/simplepages/api"
|
||||
"github.com/iceBear67/simplepages/internal/cliutil"
|
||||
)
|
||||
|
||||
func keyCmd(g *Globals) *cliutil.Command {
|
||||
return &cliutil.Command{
|
||||
Name: "key",
|
||||
Short: "Manage API keys",
|
||||
Sub: []*cliutil.Command{
|
||||
keyCreateCmd(g),
|
||||
keyListCmd(g),
|
||||
keyRevokeCmd(g),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func keyCreateCmd(g *Globals) *cliutil.Command {
|
||||
var (
|
||||
name string
|
||||
admin bool
|
||||
expires string
|
||||
)
|
||||
return &cliutil.Command{
|
||||
Name: "create",
|
||||
Short: "Mint a key",
|
||||
Long: "The token is printed once and cannot be retrieved again — the server\n" +
|
||||
"stores only its hash. Redirect it straight into a file or a secret\n" +
|
||||
"store; do not let it reach a CI log.\n\n" +
|
||||
"Without --admin the key is scoped to one project and can do nothing\n" +
|
||||
"outside it. Requires an admin key either way.",
|
||||
Flags: func(fs *flag.FlagSet) {
|
||||
fs.StringVar(&name, "name", "", "`label` recorded with the key, e.g. github-actions")
|
||||
fs.BoolVar(&admin, "admin", false, "mint an admin key instead of a project key")
|
||||
fs.StringVar(&expires, "expires", "", "expire after this `duration`, e.g. 90d; default never")
|
||||
},
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
if err := exactArgs(args, 0, "no arguments"); err != nil {
|
||||
return err
|
||||
}
|
||||
req := api.CreateKeyRequest{Name: name}
|
||||
if expires != "" {
|
||||
d, err := cliutil.ParseDuration(expires)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d <= 0 {
|
||||
return errors.New("--expires must be positive")
|
||||
}
|
||||
// Computed here, sent absolute: a clock difference between this
|
||||
// machine and the server then shifts nothing.
|
||||
t := time.Now().Add(d).UTC().Truncate(time.Second)
|
||||
req.ExpiresAt = &t
|
||||
}
|
||||
|
||||
c, err := g.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var out api.CreateKeyResponse
|
||||
if admin {
|
||||
out, err = c.CreateAdminKey(ctx, req)
|
||||
} else {
|
||||
var project string
|
||||
if project, err = g.ProjectName(); err != nil {
|
||||
return fmt.Errorf("%w, or pass --admin for a server-wide key", err)
|
||||
}
|
||||
out, err = c.CreateProjectKey(ctx, project, req)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := g.Printer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Format == cliutil.FormatJSON {
|
||||
return p.JSON(out)
|
||||
}
|
||||
// Table format prints the token on a line of its own so it survives
|
||||
// a copy-paste and so `pages key create | tail -1` is not tempting.
|
||||
if err := keyTable(out.Key).Write(g.Out); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(g.Out, "\n%s\n", out.Token)
|
||||
fmt.Fprintln(g.Err, "this token is shown once and cannot be recovered; store it now")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func keyListCmd(g *Globals) *cliutil.Command {
|
||||
return &cliutil.Command{
|
||||
Name: "list",
|
||||
Short: "List keys",
|
||||
Long: "With --project, lists that project's keys; a project key may list its\n" +
|
||||
"own. Without it, lists every key on the server and requires an admin\n" +
|
||||
"key. Secrets are never listed — only key ids.",
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
if err := exactArgs(args, 0, "no arguments"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.Resolve(); err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := g.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var list api.KeyList
|
||||
if g.Project != "" {
|
||||
list, err = c.ListProjectKeys(ctx, g.Project)
|
||||
} else {
|
||||
list, err = c.ListKeys(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if list.Keys == nil {
|
||||
list.Keys = []api.Key{}
|
||||
}
|
||||
p, err := g.Printer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.Print(list, func() *cliutil.Table {
|
||||
t := cliutil.NewTable("ID", "SCOPE", "PROJECT", "NAME", "CREATED", "EXPIRES", "LAST USED", "STATE")
|
||||
for _, k := range list.Keys {
|
||||
t.Row(k.ID, k.Scope, cliutil.Str(k.Project), cliutil.Str(k.Name),
|
||||
cliutil.Time(k.CreatedAt), cliutil.TimePtr(k.ExpiresAt),
|
||||
cliutil.TimePtr(k.LastUsed), keyState(k))
|
||||
}
|
||||
return t
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func keyRevokeCmd(g *Globals) *cliutil.Command {
|
||||
var yes bool
|
||||
return &cliutil.Command{
|
||||
Name: "revoke",
|
||||
Args: "<key-id>",
|
||||
Short: "Revoke a key",
|
||||
Long: "Takes effect immediately across the server. The key id is the middle\n" +
|
||||
"segment of a token (pgs_<key-id>_<secret>) and is what `key list`\n" +
|
||||
"shows. Revoking an already-revoked key succeeds.",
|
||||
Flags: func(fs *flag.FlagSet) {
|
||||
fs.BoolVar(&yes, "yes", false, "do not ask for confirmation")
|
||||
},
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
if err := exactArgs(args, 1, "one key id"); err != nil {
|
||||
return err
|
||||
}
|
||||
id := args[0]
|
||||
if !yes {
|
||||
if err := cliutil.Confirm(g.In, g.Err, fmt.Sprintf("Revoke key %s?", id)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c, err := g.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.RevokeKey(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
p, err := g.Printer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Printf("revoked key %s\n", id)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// keyState collapses the two timestamps that decide whether a key still works.
|
||||
func keyState(k api.Key) string {
|
||||
switch {
|
||||
case k.Revoked():
|
||||
return "revoked"
|
||||
case k.ExpiresAt != nil && k.ExpiresAt.Before(time.Now()):
|
||||
return "expired"
|
||||
default:
|
||||
return "active"
|
||||
}
|
||||
}
|
||||
|
||||
func keyTable(k api.Key) *cliutil.Table {
|
||||
t := cliutil.NewTable()
|
||||
t.Row("id", k.ID)
|
||||
t.Row("scope", k.Scope)
|
||||
t.Row("project", cliutil.Str(k.Project))
|
||||
t.Row("name", cliutil.Str(k.Name))
|
||||
t.Row("created_at", cliutil.Time(k.CreatedAt))
|
||||
t.Row("expires_at", cliutil.TimePtr(k.ExpiresAt))
|
||||
return t
|
||||
}
|
||||
Reference in New Issue
Block a user