@@ -0,0 +1,198 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.gangary.cn/gary/Hugs-Proxy/internal/config"
|
||||
"gitea.gangary.cn/gary/Hugs-Proxy/internal/db"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := config.Load()
|
||||
if len(os.Args) < 2 {
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sub := os.Args[1]
|
||||
switch sub {
|
||||
case "create-user":
|
||||
handleCreateUser(cfg, os.Args[2:])
|
||||
case "issue-token":
|
||||
handleIssueToken(cfg, os.Args[2:])
|
||||
case "disable-token":
|
||||
handleDisableToken(cfg, os.Args[2:])
|
||||
case "list-users":
|
||||
handleListUsers(cfg, os.Args[2:])
|
||||
case "list-tokens":
|
||||
handleListTokens(cfg, os.Args[2:])
|
||||
default:
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func handleCreateUser(cfg config.Config, args []string) {
|
||||
fs := flag.NewFlagSet("create-user", flag.ExitOnError)
|
||||
dbPath := fs.String("db", cfg.DBPath, "SQLite DB path")
|
||||
name := fs.String("name", "", "username")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
if strings.TrimSpace(*name) == "" {
|
||||
log.Fatal("--name is required")
|
||||
}
|
||||
|
||||
store := openStore(*dbPath, cfg.BusyTimeoutMS)
|
||||
defer store.Close()
|
||||
|
||||
u, err := store.CreateUser(context.Background(), strings.TrimSpace(*name))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("created user: id=%d username=%s created_at=%s\n", u.ID, u.Username, u.CreatedAt.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
func handleIssueToken(cfg config.Config, args []string) {
|
||||
fs := flag.NewFlagSet("issue-token", flag.ExitOnError)
|
||||
dbPath := fs.String("db", cfg.DBPath, "SQLite DB path")
|
||||
username := fs.String("user", "", "username")
|
||||
expires := fs.String("expires", "", "expires duration like 7d/30d or RFC3339 time")
|
||||
uses := fs.Int("uses", 1, "max usable times for this token, must be > 0")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
if strings.TrimSpace(*username) == "" {
|
||||
log.Fatal("--user is required")
|
||||
}
|
||||
if *uses <= 0 {
|
||||
log.Fatal("--uses must be > 0")
|
||||
}
|
||||
|
||||
store := openStore(*dbPath, cfg.BusyTimeoutMS)
|
||||
defer store.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
u, err := store.GetUserByName(ctx, strings.TrimSpace(*username))
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNotFound) {
|
||||
log.Fatalf("user %q not found", *username)
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
expiresAt, err := parseExpiresAt(*expires, cfg.DefaultTokenTTL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
tok, err := store.IssueToken(ctx, u.ID, expiresAt, *uses)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("issued token: id=%d user=%s token=%s expires_at=%s max_uses=%d\n", tok.ID, u.Username, tok.Token, tok.ExpiresAt.Format(time.RFC3339), tok.MaxUses)
|
||||
}
|
||||
|
||||
func handleDisableToken(cfg config.Config, args []string) {
|
||||
fs := flag.NewFlagSet("disable-token", flag.ExitOnError)
|
||||
dbPath := fs.String("db", cfg.DBPath, "SQLite DB path")
|
||||
token := fs.String("token", "", "token value")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
if strings.TrimSpace(*token) == "" {
|
||||
log.Fatal("--token is required")
|
||||
}
|
||||
|
||||
store := openStore(*dbPath, cfg.BusyTimeoutMS)
|
||||
defer store.Close()
|
||||
|
||||
err := store.DisableToken(context.Background(), strings.TrimSpace(*token))
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNotFound) {
|
||||
log.Fatalf("token not found")
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("token disabled")
|
||||
}
|
||||
|
||||
func handleListUsers(cfg config.Config, args []string) {
|
||||
fs := flag.NewFlagSet("list-users", flag.ExitOnError)
|
||||
dbPath := fs.String("db", cfg.DBPath, "SQLite DB path")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
store := openStore(*dbPath, cfg.BusyTimeoutMS)
|
||||
defer store.Close()
|
||||
|
||||
users, err := store.ListUsers(context.Background())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("id\tusername\tcreated_at")
|
||||
for _, u := range users {
|
||||
fmt.Printf("%d\t%s\t%s\n", u.ID, u.Username, u.CreatedAt.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
|
||||
func handleListTokens(cfg config.Config, args []string) {
|
||||
fs := flag.NewFlagSet("list-tokens", flag.ExitOnError)
|
||||
dbPath := fs.String("db", cfg.DBPath, "SQLite DB path")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
store := openStore(*dbPath, cfg.BusyTimeoutMS)
|
||||
defer store.Close()
|
||||
|
||||
tokens, err := store.ListTokens(context.Background())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("id\tuser\tdisabled\tused/max\texpires_at\tcreated_at\ttoken")
|
||||
for _, t := range tokens {
|
||||
maxUsesDisplay := fmt.Sprintf("%d", t.MaxUses)
|
||||
if t.MaxUses == 0 {
|
||||
maxUsesDisplay = "unlimited"
|
||||
}
|
||||
fmt.Printf("%d\t%s\t%t\t%d/%s\t%s\t%s\t%s\n", t.ID, t.Username, t.Disabled, t.UsedCount, maxUsesDisplay, t.ExpiresAt.Format(time.RFC3339), t.CreatedAt.Format(time.RFC3339), t.Token)
|
||||
}
|
||||
}
|
||||
|
||||
func parseExpiresAt(raw string, defaultTTL time.Duration) (time.Time, error) {
|
||||
now := time.Now().UTC()
|
||||
clean := strings.TrimSpace(raw)
|
||||
if clean == "" {
|
||||
return now.Add(defaultTTL), nil
|
||||
}
|
||||
if t, err := time.Parse(time.RFC3339, clean); err == nil {
|
||||
return t.UTC(), nil
|
||||
}
|
||||
d, err := config.ParseExpiryDuration(clean)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("invalid --expires value: %w", err)
|
||||
}
|
||||
return now.Add(d), nil
|
||||
}
|
||||
|
||||
func openStore(dbPath string, busyTimeoutMS int) *db.Store {
|
||||
store, err := db.NewStore(strings.TrimSpace(dbPath), busyTimeoutMS)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println(`tokenctl usage:
|
||||
tokenctl create-user --name <username> [--db <path>]
|
||||
tokenctl issue-token --user <username> [--expires <7d|30d|RFC3339>] [--uses <n>] [--db <path>]
|
||||
tokenctl disable-token --token <token> [--db <path>]
|
||||
tokenctl list-users [--db <path>]
|
||||
tokenctl list-tokens [--db <path>]`)
|
||||
}
|
||||
Reference in New Issue
Block a user