Move to Cobra for CLI
This commit is contained in:
parent
452048359a
commit
1f50b8621e
15 changed files with 230 additions and 155 deletions
2
Makefile
2
Makefile
|
@ -1,7 +1,7 @@
|
||||||
build:
|
build:
|
||||||
go build
|
go build
|
||||||
|
|
||||||
serve:
|
run:
|
||||||
go build && ./pye serve
|
go build && ./pye serve
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
|
|
12
auth/auth.go
12
auth/auth.go
|
@ -19,21 +19,21 @@ func validPass(pass string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register creates a new user with credentials provided through Basic Auth
|
// Register creates a new user with credentials provided through Basic Auth
|
||||||
func Register(w http.ResponseWriter, r *http.Request, data storage.Storage) {
|
func Register(w http.ResponseWriter, r *http.Request) {
|
||||||
email, password, ok := r.BasicAuth()
|
email, password, ok := r.BasicAuth()
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
email = strings.TrimSpace(email)
|
email = strings.TrimSpace(email)
|
||||||
password = strings.TrimSpace(password)
|
password = strings.TrimSpace(password)
|
||||||
if !(validEmail(email) && validPass(password) && !data.EmailExists(email)) {
|
if !(validEmail(email) && validPass(password) && !storage.Data.EmailExists(email)) {
|
||||||
slog.Debug("Outcome",
|
slog.Debug("Outcome",
|
||||||
"email", validEmail(email),
|
"email", validEmail(email),
|
||||||
"pass", validPass(password),
|
"pass", validPass(password),
|
||||||
"taken", !data.EmailExists(email))
|
"taken", !storage.Data.EmailExists(email))
|
||||||
http.Error(w, "invalid auth credentials", http.StatusBadRequest)
|
http.Error(w, "invalid auth credentials", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := data.AddUser(email, password)
|
err := storage.Data.AddUser(email, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("error adding a new user", "error", err)
|
slog.Error("error adding a new user", "error", err)
|
||||||
http.Error(w, "error adding a new user", http.StatusInternalServerError)
|
http.Error(w, "error adding a new user", http.StatusInternalServerError)
|
||||||
|
@ -50,13 +50,13 @@ func Register(w http.ResponseWriter, r *http.Request, data storage.Storage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login returns JWT for a registered user through Basic Auth
|
// Login returns JWT for a registered user through Basic Auth
|
||||||
func Login(w http.ResponseWriter, r *http.Request, data storage.Storage) {
|
func Login(w http.ResponseWriter, r *http.Request) {
|
||||||
email, password, ok := r.BasicAuth()
|
email, password, ok := r.BasicAuth()
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
email = strings.TrimSpace(email)
|
email = strings.TrimSpace(email)
|
||||||
password = strings.TrimSpace(password)
|
password = strings.TrimSpace(password)
|
||||||
user, ok := data.ByEmail(email)
|
user, ok := storage.Data.ByEmail(email)
|
||||||
if !ok || !user.PasswordFits(password) {
|
if !ok || !user.PasswordFits(password) {
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||||
http.Error(w, "you did something wrong", http.StatusUnauthorized)
|
http.Error(w, "you did something wrong", http.StatusUnauthorized)
|
||||||
|
|
|
@ -101,3 +101,9 @@ func VerifyJWT(token string, publicKey []byte) (*jwt.Token, error) {
|
||||||
})
|
})
|
||||||
return t, err
|
return t, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func VerifyLocalJWT(token string) (*jwt.Token, error) {
|
||||||
|
key_marshalled := x509.MarshalPKCS1PublicKey(&key.PublicKey)
|
||||||
|
block := pem.Block{Bytes: key_marshalled, Type: "RSA PUBLIC KEY"}
|
||||||
|
return VerifyJWT(token, pem.EncodeToMemory(&block))
|
||||||
|
}
|
45
cmd/find_user.go
Normal file
45
cmd/find_user.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
68
cmd/main.go
68
cmd/main.go
|
@ -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
54
cmd/root.go
Normal 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
42
cmd/serve.go
Normal 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)
|
||||||
|
}
|
|
@ -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
54
cmd/verify.go
Normal 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)
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
|
6
go.mod
6
go.mod
|
@ -6,5 +6,11 @@ require (
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.24
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
|
github.com/spf13/cobra v1.8.1
|
||||||
golang.org/x/crypto v0.28.0
|
golang.org/x/crypto v0.28.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
)
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -1,8 +1,18 @@
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
2
main.go
2
main.go
|
@ -3,5 +3,5 @@ package main
|
||||||
import "git.a71.su/Andrew71/pye/cmd"
|
import "git.a71.su/Andrew71/pye/cmd"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd.Run()
|
cmd.Execute()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
|
// Storage is an arbitrary storage interface
|
||||||
type Storage interface {
|
type Storage interface {
|
||||||
AddUser(email, password string) error
|
AddUser(email, password string) error
|
||||||
ById(uuid string) (User, bool)
|
ById(uuid string) (User, bool)
|
||||||
ByEmail(uuid string) (User, bool)
|
ByEmail(uuid string) (User, bool)
|
||||||
EmailExists(email string) bool
|
EmailExists(email string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Data stores active information for the app
|
||||||
|
// It should be populated at app startup
|
||||||
|
var Data Storage
|
||||||
|
|
Loading…
Reference in a new issue