1package command
2
3import (
4	"errors"
5	"os"
6	"os/user"
7	"strings"
8
9	"github.com/jessevdk/go-flags"
10)
11
12type Options struct {
13	Version                      bool   `short:"v" long:"version" description:"Print version"`
14	Debug                        bool   `short:"d" long:"debug" description:"Enable debugging mode"`
15	URL                          string `long:"url" description:"Database connection string"`
16	Host                         string `long:"host" description:"Server hostname or IP" default:"localhost"`
17	Port                         int    `long:"port" description:"Server port" default:"5432"`
18	User                         string `long:"user" description:"Database user"`
19	Pass                         string `long:"pass" description:"Password for user"`
20	DbName                       string `long:"db" description:"Database name"`
21	Ssl                          string `long:"ssl" description:"SSL mode"`
22	SslRootCert                  string `long:"ssl-rootcert" description:"SSL certificate authority file"`
23	SslCert                      string `long:"ssl-cert" description:"SSL client certificate file"`
24	SslKey                       string `long:"ssl-key" description:"SSL client certificate key file"`
25	HTTPHost                     string `long:"bind" description:"HTTP server host" default:"localhost"`
26	HTTPPort                     uint   `long:"listen" description:"HTTP server listen port" default:"8081"`
27	AuthUser                     string `long:"auth-user" description:"HTTP basic auth user"`
28	AuthPass                     string `long:"auth-pass" description:"HTTP basic auth password"`
29	SkipOpen                     bool   `short:"s" long:"skip-open" description:"Skip browser open on start"`
30	Sessions                     bool   `long:"sessions" description:"Enable multiple database sessions"`
31	Prefix                       string `long:"prefix" description:"Add a url prefix"`
32	ReadOnly                     bool   `long:"readonly" description:"Run database connection in readonly mode"`
33	LockSession                  bool   `long:"lock-session" description:"Lock session to a single database connection"`
34	Bookmark                     string `short:"b" long:"bookmark" description:"Bookmark to use for connection. Bookmark files are stored under $HOME/.pgweb/bookmarks/*.toml" default:""`
35	BookmarksDir                 string `long:"bookmarks-dir" description:"Overrides default directory for bookmark files to search" default:""`
36	DisablePrettyJSON            bool   `long:"no-pretty-json" description:"Disable JSON formatting feature for result export"`
37	DisableSSH                   bool   `long:"no-ssh" description:"Disable database connections via SSH"`
38	ConnectBackend               string `long:"connect-backend" description:"Enable database authentication through a third party backend"`
39	ConnectToken                 string `long:"connect-token" description:"Authentication token for the third-party connect backend"`
40	ConnectHeaders               string `long:"connect-headers" description:"List of headers to pass to the connect backend"`
41	DisableConnectionIdleTimeout bool   `long:"no-idle-timeout" description:"Disable connection idle timeout"`
42	ConnectionIdleTimeout        int    `long:"idle-timeout" description:"Set connection idle timeout in minutes" default:"180"`
43	Cors                         bool   `long:"cors" description:"Enable Cross-Origin Resource Sharing (CORS)"`
44	CorsOrigin                   string `long:"cors-origin" description:"Allowed CORS origins" default:"*"`
45}
46
47var Opts Options
48
49// ParseOptions returns a new options struct from the input arguments
50func ParseOptions(args []string) (Options, error) {
51	var opts = Options{}
52
53	_, err := flags.ParseArgs(&opts, args)
54	if err != nil {
55		return opts, err
56	}
57
58	if opts.URL == "" {
59		opts.URL = os.Getenv("DATABASE_URL")
60	}
61
62	if opts.Prefix == "" {
63		opts.Prefix = os.Getenv("URL_PREFIX")
64	}
65
66	// Handle edge case where pgweb is started with a default host `localhost` and no user.
67	// When user is not set the `lib/pq` connection will fail and cause pgweb's termination.
68	if (opts.Host == "localhost" || opts.Host == "127.0.0.1") && opts.User == "" {
69		if username := GetCurrentUser(); username != "" {
70			opts.User = username
71		} else {
72			opts.Host = ""
73		}
74	}
75
76	if os.Getenv("SESSIONS") != "" {
77		opts.Sessions = true
78	}
79
80	if os.Getenv("LOCK_SESSION") != "" {
81		opts.LockSession = true
82		opts.Sessions = false
83	}
84
85	if opts.Sessions || opts.ConnectBackend != "" {
86		opts.Bookmark = ""
87		opts.URL = ""
88		opts.Host = ""
89		opts.User = ""
90		opts.Pass = ""
91		opts.DbName = ""
92		opts.Ssl = ""
93	}
94
95	if opts.Prefix != "" && !strings.Contains(opts.Prefix, "/") {
96		opts.Prefix = opts.Prefix + "/"
97	}
98
99	if opts.AuthUser == "" && os.Getenv("AUTH_USER") != "" {
100		opts.AuthUser = os.Getenv("AUTH_USER")
101	}
102
103	if opts.AuthPass == "" && os.Getenv("AUTH_PASS") != "" {
104		opts.AuthPass = os.Getenv("AUTH_PASS")
105	}
106
107	if opts.ConnectBackend != "" {
108		if !opts.Sessions {
109			return opts, errors.New("--sessions flag must be set")
110		}
111		if opts.ConnectToken == "" {
112			return opts, errors.New("--connect-token flag must be set")
113		}
114	} else {
115		if opts.ConnectToken != "" || opts.ConnectHeaders != "" {
116			return opts, errors.New("--connect-backend flag must be set")
117		}
118	}
119
120	return opts, nil
121}
122
123// SetDefaultOptions parses and assigns the options
124func SetDefaultOptions() error {
125	opts, err := ParseOptions([]string{})
126	if err != nil {
127		return err
128	}
129	Opts = opts
130	return nil
131}
132
133// GetCurrentUser returns a current user name
134func GetCurrentUser() string {
135	u, _ := user.Current()
136	if u != nil {
137		return u.Username
138	}
139	return os.Getenv("USER")
140}
141