mirror of https://gitlab.com/ngerakines/tavern.git
209 lines
5.1 KiB
Go
209 lines
5.1 KiB
Go
package start
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"runtime"
|
|
|
|
"github.com/getsentry/sentry-go"
|
|
"github.com/kr/pretty"
|
|
"github.com/urfave/cli/v2"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
"github.com/ngerakines/tavern/config"
|
|
"github.com/ngerakines/tavern/errors"
|
|
"github.com/ngerakines/tavern/g"
|
|
"github.com/ngerakines/tavern/storage"
|
|
)
|
|
|
|
var InitAdminCommand = cli.Command{
|
|
Name: "init-admin",
|
|
Usage: "Initialize the server",
|
|
Flags: []cli.Flag{
|
|
&config.EnvironmentFlag,
|
|
&config.ListenFlag,
|
|
&config.DomainFlag,
|
|
&config.DatabaseFlag,
|
|
&config.TranslationsFlag,
|
|
&cli.StringFlag{
|
|
Name: "admin-email",
|
|
Usage: "The email address of the admin user",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "admin-password",
|
|
Usage: "The password of the admin user",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "admin-locale",
|
|
Usage: "The locale of the admin user",
|
|
Value: "en",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "admin-name",
|
|
Usage: "The name of the admin user",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "admin-displayname",
|
|
Usage: "The display name of the admin user",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "admin-about",
|
|
Usage: "The 'about me' of the admin user",
|
|
},
|
|
},
|
|
Action: initAdminCommandAction,
|
|
}
|
|
|
|
func initAdminCommandAction(cliCtx *cli.Context) error {
|
|
logger, err := config.Logger(cliCtx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
domain := cliCtx.String("domain")
|
|
siteBase := fmt.Sprintf("https://%s", domain)
|
|
|
|
logger.Info("Starting",
|
|
zap.String("command", cliCtx.Command.Name),
|
|
zap.String("GOOS", runtime.GOOS),
|
|
zap.String("site", siteBase),
|
|
zap.String("env", cliCtx.String("environment")))
|
|
|
|
sentryConfig, err := config.NewSentryConfig(cliCtx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if sentryConfig.Enabled {
|
|
err = sentry.Init(sentry.ClientOptions{
|
|
Dsn: sentryConfig.Key,
|
|
Environment: cliCtx.String("environment"),
|
|
Release: fmt.Sprintf("%s-%s", g.Release, g.GitCommit),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sentry.ConfigureScope(func(scope *sentry.Scope) {
|
|
scope.SetTags(map[string]string{"container": "server"})
|
|
})
|
|
defer sentry.Recover()
|
|
}
|
|
|
|
db, dbClose, err := config.DB(cliCtx, logger)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dbClose()
|
|
|
|
ctx := context.Background()
|
|
|
|
txErr := storage.TransactionalStorage(ctx, storage.DefaultStorage(storage.LoggingSQLDriver{Driver: db, Logger: logger}), func(s storage.Storage) error {
|
|
|
|
name := cliCtx.String("admin-name")
|
|
displayName := cliCtx.String("admin-displayname")
|
|
if len(displayName) == 0 {
|
|
displayName = name
|
|
}
|
|
about := cliCtx.String("admin-about")
|
|
if len(about) == 0 {
|
|
about = "Just a user"
|
|
}
|
|
|
|
encPassword, err := bcrypt.GenerateFromPassword([]byte(cliCtx.String("admin-password")), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
privateKeyBytes := x509.MarshalPKCS1PrivateKey(key)
|
|
var privateKeyBuffer bytes.Buffer
|
|
if err := pem.Encode(&privateKeyBuffer, &pem.Block{
|
|
Type: "PRIVATE KEY",
|
|
Bytes: privateKeyBytes,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
privateKey := string(privateKeyBuffer.Bytes())
|
|
|
|
publicKeyBytes, err := x509.MarshalPKIXPublicKey(key.Public())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var publicKeyBuffer bytes.Buffer
|
|
if err = pem.Encode(&publicKeyBuffer, &pem.Block{
|
|
Type: "PUBLIC KEY",
|
|
Bytes: publicKeyBytes,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
publicKey := string(publicKeyBuffer.Bytes())
|
|
|
|
// ActorFromUserInfo(name, displayName, domain, publicKey string, privateKey *rsa.PrivateKey) Payload {
|
|
userActor := storage.ActorFromUserInfo(name, displayName, domain, publicKey, key)
|
|
|
|
actorID, hasActorID := storage.JSONString(userActor, "id")
|
|
if !hasActorID || len(actorID) == 0 {
|
|
pretty.Println(userActor)
|
|
return fmt.Errorf("missing id from actor")
|
|
}
|
|
keyID, hasKeyID := storage.JSONDeepString(userActor, "publicKey", "id")
|
|
if !hasKeyID || len(keyID) == 0 {
|
|
pretty.Println(userActor)
|
|
return fmt.Errorf("missing key id from actor")
|
|
}
|
|
|
|
err = s.CreateActor(ctx, actorID, userActor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
actorRowID, err := s.ActorRowIDForActorID(ctx, actorID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = s.CreateUser(ctx, actorRowID, cliCtx.String("admin-email"), cliCtx.String("admin-locale"), name, displayName, about, publicKey, privateKey, encPassword)
|
|
if err != nil {
|
|
logger.Error("unable to create user", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
user, err := s.GetUserByName(ctx, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = s.RecordActorKey(ctx, actorRowID, keyID, publicKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = s.RecordActorAlias(ctx, actorRowID, fmt.Sprintf("acct:%s@%s", user.Name, domain), storage.ActorAliasSubject); err != nil {
|
|
return err
|
|
}
|
|
if err = s.RecordActorAlias(ctx, actorRowID, actorID, storage.ActorAliasSelf); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if txErr != nil {
|
|
logger.Error("error creating user", zap.Error(txErr), zap.Strings("error_chain", errors.ErrorChain(txErr)))
|
|
}
|
|
return txErr
|
|
}
|