Add basic authentication
This commit is contained in:
340
actions/client.go
Normal file
340
actions/client.go
Normal file
@@ -0,0 +1,340 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gitea.benny.dog/torjus/ezshare/certs"
|
||||
"gitea.benny.dog/torjus/ezshare/config"
|
||||
"gitea.benny.dog/torjus/ezshare/pb"
|
||||
"gitea.benny.dog/torjus/ezshare/server"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
func ActionClientGet(c *cli.Context) error {
|
||||
addr := c.String("addr")
|
||||
conn, err := grpc.DialContext(c.Context, addr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := pb.NewFileServiceClient(conn)
|
||||
|
||||
for _, arg := range c.Args().Slice() {
|
||||
req := &pb.GetFileRequest{Id: arg}
|
||||
resp, err := client.GetFile(c.Context, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filename := resp.File.FileId
|
||||
if resp.File.Metadata.OriginalFilename != "" {
|
||||
filename = resp.File.Metadata.OriginalFilename
|
||||
}
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := f.Write(resp.File.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Wrote file '%s'\n", filename)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ActionClientUpload(c *cli.Context) error {
|
||||
cfg, err := getConfig(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr := cfg.Client.DefaultServer
|
||||
if c.IsSet("addr") {
|
||||
addr = c.String("addr")
|
||||
}
|
||||
|
||||
clientCreds, err := cfg.Client.Creds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := grpc.DialContext(c.Context, addr, grpc.WithTransportCredentials(clientCreds))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := pb.NewFileServiceClient(conn)
|
||||
|
||||
for _, arg := range c.Args().Slice() {
|
||||
f, err := os.Open(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &pb.UploadFileRequest{Data: data, OriginalFilename: filepath.Base(arg)}
|
||||
|
||||
resp, err := client.UploadFile(c.Context, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s uploaded with id %s. Available at %s\n", arg, resp.Id, resp.FileUrl)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ActionClientList(c *cli.Context) error {
|
||||
cfg, err := getConfig(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr := cfg.Client.DefaultServer
|
||||
if c.IsSet("addr") {
|
||||
addr = c.String("addr")
|
||||
}
|
||||
|
||||
clientCreds, err := cfg.Client.Creds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := grpc.DialContext(c.Context, addr, grpc.WithTransportCredentials(clientCreds))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := pb.NewFileServiceClient(conn)
|
||||
|
||||
resp, err := client.ListFiles(c.Context, &pb.ListFilesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, elem := range resp.Files {
|
||||
fmt.Println(elem.FileId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ActionClientDelete(c *cli.Context) error {
|
||||
cfg, err := getConfig(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr := cfg.Client.DefaultServer
|
||||
if c.IsSet("addr") {
|
||||
addr = c.String("addr")
|
||||
}
|
||||
|
||||
clientCreds, err := cfg.Client.Creds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := grpc.DialContext(c.Context, addr, grpc.WithTransportCredentials(clientCreds))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := pb.NewFileServiceClient(conn)
|
||||
for _, arg := range c.Args().Slice() {
|
||||
_, err := client.DeleteFile(c.Context, &pb.DeleteFileRequest{Id: arg})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting file: %w", err)
|
||||
}
|
||||
fmt.Printf("Deleted file %s\n", arg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ActionClientLogin(c *cli.Context) error {
|
||||
if err := config.CreateDefaultConfigDir(); err != nil {
|
||||
return err
|
||||
}
|
||||
configFilePath, err := config.DefaultConfigFilePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configFilePath); !errors.Is(err, os.ErrNotExist) {
|
||||
if err == nil {
|
||||
if !c.Bool("overwrite") {
|
||||
return cli.Exit("Config already exists. To overwrite run with --overwrite\n", 1)
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
configDirPath := filepath.Dir(configFilePath)
|
||||
clientCertPath := filepath.Join(configDirPath, "client.pem")
|
||||
clientKeyPath := filepath.Join(configDirPath, "client.key")
|
||||
serverCertPath := filepath.Join(configDirPath, "server.pem")
|
||||
|
||||
if c.Args().Len() != 1 {
|
||||
return cli.Exit("Need 1 argument", 1)
|
||||
}
|
||||
|
||||
// Fetch server certificate
|
||||
ctx, cancel := context.WithTimeout(c.Context, 10*time.Second)
|
||||
defer cancel()
|
||||
certEndpoint := fmt.Sprintf("%s/%s", c.Args().First(), "server.pem")
|
||||
certReq, err := http.NewRequestWithContext(ctx, http.MethodGet, certEndpoint, nil)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable to create http request: %s", err), 1)
|
||||
}
|
||||
verbosePrint(c, fmt.Sprintf("fetching cert from %s", certEndpoint))
|
||||
certResp, err := http.DefaultClient.Do(certReq)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("error fetching server cert: %s", err), 1)
|
||||
}
|
||||
defer certResp.Body.Close()
|
||||
|
||||
serverCert, err := io.ReadAll(certResp.Body)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("error reading certificate from server: %s", err), 1)
|
||||
}
|
||||
|
||||
// Fetch metadata
|
||||
mdEndpoint := fmt.Sprintf("%s/%s", c.Args().First(), "metadata")
|
||||
mdReq, err := http.NewRequestWithContext(ctx, http.MethodGet, mdEndpoint, nil)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable to create http request: %s", err), 1)
|
||||
}
|
||||
|
||||
verbosePrint(c, fmt.Sprintf("fetching metadata from %s", mdEndpoint))
|
||||
mdResp, err := http.DefaultClient.Do(mdReq)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("error fetching server cert: %s", err), 1)
|
||||
}
|
||||
defer mdResp.Body.Close()
|
||||
|
||||
decoder := json.NewDecoder(mdResp.Body)
|
||||
var md server.MetadataResponse
|
||||
if err := decoder.Decode(&md); err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable to decode metadata response: %s", err), 1)
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
fmt.Printf("username: ")
|
||||
scanner.Scan()
|
||||
username := scanner.Text()
|
||||
|
||||
fmt.Printf("password: ")
|
||||
passwordBytes, err := term.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable to read password: %s", err), 1)
|
||||
}
|
||||
password := string(passwordBytes)
|
||||
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(serverCert) {
|
||||
return cli.Exit(fmt.Sprintf("unable to use server certificate: %s", err), 1)
|
||||
}
|
||||
// Generate temporary self-signed cert
|
||||
keyBytes, certBytes, err := certs.GenCACert()
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable to generate self-signed certificate: %s", err), 1)
|
||||
}
|
||||
keyPem, err := certs.ToPEM(keyBytes, "EC PRIVATE KEY")
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable to pem-encode key: %s", err), 1)
|
||||
}
|
||||
certPem, err := certs.ToPEM(certBytes, "CERTIFICATE")
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable to pem-encode key: %s", err), 1)
|
||||
}
|
||||
clientCert, err := tls.X509KeyPair(certPem, keyPem)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable to use self-signed certificate: %s", err), 1)
|
||||
}
|
||||
|
||||
creds := credentials.NewTLS(&tls.Config{RootCAs: certPool, Certificates: []tls.Certificate{clientCert}})
|
||||
|
||||
verbosePrint(c, fmt.Sprintf("dialing grpc at %s", md.GRPCEndpoint))
|
||||
conn, err := grpc.DialContext(c.Context, md.GRPCEndpoint, grpc.WithTransportCredentials(creds))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := pb.NewUserServiceClient(conn)
|
||||
|
||||
resp, err := client.Login(c.Context, &pb.LoginUserRequest{Username: username, Password: password})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write key to file
|
||||
verbosePrint(c, fmt.Sprintf("Writing client certificate key to %s", clientKeyPath))
|
||||
keyFile, err := os.Create(clientKeyPath)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable create file for client key: %s", err), 1)
|
||||
}
|
||||
defer keyFile.Close()
|
||||
if _, err := keyFile.Write(resp.ClientKey); err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable write client key to file: %s", err), 1)
|
||||
}
|
||||
|
||||
// Write client cert to file
|
||||
verbosePrint(c, fmt.Sprintf("Writing client certificate to %s", clientKeyPath))
|
||||
clientCertFile, err := os.Create(clientCertPath)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable create file for client cert: %s", err), 1)
|
||||
}
|
||||
defer clientCertFile.Close()
|
||||
if _, err := clientCertFile.Write(resp.ClientCert); err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable write client cert to file: %s", err), 1)
|
||||
}
|
||||
|
||||
// Write server cer to file
|
||||
verbosePrint(c, fmt.Sprintf("Writing server certificate to %s", serverCertPath))
|
||||
serverCertFile, err := os.Create(serverCertPath)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable create file for client key: %s", err), 1)
|
||||
}
|
||||
defer serverCertFile.Close()
|
||||
if _, err := serverCertFile.Write(serverCert); err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable write client cert to file: %s", err), 1)
|
||||
}
|
||||
|
||||
// Write config
|
||||
cfg := config.FromDefault()
|
||||
cfg.Client.Certs.CertificateKeyPath = clientKeyPath
|
||||
cfg.Client.Certs.CertificatePath = clientCertPath
|
||||
cfg.Client.DefaultServer = md.GRPCEndpoint
|
||||
cfg.Client.ServerCertPath = serverCertPath
|
||||
|
||||
if err := cfg.ToDefaultFile(); err != nil {
|
||||
return cli.Exit(fmt.Sprintf("unable write config to file: %s", err), 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
44
actions/misc.go
Normal file
44
actions/misc.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.benny.dog/torjus/ezshare/certs"
|
||||
"gitea.benny.dog/torjus/ezshare/config"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func ActionGencerts(c *cli.Context) error {
|
||||
outDir := "."
|
||||
if c.IsSet("out-dir") {
|
||||
outDir = c.String("out-dir")
|
||||
}
|
||||
if !c.IsSet("hostname") {
|
||||
return fmt.Errorf("--hostname required")
|
||||
}
|
||||
hostname := c.String("hostname")
|
||||
return certs.GenAllCerts(outDir, hostname)
|
||||
}
|
||||
|
||||
func ActionInitConfig(c *cli.Context) error {
|
||||
defaultCfg := config.FromDefault()
|
||||
return defaultCfg.ToDefaultFile()
|
||||
}
|
||||
|
||||
func getConfig(c *cli.Context) (*config.Config, error) {
|
||||
if c.IsSet("config") {
|
||||
cfgPath := c.String("config")
|
||||
return config.FromFile(cfgPath)
|
||||
}
|
||||
cfg, err := config.FromDefaultLocations()
|
||||
if err == nil {
|
||||
verbosePrint(c, fmt.Sprintf("Config loaded from %s", cfg.Location()))
|
||||
}
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func verbosePrint(c *cli.Context, message string) {
|
||||
if c.Bool("verbose") {
|
||||
fmt.Println(message)
|
||||
}
|
||||
}
|
222
actions/serve.go
Normal file
222
actions/serve.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"gitea.benny.dog/torjus/ezshare/certs"
|
||||
"gitea.benny.dog/torjus/ezshare/pb"
|
||||
"gitea.benny.dog/torjus/ezshare/server"
|
||||
"gitea.benny.dog/torjus/ezshare/server/interceptors"
|
||||
"gitea.benny.dog/torjus/ezshare/store"
|
||||
"github.com/google/uuid"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
func ActionServe(c *cli.Context) error {
|
||||
cfg, err := getConfig(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read certificates
|
||||
srvCertBytes, err := cfg.Server.GRPC.Certs.GetCertBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srvKeyBytes, err := cfg.Server.GRPC.Certs.GetKeyBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
caCertBytes, err := cfg.Server.GRPC.CACerts.GetCertBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
caKeyBytes, err := cfg.Server.GRPC.CACerts.GetKeyBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup file store
|
||||
s, fileCloseFunc, err := cfg.Server.FileStoreConfig.GetStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize file store: %w", err)
|
||||
}
|
||||
defer fileCloseFunc()
|
||||
|
||||
// Setup user store
|
||||
userStore, userCloseFunc, err := cfg.Server.UserStoreConfig.GetStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to initialize user store: %w", err)
|
||||
}
|
||||
defer userCloseFunc()
|
||||
|
||||
// Create initial admin-user if neccessary
|
||||
if err := initializeUsers(userStore); err != nil {
|
||||
return fmt.Errorf("error initializing store: %w", err)
|
||||
}
|
||||
|
||||
// Setup cert store
|
||||
// TODO: User proper store
|
||||
certStore := store.NewMemoryStore()
|
||||
|
||||
// Setup cert-service
|
||||
certSvc, err := certs.NewCertService(certStore, caCertBytes, caKeyBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initializing certificate service: %w", err)
|
||||
}
|
||||
|
||||
// Setup shutdown-handling
|
||||
rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer rootCancel()
|
||||
|
||||
// Used to initiate grpc shutdown
|
||||
grpcCtx, grpcCancel := context.WithCancel(rootCtx)
|
||||
defer grpcCancel()
|
||||
|
||||
// Cancelled once grpc is successfully shut down
|
||||
grpcShutdownCtx, grpcShutdownCancel := context.WithCancel(context.Background())
|
||||
defer grpcShutdownCancel()
|
||||
|
||||
// Start grpc server
|
||||
go func() {
|
||||
grpcAddr := cfg.Server.GRPC.ListenAddr
|
||||
if c.IsSet("grpc-addr") {
|
||||
grpcAddr = c.String("grpc-addr")
|
||||
}
|
||||
|
||||
// Setup file-service
|
||||
grpcFileServer := server.NewGRPCFileServiceServer(s)
|
||||
grpcFileServer.Hostname = cfg.Server.Hostname
|
||||
if c.IsSet("hostname") {
|
||||
grpcFileServer.Hostname = c.String("hostname")
|
||||
}
|
||||
|
||||
// Setup user-service
|
||||
grpcUserServer := server.NewGRPCUserServiceServer(userStore, certSvc)
|
||||
|
||||
lis, err := net.Listen("tcp", grpcAddr)
|
||||
if err != nil {
|
||||
log.Printf("Unable to setup grpc listener: %s\n", err)
|
||||
rootCancel()
|
||||
}
|
||||
srvCert, err := tls.X509KeyPair(srvCertBytes, srvKeyBytes)
|
||||
if err != nil {
|
||||
log.Printf("Unable load server certs: %s\n", err)
|
||||
rootCancel()
|
||||
}
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(caCertBytes) {
|
||||
log.Println("Unable to load CA cert")
|
||||
rootCancel()
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{srvCert},
|
||||
ClientAuth: tls.RequireAnyClientCert,
|
||||
ClientCAs: certPool,
|
||||
}
|
||||
creds := credentials.NewTLS(tlsConfig)
|
||||
|
||||
grpcServer := grpc.NewServer(
|
||||
grpc.Creds(creds),
|
||||
grpc.ChainUnaryInterceptor(interceptors.NewAuthInterceptor(&store.MemoryStore{})),
|
||||
)
|
||||
pb.RegisterFileServiceServer(grpcServer, grpcFileServer)
|
||||
pb.RegisterUserServiceServer(grpcServer, grpcUserServer)
|
||||
|
||||
// wait for cancel
|
||||
go func() {
|
||||
<-grpcCtx.Done()
|
||||
grpcServer.GracefulStop()
|
||||
}()
|
||||
|
||||
log.Printf("Starting grpc server")
|
||||
if err = grpcServer.Serve(lis); err != nil {
|
||||
log.Printf("GRPC Shutdown with error: %s\n", err)
|
||||
rootCancel()
|
||||
}
|
||||
log.Println("GRPC Shutdown")
|
||||
grpcShutdownCancel()
|
||||
}()
|
||||
|
||||
httpCtx, httpCancel := context.WithCancel(rootCtx)
|
||||
defer httpCancel()
|
||||
|
||||
httpShutdownCtx, httpShutdownCancel := context.WithCancel(context.Background())
|
||||
defer httpShutdownCancel()
|
||||
// Start http server
|
||||
go func() {
|
||||
httpAddr := ":8088"
|
||||
if c.IsSet("http-addr") {
|
||||
httpAddr = c.String("http-addr")
|
||||
}
|
||||
httpServer := server.NewHTTPSever(s, srvCertBytes, cfg.Server.GRPCEndpoint)
|
||||
httpServer.Addr = httpAddr
|
||||
|
||||
// wait for cancel
|
||||
go func() {
|
||||
<-httpCtx.Done()
|
||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
httpServer.Shutdown(timeoutCtx)
|
||||
}()
|
||||
|
||||
log.Printf("Starting http server")
|
||||
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Printf("HTTP Server shutdown with error: %s\n", err)
|
||||
rootCancel()
|
||||
}
|
||||
log.Println("HTTP Shutdown")
|
||||
httpShutdownCancel()
|
||||
}()
|
||||
|
||||
<-grpcShutdownCtx.Done()
|
||||
<-httpShutdownCtx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
func initializeUsers(us store.UserStore) error {
|
||||
// TODO: Logging
|
||||
userIDs, err := us.ListUsers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(userIDs) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// no users, create initial admin-user
|
||||
log.Printf("No users in store. Creating admin-user.")
|
||||
password := uuid.Must(uuid.NewRandom()).String()
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
admin := &pb.User{
|
||||
Id: uuid.Must(uuid.NewRandom()).String(),
|
||||
HashedPassword: hashedPassword,
|
||||
Username: "admin",
|
||||
UserRole: pb.User_ADMIN,
|
||||
Active: true,
|
||||
}
|
||||
|
||||
if err := us.StoreUser(admin); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("user created %s:%s", admin.Username, password)
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user