Move to Cobra for CLI

This commit is contained in:
Andrew-71 2024-10-13 16:16:19 +03:00
parent 452048359a
commit 1f50b8621e
15 changed files with 230 additions and 155 deletions

45
cmd/find_user.go Normal file
View file

@ -0,0 +1,45 @@
package cmd
import (
"fmt"
"git.a71.su/Andrew71/pye/storage"
"github.com/spf13/cobra"
)
var (
findMethod string
findQuery string
)
func init() {
rootCmd.AddCommand(findUserCmd)
}
var findUserCmd = &cobra.Command{
Use: "find <uuid/email> <query>",
Short: "Find a user",
Long: `Find information about a user from their UUID or email`,
Args: cobra.ExactArgs(2),
Run: findUser,
}
// TODO: Better name.
func findUser(cmd *cobra.Command, args []string) {
var user storage.User
var ok bool
if args[0] == "email" {
user, ok = storage.Data.ByEmail(args[1])
} else if args[0] == "uuid" {
user, ok = storage.Data.ById(args[1])
} else {
fmt.Println("expected email or uuid")
return
}
if !ok {
fmt.Println("User not found")
} else {
fmt.Printf("Information for user:\nuuid\t- %s\nemail\t- %s\nhash\t- %s\n",
user.Uuid, user.Email, user.Hash)
}
}

View file

@ -1,29 +0,0 @@
package find_user
import (
"fmt"
"git.a71.su/Andrew71/pye/config"
"git.a71.su/Andrew71/pye/storage"
"git.a71.su/Andrew71/pye/storage/sqlite"
)
func FindUser(mode, query string) {
data := sqlite.MustLoadSQLite(config.Cfg.SQLiteFile)
var user storage.User
var ok bool
if mode == "email" {
user, ok = data.ByEmail(query)
} else if mode == "uuid" {
user, ok = data.ById(query)
} else {
fmt.Println("expected email or uuid")
return
}
if !ok {
fmt.Println("User not found")
} else {
fmt.Printf("Information for user:\nuuid\t- %s\nemail\t- %s\nhash\t- %s\n",
user.Uuid, user.Email, user.Hash)
}
}

View file

@ -1,68 +0,0 @@
package cmd
import (
"flag"
"fmt"
"log/slog"
"os"
"git.a71.su/Andrew71/pye/cmd/find_user"
"git.a71.su/Andrew71/pye/cmd/serve"
"git.a71.su/Andrew71/pye/cmd/verify"
"git.a71.su/Andrew71/pye/config"
"git.a71.su/Andrew71/pye/logging"
)
func Run() {
serveCmd := flag.NewFlagSet("serve", flag.ExitOnError)
serveConfig := serveCmd.String("config", "", "override config file")
servePort := serveCmd.Int("port", 0, "override port")
serveDb := serveCmd.String("db", "", "override sqlite database")
serveDebug := serveCmd.Bool("debug", false, "debug logging")
verifyCmd := flag.NewFlagSet("verify", flag.ExitOnError)
verifyDebug := verifyCmd.Bool("debug", false, "debug logging")
if len(os.Args) < 2 {
fmt.Println("expected 'serve' or 'verify' subcommands")
os.Exit(0)
}
switch os.Args[1] {
case "serve":
serveCmd.Parse(os.Args[2:])
logging.LogInit(*serveDebug)
if *serveConfig != "" {
err := config.LoadConfig(*serveConfig)
if err != nil {
slog.Error("error loading custom config", "error", err)
}
}
if *servePort != 0 {
config.Cfg.Port = *servePort
}
if *serveDb != "" {
config.Cfg.SQLiteFile = *serveDb
}
serve.Serve()
case "verify":
verifyCmd.Parse(os.Args[2:])
logging.LogInit(*verifyDebug)
if len(os.Args) < 4 {
fmt.Println("Usage: <jwt> <pem file> [--debug]")
} else {
verify.Verify(os.Args[2], os.Args[3])
}
case "user":
if len(os.Args) !=4 {
fmt.Println("Usage: <uuid/email> <query>")
} else {
find_user.FindUser(os.Args[2], os.Args[3])
}
default:
fmt.Println("expected 'serve'/'verify'/'user' subcommands")
os.Exit(0)
}
}

54
cmd/root.go Normal file
View file

@ -0,0 +1,54 @@
package cmd
import (
"fmt"
"log/slog"
"os"
"git.a71.su/Andrew71/pye/config"
"git.a71.su/Andrew71/pye/logging"
"git.a71.su/Andrew71/pye/storage"
"git.a71.su/Andrew71/pye/storage/sqlite"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "pye",
Short: "Pye is a simple JWT system",
Long: `A bare-bones authentication system built by Andrew71 as an assignment`,
}
var (
cfgFile string
cfgDb string
debugMode *bool
)
func initConfig() {
logging.LogInit(*debugMode)
if cfgFile != "" {
err := config.LoadConfig(cfgFile)
if err != nil {
slog.Error("error loading custom config", "error", err)
}
}
if cfgDb != "" {
config.Cfg.SQLiteFile = cfgDb
}
storage.Data = sqlite.MustLoadSQLite(config.Cfg.SQLiteFile)
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "config.json", "config file")
rootCmd.PersistentFlags().StringVar(&cfgDb, "db", "", "database to use")
debugMode = rootCmd.PersistentFlags().BoolP("debug", "d", false, "enable debug mode")
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

42
cmd/serve.go Normal file
View file

@ -0,0 +1,42 @@
package cmd
import (
"log/slog"
"net/http"
"strconv"
"git.a71.su/Andrew71/pye/auth"
"git.a71.su/Andrew71/pye/config"
"github.com/spf13/cobra"
)
var port int
func init() {
serveCmd.Flags().IntVarP(&port, "port", "p", config.Cfg.Port, "port to use")
rootCmd.AddCommand(serveCmd)
}
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Start JWT service",
Long: `Start a simple authentication service`,
Run: serveAuth,
}
func serveAuth(cmd *cobra.Command, args []string) {
router := http.NewServeMux()
router.HandleFunc("GET /pem", auth.PublicKey)
router.HandleFunc("POST /register", auth.Register)
router.HandleFunc("POST /login", auth.Login)
// Note: likely temporary, possibly to be replaced by a fake "frontend"
router.HandleFunc("GET /register", auth.Register)
router.HandleFunc("GET /login", auth.Login)
slog.Info("🪐 pye started", "port", port)
slog.Debug("debug mode active")
http.ListenAndServe(":"+strconv.Itoa(port), router)
}

View file

@ -1,33 +0,0 @@
package serve
import (
"log/slog"
"net/http"
"strconv"
"git.a71.su/Andrew71/pye/auth"
"git.a71.su/Andrew71/pye/config"
"git.a71.su/Andrew71/pye/storage"
"git.a71.su/Andrew71/pye/storage/sqlite"
)
var data storage.Storage
func Serve() {
data = sqlite.MustLoadSQLite(config.Cfg.SQLiteFile)
router := http.NewServeMux()
router.HandleFunc("GET /pem", auth.PublicKey)
router.HandleFunc("POST /register", func(w http.ResponseWriter, r *http.Request) { auth.Register(w, r, data) })
router.HandleFunc("POST /login", func(w http.ResponseWriter, r *http.Request) { auth.Login(w, r, data) })
// Note: likely temporary, possibly to be replaced by a fake "frontend"
router.HandleFunc("GET /register", func(w http.ResponseWriter, r *http.Request) { auth.Register(w, r, data) })
router.HandleFunc("GET /login", func(w http.ResponseWriter, r *http.Request) { auth.Login(w, r, data) })
slog.Info("🪐 pye started", "port", config.Cfg.Port)
slog.Debug("debug mode active")
http.ListenAndServe(":"+strconv.Itoa(config.Cfg.Port), router)
}

54
cmd/verify.go Normal file
View file

@ -0,0 +1,54 @@
package cmd
import (
"fmt"
"log/slog"
"os"
"git.a71.su/Andrew71/pye/auth"
"github.com/golang-jwt/jwt/v5"
"github.com/spf13/cobra"
)
var (
verifyToken string
verifyFile string
)
func init() {
verifyCmd.Flags().StringVarP(&verifyToken, "token", "t", "", "token to verify")
verifyCmd.MarkFlagRequired("token")
verifyCmd.Flags().StringVarP(&verifyFile, "file", "f", "", "file to use")
rootCmd.AddCommand(verifyCmd)
}
var verifyCmd = &cobra.Command{
Use: "verify",
Short: "Verify a JWT token",
Long: `Pass a JWT token and a path to PEM-encoded file with a public key
to verify whether it is legit.`,
Run: verifyFunc,
}
// TODO: Better name.
func verifyFunc(cmd *cobra.Command, args []string) {
if verifyToken == "" {
fmt.Println("Empty token supplied!")
return
}
var t *jwt.Token
var err error
if verifyFile == "" {
fmt.Println("No PEM file supplied, assuming local")
t, err = auth.VerifyLocalJWT(verifyToken)
} else {
key, err_k := os.ReadFile(verifyFile)
if err_k != nil {
slog.Error("error reading file", "error", err, "file", verifyFile)
return
}
t, err = auth.VerifyJWT(verifyToken, key)
}
slog.Info("result", "token", t, "error", err, "ok", err == nil)
}

View file

@ -1,17 +0,0 @@
package verify
import (
"log/slog"
"os"
"git.a71.su/Andrew71/pye/auth"
)
func Verify(token, filename string) {
key, err := os.ReadFile(filename)
if err != nil {
slog.Error("error reading file", "error", err, "file", filename)
}
t, err := auth.VerifyJWT(token, key)
slog.Info("result", "token", t, "error", err, "ok", err == nil)
}