diff --git a/cmd/admin/main.go b/cmd/admin/main.go deleted file mode 100644 --- a/cmd/admin/main.go +++ /dev/null @@ -1,186 +0,0 @@ -package main - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "encoding/json" - "fmt" - "os" - "time" - - "github.com/bluesky-social/indigo/atproto/crypto" - "github.com/bluesky-social/indigo/atproto/syntax" - "github.com/haileyok/cocoon/internal/helpers" - "github.com/lestrrat-go/jwx/v2/jwk" - "github.com/urfave/cli/v2" - "golang.org/x/crypto/bcrypt" - "gorm.io/driver/sqlite" - "gorm.io/gorm" -) - -func main() { - app := cli.App{ - Name: "admin", - Commands: cli.Commands{ - runCreateRotationKey, - runCreatePrivateJwk, - runCreateInviteCode, - runResetPassword, - }, - ErrWriter: os.Stdout, - } - - app.Run(os.Args) -} - -var runCreateRotationKey = &cli.Command{ - Name: "create-rotation-key", - Usage: "creates a rotation key for your pds", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "out", - Required: true, - Usage: "output file for your rotation key", - }, - }, - Action: func(cmd *cli.Context) error { - key, err := crypto.GeneratePrivateKeyK256() - if err != nil { - return err - } - - bytes := key.Bytes() - - if err := os.WriteFile(cmd.String("out"), bytes, 0644); err != nil { - return err - } - - return nil - }, -} - -var runCreatePrivateJwk = &cli.Command{ - Name: "create-private-jwk", - Usage: "creates a private jwk for your pds", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "out", - Required: true, - Usage: "output file for your jwk", - }, - }, - Action: func(cmd *cli.Context) error { - privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return err - } - - key, err := jwk.FromRaw(privKey) - if err != nil { - return err - } - - kid := fmt.Sprintf("%d", time.Now().Unix()) - - if err := key.Set(jwk.KeyIDKey, kid); err != nil { - return err - } - - b, err := json.Marshal(key) - if err != nil { - return err - } - - if err := os.WriteFile(cmd.String("out"), b, 0644); err != nil { - return err - } - - return nil - }, -} - -var runCreateInviteCode = &cli.Command{ - Name: "create-invite-code", - Usage: "creates an invite code", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "for", - Usage: "optional did to assign the invite code to", - }, - &cli.IntFlag{ - Name: "uses", - Usage: "number of times the invite code can be used", - Value: 1, - }, - }, - Action: func(cmd *cli.Context) error { - db, err := newDb() - if err != nil { - return err - } - - forDid := "did:plc:123" - if cmd.String("for") != "" { - did, err := syntax.ParseDID(cmd.String("for")) - if err != nil { - return err - } - - forDid = did.String() - } - - uses := cmd.Int("uses") - - code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(8), helpers.RandomVarchar(8)) - - if err := db.Exec("INSERT INTO invite_codes (did, code, remaining_use_count) VALUES (?, ?, ?)", forDid, code, uses).Error; err != nil { - return err - } - - fmt.Printf("New invite code created with %d uses: %s\n", uses, code) - - return nil - }, -} - -var runResetPassword = &cli.Command{ - Name: "reset-password", - Usage: "resets a password", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "did", - Usage: "did of the user who's password you want to reset", - }, - }, - Action: func(cmd *cli.Context) error { - db, err := newDb() - if err != nil { - return err - } - - didStr := cmd.String("did") - did, err := syntax.ParseDID(didStr) - if err != nil { - return err - } - - newPass := fmt.Sprintf("%s-%s", helpers.RandomVarchar(12), helpers.RandomVarchar(12)) - hashed, err := bcrypt.GenerateFromPassword([]byte(newPass), 10) - if err != nil { - return err - } - - if err := db.Exec("UPDATE repos SET password = ? WHERE did = ?", hashed, did.String()).Error; err != nil { - return err - } - - fmt.Printf("Password for %s has been reset to: %s", did.String(), newPass) - - return nil - }, -} - -func newDb() (*gorm.DB, error) { - return gorm.Open(sqlite.Open("cocoon.db"), &gorm.Config{}) -} diff --git a/cmd/cocoon/main.go b/cmd/cocoon/main.go --- a/cmd/cocoon/main.go +++ b/cmd/cocoon/main.go @@ -1,12 +1,24 @@ package main import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/json" "fmt" "os" + "time" + "github.com/bluesky-social/indigo/atproto/crypto" + "github.com/bluesky-social/indigo/atproto/syntax" + "github.com/haileyok/cocoon/internal/helpers" "github.com/haileyok/cocoon/server" _ "github.com/joho/godotenv/autoload" + "github.com/lestrrat-go/jwx/v2/jwk" "github.com/urfave/cli/v2" + "golang.org/x/crypto/bcrypt" + "gorm.io/driver/sqlite" + "gorm.io/gorm" ) var Version = "dev" @@ -121,7 +133,11 @@ EnvVars: []string{"COCOON_SESSION_SECRET"}, }, }, Commands: []*cli.Command{ - run, + runServe, + runCreateRotationKey, + runCreatePrivateJwk, + runCreateInviteCode, + runResetPassword, }, ErrWriter: os.Stdout, Version: Version, @@ -132,7 +148,7 @@ fmt.Printf("Error: %v\n", err) } } -var run = &cli.Command{ +var runServe = &cli.Command{ Name: "run", Usage: "Start the cocoon PDS", Flags: []cli.Flag{}, @@ -177,3 +193,154 @@ return nil }, } + +var runCreateRotationKey = &cli.Command{ + Name: "create-rotation-key", + Usage: "creates a rotation key for your pds", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "out", + Required: true, + Usage: "output file for your rotation key", + }, + }, + Action: func(cmd *cli.Context) error { + key, err := crypto.GeneratePrivateKeyK256() + if err != nil { + return err + } + + bytes := key.Bytes() + + if err := os.WriteFile(cmd.String("out"), bytes, 0644); err != nil { + return err + } + + return nil + }, +} + +var runCreatePrivateJwk = &cli.Command{ + Name: "create-private-jwk", + Usage: "creates a private jwk for your pds", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "out", + Required: true, + Usage: "output file for your jwk", + }, + }, + Action: func(cmd *cli.Context) error { + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + + key, err := jwk.FromRaw(privKey) + if err != nil { + return err + } + + kid := fmt.Sprintf("%d", time.Now().Unix()) + + if err := key.Set(jwk.KeyIDKey, kid); err != nil { + return err + } + + b, err := json.Marshal(key) + if err != nil { + return err + } + + if err := os.WriteFile(cmd.String("out"), b, 0644); err != nil { + return err + } + + return nil + }, +} + +var runCreateInviteCode = &cli.Command{ + Name: "create-invite-code", + Usage: "creates an invite code", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "for", + Usage: "optional did to assign the invite code to", + }, + &cli.IntFlag{ + Name: "uses", + Usage: "number of times the invite code can be used", + Value: 1, + }, + }, + Action: func(cmd *cli.Context) error { + db, err := newDb() + if err != nil { + return err + } + + forDid := "did:plc:123" + if cmd.String("for") != "" { + did, err := syntax.ParseDID(cmd.String("for")) + if err != nil { + return err + } + + forDid = did.String() + } + + uses := cmd.Int("uses") + + code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(8), helpers.RandomVarchar(8)) + + if err := db.Exec("INSERT INTO invite_codes (did, code, remaining_use_count) VALUES (?, ?, ?)", forDid, code, uses).Error; err != nil { + return err + } + + fmt.Printf("New invite code created with %d uses: %s\n", uses, code) + + return nil + }, +} + +var runResetPassword = &cli.Command{ + Name: "reset-password", + Usage: "resets a password", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "did", + Usage: "did of the user who's password you want to reset", + }, + }, + Action: func(cmd *cli.Context) error { + db, err := newDb() + if err != nil { + return err + } + + didStr := cmd.String("did") + did, err := syntax.ParseDID(didStr) + if err != nil { + return err + } + + newPass := fmt.Sprintf("%s-%s", helpers.RandomVarchar(12), helpers.RandomVarchar(12)) + hashed, err := bcrypt.GenerateFromPassword([]byte(newPass), 10) + if err != nil { + return err + } + + if err := db.Exec("UPDATE repos SET password = ? WHERE did = ?", hashed, did.String()).Error; err != nil { + return err + } + + fmt.Printf("Password for %s has been reset to: %s", did.String(), newPass) + + return nil + }, +} + +func newDb() (*gorm.DB, error) { + return gorm.Open(sqlite.Open("cocoon.db"), &gorm.Config{}) +}