fix namings
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"git.dcrubro.com/dcrubro/hammy-backend/internal/apikey"
|
||||
"git.dcrubro.com/dcrubro/hammy-backend/internal/db"
|
||||
)
|
||||
|
||||
// runKeygen issues an API key from the command line. This is how you get the
|
||||
// first key, before there is any self-service registration, and how you issue
|
||||
// yourself something to test against.
|
||||
//
|
||||
// hammyd keygen -email [email protected] -scopes reference,callsign
|
||||
func runKeygen(ctx context.Context, args []string) error {
|
||||
fs := flag.NewFlagSet("keygen", flag.ExitOnError)
|
||||
|
||||
email := fs.String("email", "", "owner email (required); created if new")
|
||||
callsign := fs.String("callsign", "", "owner callsign (optional, uppercase)")
|
||||
label := fs.String("label", "", "what this key is for, shown in listings")
|
||||
scopes := fs.String("scopes", "reference", "comma-separated scopes")
|
||||
tier := fs.String("tier", "default", "quota tier: default, verified, trusted, internal")
|
||||
env := fs.String("env", "live", "live or test")
|
||||
expires := fs.Duration("expires", 0, "optional lifetime, e.g. 720h. Zero means no expiry")
|
||||
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *email == "" {
|
||||
fs.Usage()
|
||||
|
||||
return errors.New("keygen: -email is required")
|
||||
}
|
||||
|
||||
// The core.email domain requires lowercase and core.callsign requires
|
||||
// uppercase. Normalise here so the CLI is forgiving and the database stays
|
||||
// canonical.
|
||||
normEmail := strings.ToLower(strings.TrimSpace(*email))
|
||||
normCall := strings.ToUpper(strings.TrimSpace(*callsign))
|
||||
|
||||
dsn := os.Getenv("HAMMY_DSN")
|
||||
if dsn == "" {
|
||||
return errors.New("keygen: HAMMY_DSN is not set")
|
||||
}
|
||||
|
||||
pool, err := pgxpool.New(ctx, dsn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("keygen: connect: %w", err)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
q := db.New(pool)
|
||||
|
||||
owner, err := q.OwnerByEmail(ctx, normEmail)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
created, cerr := q.CreateOwner(ctx, db.CreateOwnerParams{
|
||||
Email: normEmail,
|
||||
Callsign: nullableCallsign(normCall),
|
||||
})
|
||||
if cerr != nil {
|
||||
return fmt.Errorf("keygen: creating owner: %w", cerr)
|
||||
}
|
||||
|
||||
owner.ID = created.ID
|
||||
fmt.Printf("created owner %d for %s\n", owner.ID, normEmail)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("keygen: looking up owner: %w", err)
|
||||
}
|
||||
|
||||
gen, err := apikey.Generate(apikey.Environment(*env))
|
||||
if err != nil {
|
||||
return fmt.Errorf("keygen: %w", err)
|
||||
}
|
||||
|
||||
var expiresAt *time.Time
|
||||
if *expires > 0 {
|
||||
t := time.Now().Add(*expires)
|
||||
expiresAt = &t
|
||||
}
|
||||
|
||||
scopeList := splitScopes(*scopes)
|
||||
|
||||
key, err := q.CreateKey(ctx, db.CreateKeyParams{
|
||||
OwnerID: owner.ID,
|
||||
KeyHash: gen.Hash,
|
||||
KeyPrefix: gen.Display,
|
||||
Label: nullableString(*label),
|
||||
Scopes: scopeList,
|
||||
QuotaTier: *tier,
|
||||
ExpiresAt: expiresAt,
|
||||
})
|
||||
if err != nil {
|
||||
// The keys_scopes_known CHECK rejects unknown scopes, which is the
|
||||
// most likely failure here and worth naming.
|
||||
return fmt.Errorf("keygen: creating key (check scopes against migrations/003_api.sql): %w", err)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Key issued. This is the only time it will be shown.")
|
||||
fmt.Println()
|
||||
fmt.Printf(" %s\n", gen.Plaintext)
|
||||
fmt.Println()
|
||||
fmt.Printf(" id %d\n", key.ID)
|
||||
fmt.Printf(" owner %d (%s)\n", owner.ID, normEmail)
|
||||
fmt.Printf(" scopes %s\n", strings.Join(scopeList, ", "))
|
||||
fmt.Printf(" tier %s\n", key.QuotaTier)
|
||||
|
||||
if expiresAt != nil {
|
||||
fmt.Printf(" expires %s\n", expiresAt.UTC().Format(time.RFC3339))
|
||||
} else {
|
||||
fmt.Printf(" expires never\n")
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Test it with:")
|
||||
fmt.Printf(" curl -H 'Authorization: Bearer %s' http://localhost:8080/v1/whoami\n", gen.Plaintext)
|
||||
fmt.Println()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitScopes(s string) []string {
|
||||
parts := strings.Split(s, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
|
||||
for _, p := range parts {
|
||||
if p = strings.TrimSpace(p); p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func nullableString(s string) *string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func nullableCallsign(s string) *string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &s
|
||||
}
|
||||
Reference in New Issue
Block a user