Merge pull request 'feature/auth' (#6) from feature/auth into master
Reviewed-on: https://gitea.benny.dog/torjus/ezshare/pulls/6
This commit is contained in:
commit
156ff4e207
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
|
||||||
|
}
|
@ -15,6 +15,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ToPEM(data []byte, pemType string) ([]byte, error) {
|
||||||
|
pemData := new(bytes.Buffer)
|
||||||
|
err := pem.Encode(pemData, &pem.Block{
|
||||||
|
Type: pemType,
|
||||||
|
Bytes: data,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pemData.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func WriteCert(data []byte, filename string) error {
|
func WriteCert(data []byte, filename string) error {
|
||||||
// Convert to PEM
|
// Convert to PEM
|
||||||
certPEM := new(bytes.Buffer)
|
certPEM := new(bytes.Buffer)
|
||||||
@ -104,7 +117,7 @@ func GenAllCerts(path, domain string) error {
|
|||||||
|
|
||||||
// Create server certs
|
// Create server certs
|
||||||
dnsNames := []string{domain}
|
dnsNames := []string{domain}
|
||||||
srvKey, srvCrt, err := GenCert(caPub, caPriv, dnsNames)
|
srvKey, srvCrt, err := GenCert("server", caPub, caPriv, dnsNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -115,7 +128,7 @@ func GenAllCerts(path, domain string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
clientKey, clientCrt, err := GenCert(caPub, caPriv, []string{})
|
clientKey, clientCrt, err := GenCert("client", caPub, caPriv, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -129,7 +142,7 @@ func GenAllCerts(path, domain string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenCert(caPub, caPrivKey []byte, dnsNames []string) (priv, pub []byte, err error) {
|
func GenCert(cn string, caPub, caPrivKey []byte, dnsNames []string) (priv, pub []byte, err error) {
|
||||||
// Parse ca
|
// Parse ca
|
||||||
ca, err := x509.ParseCertificate(caPub)
|
ca, err := x509.ParseCertificate(caPub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -144,6 +157,7 @@ func GenCert(caPub, caPrivKey []byte, dnsNames []string) (priv, pub []byte, err
|
|||||||
cert := &x509.Certificate{
|
cert := &x509.Certificate{
|
||||||
SerialNumber: big.NewInt(time.Now().Unix()),
|
SerialNumber: big.NewInt(time.Now().Unix()),
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
|
CommonName: cn,
|
||||||
Organization: []string{"ezshare"},
|
Organization: []string{"ezshare"},
|
||||||
Country: []string{"No"},
|
Country: []string{"No"},
|
||||||
Locality: []string{"Oslo"},
|
Locality: []string{"Oslo"},
|
||||||
|
452
cmd/ezshare.go
452
cmd/ezshare.go
@ -1,452 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"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"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/credentials"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
app := cli.App{
|
|
||||||
Name: "ezshare",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "config",
|
|
||||||
Usage: "Path to config-file.",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "verbose",
|
|
||||||
Aliases: []string{"v"},
|
|
||||||
Usage: "Be more verbose",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Commands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "serve",
|
|
||||||
Usage: "Start ezshare server",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "no-grpc",
|
|
||||||
Usage: "Do not enable grpc.",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "no-http",
|
|
||||||
Usage: "Do not enable http.",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "grpc-addr",
|
|
||||||
Usage: "Address to listen for grpc.",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "http-addr",
|
|
||||||
Usage: "Address to listen for http.",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "hostname",
|
|
||||||
Usage: "Hostname used in links",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: ActionServe,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "client",
|
|
||||||
Usage: "Client commands",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "addr",
|
|
||||||
Usage: "Address of server.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "get",
|
|
||||||
Usage: "Get file with id",
|
|
||||||
ArgsUsage: "ID [ID]..",
|
|
||||||
Action: ActionClientGet,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "upload",
|
|
||||||
Usage: "Upload file(s)",
|
|
||||||
ArgsUsage: "PATH [PATH]..",
|
|
||||||
Action: ActionClientUpload,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "delete",
|
|
||||||
Usage: "Delete file with id",
|
|
||||||
ArgsUsage: "ID [ID]..",
|
|
||||||
Action: ActionClientDelete,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "list",
|
|
||||||
Usage: "List files",
|
|
||||||
Action: ActionClientList,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "config-init",
|
|
||||||
Usage: "Initialize default config",
|
|
||||||
Action: ActionInitConfig,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "cert",
|
|
||||||
Usage: "Certificate commands",
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "gen-all",
|
|
||||||
Usage: "Generate CA, Server and Client certificates",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "out-dir",
|
|
||||||
Usage: "Directory where certificates will be stored.",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "hostname",
|
|
||||||
Usage: "Hostname used for server certificate.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: ActionGencerts,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := app.Run(os.Args)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup store
|
|
||||||
s, closeFunc, err := cfg.Server.StoreConfig.GetStore()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to initialize store: %w", err)
|
|
||||||
}
|
|
||||||
defer closeFunc()
|
|
||||||
// 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")
|
|
||||||
}
|
|
||||||
|
|
||||||
grpcFileServer := server.NewGRPCFileServiceServer(s)
|
|
||||||
grpcFileServer.Hostname = cfg.Server.Hostname
|
|
||||||
if c.IsSet("hostname") {
|
|
||||||
grpcFileServer.Hostname = c.String("hostname")
|
|
||||||
}
|
|
||||||
|
|
||||||
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.RequestClientCert,
|
|
||||||
ClientCAs: certPool,
|
|
||||||
}
|
|
||||||
creds := credentials.NewTLS(tlsConfig)
|
|
||||||
|
|
||||||
grpcServer := grpc.NewServer(
|
|
||||||
grpc.Creds(creds),
|
|
||||||
)
|
|
||||||
pb.RegisterFileServiceServer(grpcServer, grpcFileServer)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
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 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 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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,6 +17,8 @@ import (
|
|||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrNotFound = fmt.Errorf("config not found")
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Server *ServerConfig `toml:"Server"`
|
Server *ServerConfig `toml:"Server"`
|
||||||
Client *ClientConfig `toml:"Client"`
|
Client *ClientConfig `toml:"Client"`
|
||||||
@ -28,19 +30,26 @@ type CertificatePaths struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
LogLevel string `toml:"LogLevel"`
|
LogLevel string `toml:"LogLevel"`
|
||||||
Hostname string `toml:"Hostname"`
|
Hostname string `toml:"Hostname"`
|
||||||
StoreConfig *ServerStoreConfig `toml:"Store"`
|
GRPCEndpoint string `toml:"GRPCEndpoint"`
|
||||||
GRPC *ServerGRPCConfig `toml:"GRPC"`
|
UserStoreConfig *ServerUserStoreConfig `toml:"UserStore"`
|
||||||
HTTP *ServerHTTPConfig `toml:"HTTP"`
|
FileStoreConfig *ServerFileStoreConfig `toml:"FileStore"`
|
||||||
|
GRPC *ServerGRPCConfig `toml:"GRPC"`
|
||||||
|
HTTP *ServerHTTPConfig `toml:"HTTP"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerStoreConfig struct {
|
type ServerFileStoreConfig struct {
|
||||||
Type string `toml:"Type"`
|
Type string `toml:"Type"`
|
||||||
FSStoreConfig *FSStoreConfig `toml:"Filesystem"`
|
FSStoreConfig *FSStoreConfig `toml:"Filesystem"`
|
||||||
BoltStoreConfig *BoltStoreConfig `toml:"Bolt"`
|
BoltStoreConfig *BoltStoreConfig `toml:"Bolt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServerUserStoreConfig struct {
|
||||||
|
Type string `toml:"Type"`
|
||||||
|
BoltStoreConfig *BoltStoreConfig `toml:"Bolt"`
|
||||||
|
}
|
||||||
|
|
||||||
type BoltStoreConfig struct {
|
type BoltStoreConfig struct {
|
||||||
Path string `toml:"Path"`
|
Path string `toml:"Path"`
|
||||||
}
|
}
|
||||||
@ -78,7 +87,9 @@ func FromDefault() *Config {
|
|||||||
ListenAddr: ":8089",
|
ListenAddr: ":8089",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Client: &ClientConfig{},
|
Client: &ClientConfig{
|
||||||
|
Certs: &CertificatePaths{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
@ -97,6 +108,9 @@ func FromReader(r io.Reader) (*Config, error) {
|
|||||||
func FromFile(path string) (*Config, error) {
|
func FromFile(path string) (*Config, error) {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("unable to open config-file: %w", err)
|
return nil, fmt.Errorf("unable to open config-file: %w", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
@ -188,7 +202,7 @@ func (cc *ClientConfig) Creds() (credentials.TransportCredentials, error) {
|
|||||||
return credentials.NewTLS(config), nil
|
return credentials.NewTLS(config), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) ToDefaultFile() error {
|
func CreateDefaultConfigDir() error {
|
||||||
userConfigDir, err := os.UserConfigDir()
|
userConfigDir, err := os.UserConfigDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -207,8 +221,25 @@ func (c *Config) ToDefaultFile() error {
|
|||||||
return fmt.Errorf("config-directory is not a directory")
|
return fmt.Errorf("config-directory is not a directory")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func DefaultConfigFilePath() (string, error) {
|
||||||
|
userConfigDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(userConfigDir, "ezshare", "ezshare.toml"), nil
|
||||||
|
|
||||||
configFilePath := filepath.Join(configDirPath, "ezshare.toml")
|
}
|
||||||
|
|
||||||
|
func (c *Config) ToDefaultFile() error {
|
||||||
|
if err := CreateDefaultConfigDir(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configFilePath, err := DefaultConfigFilePath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
_, err = os.Stat(configFilePath)
|
_, err = os.Stat(configFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
@ -226,7 +257,7 @@ func (c *Config) ToDefaultFile() error {
|
|||||||
return fmt.Errorf("config-file already exists")
|
return fmt.Errorf("config-file already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *ServerStoreConfig) GetStore() (store.FileStore, func() error, error) {
|
func (sc *ServerFileStoreConfig) GetStore() (store.FileStore, func() error, error) {
|
||||||
nopCloseFunc := func() error { return nil }
|
nopCloseFunc := func() error { return nil }
|
||||||
if strings.EqualFold(sc.Type, "bolt") {
|
if strings.EqualFold(sc.Type, "bolt") {
|
||||||
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
|
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
|
||||||
@ -243,6 +274,39 @@ func (sc *ServerStoreConfig) GetStore() (store.FileStore, func() error, error) {
|
|||||||
if strings.EqualFold(sc.Type, "memory") {
|
if strings.EqualFold(sc.Type, "memory") {
|
||||||
return store.NewMemoryStore(), nopCloseFunc, nil
|
return store.NewMemoryStore(), nopCloseFunc, nil
|
||||||
}
|
}
|
||||||
|
if strings.EqualFold(sc.Type, "bolt") {
|
||||||
|
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
closeFunc := func() error { return s.Close() }
|
||||||
|
return s, closeFunc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, fmt.Errorf("invalid store config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *ServerUserStoreConfig) GetStore() (store.UserStore, func() error, error) {
|
||||||
|
nopCloseFunc := func() error { return nil }
|
||||||
|
if strings.EqualFold(sc.Type, "bolt") {
|
||||||
|
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return s, s.Close, err
|
||||||
|
|
||||||
|
}
|
||||||
|
if strings.EqualFold(sc.Type, "memory") {
|
||||||
|
return store.NewMemoryStore(), nopCloseFunc, nil
|
||||||
|
}
|
||||||
|
if strings.EqualFold(sc.Type, "bolt") {
|
||||||
|
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
closeFunc := func() error { return s.Close() }
|
||||||
|
return s, closeFunc, nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil, nil, fmt.Errorf("invalid store config")
|
return nil, nil, fmt.Errorf("invalid store config")
|
||||||
}
|
}
|
||||||
|
@ -12,23 +12,38 @@ LogLevel = "INFO"
|
|||||||
# Required
|
# Required
|
||||||
Hostname = "localhost"
|
Hostname = "localhost"
|
||||||
|
|
||||||
# Storage configuration
|
# Endpoint reachable by clients
|
||||||
[Server.Store]
|
# Fetched by clients for automatic setup
|
||||||
|
# Required
|
||||||
|
GRPCEndpoint = "localhost:50051"
|
||||||
|
|
||||||
|
# File store configuration
|
||||||
|
[Server.FileStore]
|
||||||
# How server stores file
|
# How server stores file
|
||||||
# Must be one of: filesystem, memory, bolt
|
# Must be one of: filesystem, memory, bolt
|
||||||
# Required
|
# Required
|
||||||
Type = "filesystem"
|
Type = "bolt"
|
||||||
|
|
||||||
[Server.Store.Bolt]
|
[Server.FileStore.Bolt]
|
||||||
# Where the bolt-db is stored
|
# Where the bolt-db is stored
|
||||||
# Required if store-type is bolt
|
# Required if store-type is bolt
|
||||||
Path = ""
|
Path = "/data/files.db"
|
||||||
|
|
||||||
[Server.Store.Filesystem]
|
[Server.FileStore.Filesystem]
|
||||||
# Where files are stored
|
# Where files are stored
|
||||||
# Required if store-type is filesystem
|
# Required if store-type is filesystem
|
||||||
Dir = "/data"
|
Dir = "/data"
|
||||||
|
|
||||||
|
[Server.UserStore]
|
||||||
|
# What store to use for users
|
||||||
|
# Must be one of: memory, bolt
|
||||||
|
# Required
|
||||||
|
Type = "bolt"
|
||||||
|
|
||||||
|
[Server.UserStore.Bolt]
|
||||||
|
# Path to bolt database-file
|
||||||
|
Path = "/data/users.db"
|
||||||
|
|
||||||
# GRPC Configuration
|
# GRPC Configuration
|
||||||
[Server.GRPC]
|
[Server.GRPC]
|
||||||
# Address to listen to
|
# Address to listen to
|
||||||
|
6
go.mod
6
go.mod
@ -4,10 +4,13 @@ go 1.17
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-chi/chi/v5 v5.0.7
|
github.com/go-chi/chi/v5 v5.0.7
|
||||||
|
github.com/google/go-cmp v0.5.6
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/pelletier/go-toml v1.9.4
|
github.com/pelletier/go-toml v1.9.4
|
||||||
github.com/urfave/cli/v2 v2.3.0
|
github.com/urfave/cli/v2 v2.3.0
|
||||||
go.etcd.io/bbolt v1.3.6
|
go.etcd.io/bbolt v1.3.6
|
||||||
|
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||||
google.golang.org/grpc v1.42.0
|
google.golang.org/grpc v1.42.0
|
||||||
google.golang.org/protobuf v1.27.1
|
google.golang.org/protobuf v1.27.1
|
||||||
)
|
)
|
||||||
@ -17,7 +20,8 @@ require (
|
|||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20211203184738-4852103109b8 // indirect
|
golang.org/x/net v0.0.0-20211203184738-4852103109b8 // indirect
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect
|
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9 // indirect
|
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9 // indirect
|
||||||
)
|
)
|
||||||
|
13
go.sum
13
go.sum
@ -49,8 +49,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
@ -75,6 +76,8 @@ go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
|||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8=
|
||||||
|
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
@ -87,6 +90,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211203184738-4852103109b8 h1:PFkPt/jI9Del3hmFplBtRp8tDhSRpFu7CyRs7VmEC0M=
|
golang.org/x/net v0.0.0-20211203184738-4852103109b8 h1:PFkPt/jI9Del3hmFplBtRp8tDhSRpFu7CyRs7VmEC0M=
|
||||||
golang.org/x/net v0.0.0-20211203184738-4852103109b8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211203184738-4852103109b8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@ -104,9 +108,12 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
|
||||||
|
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
142
main.go
Normal file
142
main.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gitea.benny.dog/torjus/ezshare/actions"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Version = "v0.1.1"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cli.VersionFlag = &cli.BoolFlag{Name: "version"}
|
||||||
|
app := cli.App{
|
||||||
|
Name: "ezshare",
|
||||||
|
Version: Version,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "config",
|
||||||
|
Usage: "Path to config-file.",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "verbose",
|
||||||
|
Aliases: []string{"v"},
|
||||||
|
Usage: "Be more verbose",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Commands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "serve",
|
||||||
|
Usage: "Start ezshare server",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-grpc",
|
||||||
|
Usage: "Do not enable grpc.",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-http",
|
||||||
|
Usage: "Do not enable http.",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "grpc-addr",
|
||||||
|
Usage: "Address to listen for grpc.",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "http-addr",
|
||||||
|
Usage: "Address to listen for http.",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "hostname",
|
||||||
|
Usage: "Hostname used in links",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: actions.ActionServe,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "client",
|
||||||
|
Usage: "Client commands",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "addr",
|
||||||
|
Usage: "Address of server.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "get",
|
||||||
|
Usage: "Get file with id",
|
||||||
|
ArgsUsage: "ID [ID]..",
|
||||||
|
Action: actions.ActionClientGet,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "upload",
|
||||||
|
Usage: "Upload file(s)",
|
||||||
|
ArgsUsage: "PATH [PATH]..",
|
||||||
|
Action: actions.ActionClientUpload,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "delete",
|
||||||
|
Usage: "Delete file with id",
|
||||||
|
ArgsUsage: "ID [ID]..",
|
||||||
|
Action: actions.ActionClientDelete,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "list",
|
||||||
|
Usage: "List files",
|
||||||
|
Action: actions.ActionClientList,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "login",
|
||||||
|
Usage: "Login and retrieve client cert",
|
||||||
|
ArgsUsage: "[PROTO://]HOSTNAME[:PORT]",
|
||||||
|
Action: actions.ActionClientLogin,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "username",
|
||||||
|
Usage: "Username used for login.",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "overwrite",
|
||||||
|
Usage: "Overwrite existing config",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "config-init",
|
||||||
|
Usage: "Initialize default config",
|
||||||
|
Action: actions.ActionInitConfig,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "cert",
|
||||||
|
Usage: "Certificate commands",
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "gen-all",
|
||||||
|
Usage: "Generate CA, Server and Client certificates",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "out-dir",
|
||||||
|
Usage: "Directory where certificates will be stored.",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "hostname",
|
||||||
|
Usage: "Hostname used for server certificate.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: actions.ActionGencerts,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run(os.Args)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1033
pb/ezshare.pb.go
1033
pb/ezshare.pb.go
File diff suppressed because it is too large
Load Diff
@ -207,3 +207,197 @@ var FileService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "protos/ezshare.proto",
|
Metadata: "protos/ezshare.proto",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserServiceClient is the client API for UserService service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type UserServiceClient interface {
|
||||||
|
Register(ctx context.Context, in *RegisterUserRequest, opts ...grpc.CallOption) (*RegisterUserResponse, error)
|
||||||
|
Login(ctx context.Context, in *LoginUserRequest, opts ...grpc.CallOption) (*LoginUserResponse, error)
|
||||||
|
List(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error)
|
||||||
|
Approve(ctx context.Context, in *ApproveUserRequest, opts ...grpc.CallOption) (*Empty, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type userServiceClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient {
|
||||||
|
return &userServiceClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *userServiceClient) Register(ctx context.Context, in *RegisterUserRequest, opts ...grpc.CallOption) (*RegisterUserResponse, error) {
|
||||||
|
out := new(RegisterUserResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/ezshare.UserService/Register", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *userServiceClient) Login(ctx context.Context, in *LoginUserRequest, opts ...grpc.CallOption) (*LoginUserResponse, error) {
|
||||||
|
out := new(LoginUserResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/ezshare.UserService/Login", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *userServiceClient) List(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error) {
|
||||||
|
out := new(ListUsersResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/ezshare.UserService/List", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *userServiceClient) Approve(ctx context.Context, in *ApproveUserRequest, opts ...grpc.CallOption) (*Empty, error) {
|
||||||
|
out := new(Empty)
|
||||||
|
err := c.cc.Invoke(ctx, "/ezshare.UserService/Approve", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserServiceServer is the server API for UserService service.
|
||||||
|
// All implementations must embed UnimplementedUserServiceServer
|
||||||
|
// for forward compatibility
|
||||||
|
type UserServiceServer interface {
|
||||||
|
Register(context.Context, *RegisterUserRequest) (*RegisterUserResponse, error)
|
||||||
|
Login(context.Context, *LoginUserRequest) (*LoginUserResponse, error)
|
||||||
|
List(context.Context, *ListUsersRequest) (*ListUsersResponse, error)
|
||||||
|
Approve(context.Context, *ApproveUserRequest) (*Empty, error)
|
||||||
|
mustEmbedUnimplementedUserServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedUserServiceServer must be embedded to have forward compatible implementations.
|
||||||
|
type UnimplementedUserServiceServer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedUserServiceServer) Register(context.Context, *RegisterUserRequest) (*RegisterUserResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Register not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedUserServiceServer) Login(context.Context, *LoginUserRequest) (*LoginUserResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Login not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedUserServiceServer) List(context.Context, *ListUsersRequest) (*ListUsersResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedUserServiceServer) Approve(context.Context, *ApproveUserRequest) (*Empty, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Approve not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}
|
||||||
|
|
||||||
|
// UnsafeUserServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to UserServiceServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeUserServiceServer interface {
|
||||||
|
mustEmbedUnimplementedUserServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
|
||||||
|
s.RegisterService(&UserService_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _UserService_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(RegisterUserRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(UserServiceServer).Register(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/ezshare.UserService/Register",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(UserServiceServer).Register(ctx, req.(*RegisterUserRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _UserService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(LoginUserRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(UserServiceServer).Login(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/ezshare.UserService/Login",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(UserServiceServer).Login(ctx, req.(*LoginUserRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _UserService_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListUsersRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(UserServiceServer).List(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/ezshare.UserService/List",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(UserServiceServer).List(ctx, req.(*ListUsersRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _UserService_Approve_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ApproveUserRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(UserServiceServer).Approve(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/ezshare.UserService/Approve",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(UserServiceServer).Approve(ctx, req.(*ApproveUserRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserService_ServiceDesc is the grpc.ServiceDesc for UserService service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var UserService_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "ezshare.UserService",
|
||||||
|
HandlerType: (*UserServiceServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "Register",
|
||||||
|
Handler: _UserService_Register_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Login",
|
||||||
|
Handler: _UserService_Login_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "List",
|
||||||
|
Handler: _UserService_List_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Approve",
|
||||||
|
Handler: _UserService_Approve_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "protos/ezshare.proto",
|
||||||
|
}
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
option go_package = "gitea.benny.dog/torjus/ezshare/pb";
|
|
||||||
package ezshare;
|
package ezshare;
|
||||||
|
|
||||||
|
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
option go_package = "gitea.benny.dog/torjus/ezshare/pb";
|
||||||
|
|
||||||
|
/////////////////////
|
||||||
|
// Common messages //
|
||||||
|
/////////////////////
|
||||||
|
message Empty {}
|
||||||
|
|
||||||
|
////////////////////////
|
||||||
|
// FILE RELATED STUFF //
|
||||||
|
////////////////////////
|
||||||
message File {
|
message File {
|
||||||
string file_id = 1;
|
string file_id = 1;
|
||||||
bytes data = 2;
|
bytes data = 2;
|
||||||
@ -62,4 +69,61 @@ service FileService {
|
|||||||
rpc GetFile(GetFileRequest) returns (GetFileResponse) {}
|
rpc GetFile(GetFileRequest) returns (GetFileResponse) {}
|
||||||
rpc DeleteFile(DeleteFileRequest) returns (DeleteFileResponse) {}
|
rpc DeleteFile(DeleteFileRequest) returns (DeleteFileResponse) {}
|
||||||
rpc ListFiles(ListFilesRequest) returns (ListFilesResponse) {}
|
rpc ListFiles(ListFilesRequest) returns (ListFilesResponse) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////
|
||||||
|
// USER RELATED STUFF //
|
||||||
|
////////////////////////
|
||||||
|
message User {
|
||||||
|
string id = 1;
|
||||||
|
string username = 2;
|
||||||
|
bytes hashed_password = 3;
|
||||||
|
enum Role {
|
||||||
|
UNKNOWN = 0;
|
||||||
|
VIEWONLY = 1;
|
||||||
|
USER = 2;
|
||||||
|
ADMIN = 3;
|
||||||
|
UNAPPROVED = 4;
|
||||||
|
}
|
||||||
|
Role user_role = 4;
|
||||||
|
bool active = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register
|
||||||
|
message RegisterUserRequest {
|
||||||
|
string username = 1;
|
||||||
|
string password = 2;
|
||||||
|
}
|
||||||
|
message RegisterUserResponse {
|
||||||
|
string id = 1;
|
||||||
|
string token = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login
|
||||||
|
message LoginUserRequest {
|
||||||
|
string username = 1;
|
||||||
|
string password = 2;
|
||||||
|
}
|
||||||
|
message LoginUserResponse {
|
||||||
|
bytes client_cert = 1;
|
||||||
|
bytes client_key = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List
|
||||||
|
message ListUsersRequest {
|
||||||
|
}
|
||||||
|
message ListUsersResponse {
|
||||||
|
repeated User users = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approve
|
||||||
|
message ApproveUserRequest {
|
||||||
|
string user_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service UserService {
|
||||||
|
rpc Register(RegisterUserRequest) returns (RegisterUserResponse) {}
|
||||||
|
rpc Login(LoginUserRequest) returns (LoginUserResponse) {}
|
||||||
|
rpc List(ListUsersRequest) returns (ListUsersResponse) {}
|
||||||
|
rpc Approve(ApproveUserRequest) returns (Empty) {}
|
||||||
|
}
|
||||||
|
@ -10,28 +10,49 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type HTTPServer struct {
|
type HTTPServer struct {
|
||||||
store store.FileStore
|
store store.FileStore
|
||||||
|
serverGRPCCert []byte
|
||||||
|
grpcEndpoint string
|
||||||
|
|
||||||
http.Server
|
http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPSever(store store.FileStore) *HTTPServer {
|
type MetadataResponse struct {
|
||||||
|
GRPCEndpoint string `json:"grpc_endpoint"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPSever(store store.FileStore, certBytes []byte, grpcEndpoint string) *HTTPServer {
|
||||||
srv := &HTTPServer{
|
srv := &HTTPServer{
|
||||||
store: store,
|
store: store,
|
||||||
|
serverGRPCCert: certBytes,
|
||||||
|
grpcEndpoint: grpcEndpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
r.Get("/server.pem", srv.ServerCertHandler)
|
||||||
|
r.Get("/metadata", srv.MetadataHandler)
|
||||||
r.Get("/files/{id}", srv.FileHandler)
|
r.Get("/files/{id}", srv.FileHandler)
|
||||||
|
|
||||||
srv.Handler = r
|
srv.Handler = r
|
||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *HTTPServer) ServerCertHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write(s.serverGRPCCert)
|
||||||
|
}
|
||||||
|
func (s *HTTPServer) MetadataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
md := &MetadataResponse{
|
||||||
|
GRPCEndpoint: s.grpcEndpoint,
|
||||||
|
}
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
encoder.Encode(md)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) FileHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPServer) FileHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
id := chi.URLParam(r, "id")
|
id := chi.URLParam(r, "id")
|
||||||
f, err := s.store.GetFile(id)
|
f, err := s.store.GetFile(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == store.ErrNoSuchFile {
|
if err == store.ErrNoSuchItem {
|
||||||
WriteErrorResponse(w, http.StatusNotFound, "file not found")
|
WriteErrorResponse(w, http.StatusNotFound, "file not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
55
server/interceptors/auth.go
Normal file
55
server/interceptors/auth.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package interceptors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.benny.dog/torjus/ezshare/pb"
|
||||||
|
"gitea.benny.dog/torjus/ezshare/store"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/peer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContextKey string
|
||||||
|
|
||||||
|
var ContextKeyRole ContextKey = "role"
|
||||||
|
|
||||||
|
func NewAuthInterceptor(s store.UserStore) grpc.UnaryServerInterceptor {
|
||||||
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||||
|
p, ok := peer.FromContext(ctx)
|
||||||
|
if ok {
|
||||||
|
tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo)
|
||||||
|
if ok {
|
||||||
|
fmt.Printf("%+v\n", tlsInfo.State.PeerCertificates[0].Subject.CommonName)
|
||||||
|
if len(tlsInfo.State.PeerCertificates) == 1 {
|
||||||
|
cert := tlsInfo.State.PeerCertificates[0]
|
||||||
|
|
||||||
|
id := cert.Subject.CommonName
|
||||||
|
|
||||||
|
user, err := s.GetUser(id)
|
||||||
|
if err == nil {
|
||||||
|
newCtx := context.WithValue(ctx, ContextKeyRole, user.UserRole)
|
||||||
|
return handler(newCtx, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newCtx := context.WithValue(ctx, ContextKeyRole, pb.User_UNKNOWN)
|
||||||
|
|
||||||
|
return handler(newCtx, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RoleFromContext(ctx context.Context) pb.User_Role {
|
||||||
|
value := ctx.Value(ContextKeyRole)
|
||||||
|
if value == nil {
|
||||||
|
return pb.User_UNKNOWN
|
||||||
|
}
|
||||||
|
role, ok := value.(pb.User_Role)
|
||||||
|
if ok {
|
||||||
|
return role
|
||||||
|
}
|
||||||
|
return pb.User_UNKNOWN
|
||||||
|
}
|
86
server/userservice.go
Normal file
86
server/userservice.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.benny.dog/torjus/ezshare/certs"
|
||||||
|
"gitea.benny.dog/torjus/ezshare/pb"
|
||||||
|
"gitea.benny.dog/torjus/ezshare/store"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GRPCUserServiceServer struct {
|
||||||
|
store store.UserStore
|
||||||
|
certService *certs.CertService
|
||||||
|
pb.UnimplementedUserServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGRPCUserServiceServer(store store.UserStore, certSvc *certs.CertService) *GRPCUserServiceServer {
|
||||||
|
return &GRPCUserServiceServer{store: store, certService: certSvc}
|
||||||
|
}
|
||||||
|
func (s *GRPCUserServiceServer) Register(ctx context.Context, req *pb.RegisterUserRequest) (*pb.RegisterUserResponse, error) {
|
||||||
|
// Check if user already exists
|
||||||
|
if _, err := s.store.GetUserByUsername(req.Username); err != store.ErrNoSuchItem {
|
||||||
|
return nil, status.Error(codes.AlreadyExists, "user already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
pw, err := hashPassword(req.Password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to hash password: %w", err)
|
||||||
|
}
|
||||||
|
user := &pb.User{
|
||||||
|
Id: uuid.Must(uuid.NewRandom()).String(),
|
||||||
|
Username: req.Username,
|
||||||
|
HashedPassword: pw,
|
||||||
|
UserRole: pb.User_USER,
|
||||||
|
Active: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.store.StoreUser(user); err != nil {
|
||||||
|
return nil, status.Error(codes.Internal, fmt.Sprintf("unable to store user: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.RegisterUserResponse{Id: user.Id, Token: ""}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GRPCUserServiceServer) Login(_ context.Context, req *pb.LoginUserRequest) (*pb.LoginUserResponse, error) {
|
||||||
|
user, err := s.store.GetUserByUsername(req.Username)
|
||||||
|
if err != nil {
|
||||||
|
if err == store.ErrNoSuchItem {
|
||||||
|
return nil, status.Error(codes.NotFound, "no such user")
|
||||||
|
}
|
||||||
|
return nil, status.Error(codes.Internal, "error getting user from store")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bcrypt.CompareHashAndPassword(user.HashedPassword, []byte(req.Password)); err != nil {
|
||||||
|
return nil, status.Error(codes.Unauthenticated, "wrong username and or password")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, key, err := s.certService.NewClient(user.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.Internal, "unable to generate client certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &pb.LoginUserResponse{
|
||||||
|
ClientCert: cert,
|
||||||
|
ClientKey: key,
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GRPCUserServiceServer) List(_ context.Context, _ *pb.ListUsersRequest) (*pb.ListUsersResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GRPCUserServiceServer) Approve(_ context.Context, _ *pb.ApproveUserRequest) (*pb.Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashPassword(password string) ([]byte, error) {
|
||||||
|
return bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
}
|
@ -20,6 +20,7 @@ type BoltStore struct {
|
|||||||
var bktKey = []byte("files")
|
var bktKey = []byte("files")
|
||||||
var bktKeyCerts = []byte("certs")
|
var bktKeyCerts = []byte("certs")
|
||||||
var bktKeyKeys = []byte("keys")
|
var bktKeyKeys = []byte("keys")
|
||||||
|
var bktKeyUsers = []byte("users")
|
||||||
|
|
||||||
func NewBoltStore(path string) (*BoltStore, error) {
|
func NewBoltStore(path string) (*BoltStore, error) {
|
||||||
s := &BoltStore{}
|
s := &BoltStore{}
|
||||||
@ -38,7 +39,9 @@ func NewBoltStore(path string) (*BoltStore, error) {
|
|||||||
if _, err := t.CreateBucketIfNotExists(bktKeyKeys); err != nil {
|
if _, err := t.CreateBucketIfNotExists(bktKeyKeys); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if _, err := t.CreateBucketIfNotExists(bktKeyUsers); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -62,7 +65,7 @@ func (s *BoltStore) GetFile(id string) (*pb.File, error) {
|
|||||||
return nil, fmt.Errorf("error getting file: %w", err)
|
return nil, fmt.Errorf("error getting file: %w", err)
|
||||||
}
|
}
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return nil, ErrNoSuchFile
|
return nil, ErrNoSuchItem
|
||||||
}
|
}
|
||||||
|
|
||||||
err := proto.Unmarshal(data, &file)
|
err := proto.Unmarshal(data, &file)
|
||||||
@ -95,7 +98,7 @@ func (s *BoltStore) DeleteFile(id string) error {
|
|||||||
bkt := t.Bucket(bktKey)
|
bkt := t.Bucket(bktKey)
|
||||||
data := bkt.Get([]byte(id))
|
data := bkt.Get([]byte(id))
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return ErrNoSuchFile
|
return ErrNoSuchItem
|
||||||
}
|
}
|
||||||
return bkt.Delete([]byte(id))
|
return bkt.Delete([]byte(id))
|
||||||
})
|
})
|
||||||
@ -137,7 +140,7 @@ func (s *BoltStore) GetCertificate(id string) (*x509.Certificate, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if raw == nil {
|
if raw == nil {
|
||||||
return nil, ErrNoSuchFile
|
return nil, ErrNoSuchItem
|
||||||
}
|
}
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(raw)
|
cert, err := x509.ParseCertificate(raw)
|
||||||
@ -188,14 +191,83 @@ func (s *BoltStore) ListCertificates() ([]string, error) {
|
|||||||
var ids []string
|
var ids []string
|
||||||
err := s.db.View(func(tx *bolt.Tx) error {
|
err := s.db.View(func(tx *bolt.Tx) error {
|
||||||
bkt := tx.Bucket(bktKeyCerts)
|
bkt := tx.Bucket(bktKeyCerts)
|
||||||
bkt.ForEach(func(k, v []byte) error {
|
return bkt.ForEach(func(k, v []byte) error {
|
||||||
ids = append(ids, string(k))
|
ids = append(ids, string(k))
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ids, nil
|
return ids, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ UserStore = &BoltStore{}
|
||||||
|
|
||||||
|
func (s *BoltStore) StoreUser(user *pb.User) error {
|
||||||
|
return s.db.Update(func(tx *bolt.Tx) error {
|
||||||
|
bkt := tx.Bucket(bktKeyUsers)
|
||||||
|
data, err := proto.Marshal(user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bkt.Put([]byte(user.Id), data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BoltStore) GetUser(id string) (*pb.User, error) {
|
||||||
|
var data []byte
|
||||||
|
err := s.db.View(func(tx *bolt.Tx) error {
|
||||||
|
bkt := tx.Bucket(bktKeyUsers)
|
||||||
|
data = bkt.Get([]byte(id))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var user pb.User
|
||||||
|
err = proto.Unmarshal(data, &user)
|
||||||
|
return &user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BoltStore) ListUsers() ([]string, error) {
|
||||||
|
var ids []string
|
||||||
|
err := s.db.View(func(tx *bolt.Tx) error {
|
||||||
|
bkt := tx.Bucket(bktKeyUsers)
|
||||||
|
return bkt.ForEach(func(k, _ []byte) error {
|
||||||
|
ids = append(ids, string(k))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return ids, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BoltStore) GetUserByUsername(username string) (*pb.User, error) {
|
||||||
|
var user pb.User
|
||||||
|
err := s.db.View(func(tx *bolt.Tx) error {
|
||||||
|
bkt := tx.Bucket(bktKeyUsers)
|
||||||
|
c := bkt.Cursor()
|
||||||
|
|
||||||
|
for k, v := c.First(); k != nil; c.Next() {
|
||||||
|
err := proto.Unmarshal(v, &user)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Log that db has invalid user
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if user.Username == username {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Username == username {
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
return nil, ErrNoSuchItem
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//nolint:staticcheck
|
||||||
package store_test
|
package store_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -10,19 +11,29 @@ import (
|
|||||||
func TestBoltStore(t *testing.T) {
|
func TestBoltStore(t *testing.T) {
|
||||||
path := filepath.Join(t.TempDir(), "boltstore.db")
|
path := filepath.Join(t.TempDir(), "boltstore.db")
|
||||||
s, err := store.NewBoltStore(path)
|
s, err := store.NewBoltStore(path)
|
||||||
|
defer s.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error opening store: %s", err)
|
t.Fatalf("Error opening store: %s", err)
|
||||||
}
|
}
|
||||||
doFileStoreTest(s, t)
|
doFileStoreTest(s, t)
|
||||||
_ = s.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoltCertificateStore(t *testing.T) {
|
func TestBoltCertificateStore(t *testing.T) {
|
||||||
path := filepath.Join(t.TempDir(), "boltstore.db")
|
path := filepath.Join(t.TempDir(), "boltstore.db")
|
||||||
s, err := store.NewBoltStore(path)
|
s, err := store.NewBoltStore(path)
|
||||||
|
defer s.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error opening store: %s", err)
|
t.Fatalf("Error opening store: %s", err)
|
||||||
}
|
}
|
||||||
doCertificateStoreTest(s, t)
|
doCertificateStoreTest(s, t)
|
||||||
_ = s.Close()
|
}
|
||||||
|
|
||||||
|
func TestBoltUserStore(t *testing.T) {
|
||||||
|
path := filepath.Join(t.TempDir(), "boltstore.db")
|
||||||
|
s, err := store.NewBoltStore(path)
|
||||||
|
defer s.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error opening store: %s", err)
|
||||||
|
}
|
||||||
|
doUserStoreTests(s, t)
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ func (s *FileSystemStore) GetFile(id string) (*pb.File, error) {
|
|||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(os.ErrNotExist) {
|
if os.IsNotExist(os.ErrNotExist) {
|
||||||
return nil, ErrNoSuchFile
|
return nil, ErrNoSuchItem
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unable to open file: %w", err)
|
return nil, fmt.Errorf("unable to open file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,6 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ FileStore = &MemoryStore{}
|
|
||||||
var _ CertificateStore = &MemoryStore{}
|
|
||||||
|
|
||||||
type MemoryStore struct {
|
type MemoryStore struct {
|
||||||
filesLock sync.RWMutex
|
filesLock sync.RWMutex
|
||||||
files map[string]*pb.File
|
files map[string]*pb.File
|
||||||
@ -19,6 +16,8 @@ type MemoryStore struct {
|
|||||||
certs map[string][]byte
|
certs map[string][]byte
|
||||||
keyLock sync.RWMutex
|
keyLock sync.RWMutex
|
||||||
keys map[string][]byte
|
keys map[string][]byte
|
||||||
|
usersLock sync.RWMutex
|
||||||
|
users map[string]*pb.User
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMemoryStore() *MemoryStore {
|
func NewMemoryStore() *MemoryStore {
|
||||||
@ -26,9 +25,16 @@ func NewMemoryStore() *MemoryStore {
|
|||||||
files: make(map[string]*pb.File),
|
files: make(map[string]*pb.File),
|
||||||
certs: make(map[string][]byte),
|
certs: make(map[string][]byte),
|
||||||
keys: make(map[string][]byte),
|
keys: make(map[string][]byte),
|
||||||
|
users: make(map[string]*pb.User),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// FileStore //
|
||||||
|
///////////////
|
||||||
|
|
||||||
|
var _ FileStore = &MemoryStore{}
|
||||||
|
|
||||||
func (s *MemoryStore) GetFile(id string) (*pb.File, error) {
|
func (s *MemoryStore) GetFile(id string) (*pb.File, error) {
|
||||||
s.filesLock.RLock()
|
s.filesLock.RLock()
|
||||||
defer s.filesLock.RUnlock()
|
defer s.filesLock.RUnlock()
|
||||||
@ -37,7 +43,7 @@ func (s *MemoryStore) GetFile(id string) (*pb.File, error) {
|
|||||||
return file, nil
|
return file, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, ErrNoSuchFile
|
return nil, ErrNoSuchItem
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryStore) StoreFile(file *pb.File) (string, error) {
|
func (s *MemoryStore) StoreFile(file *pb.File) (string, error) {
|
||||||
@ -56,7 +62,7 @@ func (s *MemoryStore) DeleteFile(id string) error {
|
|||||||
s.filesLock.Lock()
|
s.filesLock.Lock()
|
||||||
defer s.filesLock.Unlock()
|
defer s.filesLock.Unlock()
|
||||||
if _, ok := s.files[id]; !ok {
|
if _, ok := s.files[id]; !ok {
|
||||||
return ErrNoSuchFile
|
return ErrNoSuchItem
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(s.files, id)
|
delete(s.files, id)
|
||||||
@ -76,6 +82,12 @@ func (s *MemoryStore) ListFiles() ([]*pb.ListFilesResponse_ListFileInfo, error)
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////
|
||||||
|
// CertificateStore //
|
||||||
|
//////////////////////
|
||||||
|
|
||||||
|
var _ CertificateStore = &MemoryStore{}
|
||||||
|
|
||||||
func (s *MemoryStore) GetCertificate(id string) (*x509.Certificate, error) {
|
func (s *MemoryStore) GetCertificate(id string) (*x509.Certificate, error) {
|
||||||
s.certLock.Lock()
|
s.certLock.Lock()
|
||||||
defer s.certLock.Unlock()
|
defer s.certLock.Unlock()
|
||||||
@ -83,7 +95,7 @@ func (s *MemoryStore) GetCertificate(id string) (*x509.Certificate, error) {
|
|||||||
data, ok := s.certs[id]
|
data, ok := s.certs[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
// TODO: Make separate error, or rename error
|
// TODO: Make separate error, or rename error
|
||||||
return nil, ErrNoSuchFile
|
return nil, ErrNoSuchItem
|
||||||
}
|
}
|
||||||
|
|
||||||
return x509.ParseCertificate(data)
|
return x509.ParseCertificate(data)
|
||||||
@ -106,7 +118,7 @@ func (s *MemoryStore) GetKey(id string) (*ecdsa.PrivateKey, error) {
|
|||||||
defer s.keyLock.RUnlock()
|
defer s.keyLock.RUnlock()
|
||||||
data, ok := s.keys[id]
|
data, ok := s.keys[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrNoSuchFile
|
return nil, ErrNoSuchItem
|
||||||
}
|
}
|
||||||
|
|
||||||
return x509.ParseECPrivateKey(data)
|
return x509.ParseECPrivateKey(data)
|
||||||
@ -134,3 +146,50 @@ func (s *MemoryStore) ListCertificates() ([]string, error) {
|
|||||||
}
|
}
|
||||||
return certIDs, nil
|
return certIDs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// UserStore //
|
||||||
|
///////////////
|
||||||
|
|
||||||
|
var _ UserStore = &MemoryStore{}
|
||||||
|
|
||||||
|
func (s *MemoryStore) StoreUser(user *pb.User) error {
|
||||||
|
s.usersLock.Lock()
|
||||||
|
defer s.usersLock.Unlock()
|
||||||
|
|
||||||
|
s.users[user.Id] = user
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MemoryStore) GetUser(id string) (*pb.User, error) {
|
||||||
|
s.usersLock.RLock()
|
||||||
|
defer s.usersLock.RUnlock()
|
||||||
|
user, ok := s.users[id]
|
||||||
|
if !ok {
|
||||||
|
// TODO: Update error
|
||||||
|
return nil, ErrNoSuchItem
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MemoryStore) ListUsers() ([]string, error) {
|
||||||
|
s.usersLock.RLock()
|
||||||
|
defer s.usersLock.RUnlock()
|
||||||
|
|
||||||
|
var ids []string
|
||||||
|
for id := range s.users {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MemoryStore) GetUserByUsername(username string) (*pb.User, error) {
|
||||||
|
s.usersLock.RLock()
|
||||||
|
defer s.usersLock.RUnlock()
|
||||||
|
for _, user := range s.users {
|
||||||
|
if user.Username == username {
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, ErrNoSuchItem
|
||||||
|
}
|
||||||
|
@ -14,3 +14,8 @@ func TestMemoryCertificateStore(t *testing.T) {
|
|||||||
s := store.NewMemoryStore()
|
s := store.NewMemoryStore()
|
||||||
doCertificateStoreTest(s, t)
|
doCertificateStoreTest(s, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMemoryUserStore(t *testing.T) {
|
||||||
|
s := store.NewMemoryStore()
|
||||||
|
doUserStoreTests(s, t)
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"gitea.benny.dog/torjus/ezshare/pb"
|
"gitea.benny.dog/torjus/ezshare/pb"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrNoSuchFile = fmt.Errorf("no such file")
|
var ErrNoSuchItem = fmt.Errorf("no such item")
|
||||||
|
|
||||||
type FileStore interface {
|
type FileStore interface {
|
||||||
GetFile(id string) (*pb.File, error)
|
GetFile(id string) (*pb.File, error)
|
||||||
@ -24,3 +24,10 @@ type CertificateStore interface {
|
|||||||
StoreKey(id string, key *ecdsa.PrivateKey) error
|
StoreKey(id string, key *ecdsa.PrivateKey) error
|
||||||
ListCertificates() ([]string, error)
|
ListCertificates() ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserStore interface {
|
||||||
|
StoreUser(user *pb.User) error
|
||||||
|
GetUser(id string) (*pb.User, error)
|
||||||
|
GetUserByUsername(username string) (*pb.User, error)
|
||||||
|
ListUsers() ([]string, error)
|
||||||
|
}
|
||||||
|
@ -10,6 +10,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"google.golang.org/protobuf/testing/protocmp"
|
||||||
|
|
||||||
"gitea.benny.dog/torjus/ezshare/pb"
|
"gitea.benny.dog/torjus/ezshare/pb"
|
||||||
"gitea.benny.dog/torjus/ezshare/store"
|
"gitea.benny.dog/torjus/ezshare/store"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
@ -64,7 +68,7 @@ func doFileStoreTest(s store.FileStore, t *testing.T) {
|
|||||||
t.Fatalf("Unable to delete file: %s", err)
|
t.Fatalf("Unable to delete file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := s.GetFile(id); err != store.ErrNoSuchFile {
|
if _, err := s.GetFile(id); err != store.ErrNoSuchItem {
|
||||||
t.Fatalf("Getting deleted file returned wrong error: %s", err)
|
t.Fatalf("Getting deleted file returned wrong error: %s", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -140,3 +144,48 @@ func doCertificateStoreTest(s store.CertificateStore, t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doUserStoreTests(s store.UserStore, t *testing.T) {
|
||||||
|
t.Run("Basics", func(t *testing.T) {
|
||||||
|
// Store user
|
||||||
|
user := &pb.User{
|
||||||
|
Id: uuid.Must(uuid.NewRandom()).String(),
|
||||||
|
Username: "testuser",
|
||||||
|
UserRole: pb.User_USER,
|
||||||
|
Active: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.StoreUser(user); err != nil {
|
||||||
|
t.Fatalf("Error storing user: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieved, err := s.GetUser(user.Id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retriving user returned error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(user, retrieved, protocmp.Transform()); diff != "" {
|
||||||
|
t.Errorf("User retrieved by name difference:\n%v", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
named, err := s.GetUserByUsername(user.Username)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Retrieving user by username returned error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(user, named, protocmp.Transform()); diff != "" {
|
||||||
|
t.Errorf("User retrieved by name difference:\n%v", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := s.ListUsers()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error listing users")
|
||||||
|
}
|
||||||
|
if len(list) != 1 {
|
||||||
|
t.Fatalf("User list wrong length")
|
||||||
|
}
|
||||||
|
if list[0] != user.Id {
|
||||||
|
t.Fatalf("User list has wrong id")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
package ezshare
|
|
||||||
|
|
||||||
const Version = "v0.1.1"
|
|
Loading…
Reference in New Issue
Block a user