Skip to content

Commit

Permalink
Issue #40:http protocol is hardcoded in the URL
Browse files Browse the repository at this point in the history
   - moved hardcoded protocol to an argument
  • Loading branch information
oneils committed Aug 20, 2024
1 parent 621b9aa commit 2269bc9
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 61 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ARG TZ=America/Chicago

FROM umputun/baseimage:buildgo-latest as build-backend
FROM umputun/baseimage:buildgo-latest AS build-backend

ARG CI
ARG GIT_BRANCH
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ _Feel free to suggest any other ways to make the process safer._
- MAX_EXPIRE - maximum lifetime period, default 24h
- PIN_SIZE - size (in characters) of the pin, default 5
- PIN_ATTEMPTS - maximum number of failed attempts to enter pin, default 3
- PROTOCOL - http or https
1. Setup SSL:
- The system can make valid certificates for you automatically with integrated [nginx-le](https://github.com/umputun/nginx-le). Just set:
- LETSENCRYPT=true
Expand Down
25 changes: 12 additions & 13 deletions app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var opts struct {
WebRoot string `long:"web" env:"WEB" default:"./ui/static/" description:"web ui location"`
Dbg bool `long:"dbg" description:"debug mode"`
Domain string `short:"d" long:"domain" env:"DOMAIN" description:"site domain" required:"true"`
Protocol string `short:"p" long:"protocol" env:"PROTOCOL" description:"site protocol" choice:"http" choice:"https" default:"https" required:"true"` // nolint
}

var revision string
Expand All @@ -36,26 +37,24 @@ func main() {

setupLog(opts.Dbg)

templateCache, err := server.NewTemplateCache()
if err != nil {
log.Printf("[ERROR] can't create template cache, %+v", err)
os.Exit(1)
}

dataStore := getEngine(opts.Engine, opts.BoltDB)
crypter := messager.Crypt{Key: messager.MakeSignKey(opts.SignKey, opts.PinSize)}
params := messager.Params{MaxDuration: opts.MaxExpire, MaxPinAttempts: opts.MaxPinAttempts}
srv := server.Server{
Messager: messager.New(dataStore, crypter, params),

srv, err := server.New(messager.New(dataStore, crypter, params), revision, server.Config{
Domain: opts.Domain,
Protocol: opts.Protocol,
PinSize: opts.PinSize,
MaxExpire: opts.MaxExpire,
MaxPinAttempts: opts.MaxPinAttempts,
MaxExpire: opts.MaxExpire,
WebRoot: opts.WebRoot,
Version: revision,
Domain: opts.Domain,
TemplateCache: templateCache,
})

if err != nil {
log.Fatalf("[ERROR] can't create server, %v", err)
}
if err := srv.Run(context.Background()); err != nil {

if err = srv.Run(context.Background()); err != nil {
log.Printf("[ERROR] failed, %+v", err)
}
}
Expand Down
50 changes: 35 additions & 15 deletions app/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,36 @@ import (
"github.com/umputun/secrets/app/store"
)

// Server is a rest with store
type Server struct {
Messager Messager
// Config is a configuration for the server
type Config struct {
Domain string
Protocol string
PinSize int
MaxPinAttempts int
MaxExpire time.Duration
WebRoot string
Version string
Domain string
TemplateCache map[string]*template.Template
}

// Server is a rest with store
type Server struct {
messager Messager
cfg Config
version string
templateCache map[string]*template.Template
}

// New creates a new server
func New(m Messager, version string, cfg Config) (Server, error) {
cache, err := newTemplateCache()
if err != nil {
return Server{}, errors.Wrap(err, "can't create template cache")
}
return Server{
messager: m,
cfg: cfg,
version: version,
templateCache: cache,
}, nil
}

// Messager interface making and loading messages
Expand Down Expand Up @@ -75,7 +95,7 @@ func (s Server) routes() chi.Router {

router.Use(middleware.RequestID, middleware.RealIP, um.Recoverer(log.Default()))
router.Use(middleware.Throttle(1000), middleware.Timeout(60*time.Second))
router.Use(um.AppInfo("secrets", "Umputun", s.Version), um.Ping, um.SizeLimit(64*1024))
router.Use(um.AppInfo("secrets", "Umputun", s.version), um.Ping, um.SizeLimit(64*1024))
router.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(10, nil)))

router.Route("/api/v1", func(r chi.Router) {
Expand Down Expand Up @@ -109,7 +129,7 @@ func (s Server) routes() chi.Router {
r.Get("/", s.indexCtrl)
})

fs, err := um.NewFileServer("/static", s.WebRoot)
fs, err := um.NewFileServer("/static", s.cfg.WebRoot)
if err != nil {
log.Fatalf("[ERROR] can't create file server %v", err)
}
Expand All @@ -133,14 +153,14 @@ func (s Server) saveMessageCtrl(w http.ResponseWriter, r *http.Request) {
return
}

if len(request.Pin) != s.PinSize {
if len(request.Pin) != s.cfg.PinSize {
log.Printf("[WARN] incorrect pin size %d", len(request.Pin))
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, JSON{"error": "Incorrect pin size"})
return
}

msg, err := s.Messager.MakeMessage(time.Second*time.Duration(request.Exp), request.Message, request.Pin)
msg, err := s.messager.MakeMessage(time.Second*time.Duration(request.Exp), request.Message, request.Pin)
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, JSON{"error": err.Error()})
Expand All @@ -154,15 +174,15 @@ func (s Server) saveMessageCtrl(w http.ResponseWriter, r *http.Request) {
func (s Server) getMessageCtrl(w http.ResponseWriter, r *http.Request) {

key, pin := chi.URLParam(r, "key"), chi.URLParam(r, "pin")
if key == "" || pin == "" || len(pin) != s.PinSize {
if key == "" || pin == "" || len(pin) != s.cfg.PinSize {
log.Print("[WARN] no valid key or pin in get request")
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, JSON{"error": "no key or pin passed"})
return
}

serveRequest := func() (status int, res JSON) {
msg, err := s.Messager.LoadMessage(key, pin)
msg, err := s.messager.LoadMessage(key, pin)
if err != nil {
log.Printf("[WARN] failed to load key %v", key)
if err == messager.ErrBadPinAttempt {
Expand All @@ -188,9 +208,9 @@ func (s Server) getParamsCtrl(w http.ResponseWriter, r *http.Request) {
MaxPinAttempts int `json:"max_pin_attempts"`
MaxExpSecs int `json:"max_exp_sec"`
}{
PinSize: s.PinSize,
MaxPinAttempts: s.MaxPinAttempts,
MaxExpSecs: int(s.MaxExpire.Seconds()),
PinSize: s.cfg.PinSize,
MaxPinAttempts: s.cfg.MaxPinAttempts,
MaxExpSecs: int(s.cfg.MaxExpire.Seconds()),
}
render.JSON(w, r, params)
}
35 changes: 21 additions & 14 deletions app/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,20 @@ func TestServer_saveAndLoadBolt(t *testing.T) {
require.NoError(t, os.Remove("/tmp/secrets-test.bdb"))
}()
signKey := messager.MakeSignKey("stew-pub-barcan-scatty-daimio-wicker-yakona", 5)
srv := Server{
Messager: messager.New(eng, messager.Crypt{Key: signKey}, messager.Params{
srv, err := New(
messager.New(eng, messager.Crypt{Key: signKey}, messager.Params{
MaxDuration: 10 * time.Hour,
MaxPinAttempts: 3,
}),
PinSize: 5,
MaxPinAttempts: 3,
MaxExpire: 10 * time.Hour,
Version: "1",
}
"1",
Config{
PinSize: 5,
MaxPinAttempts: 3,
MaxExpire: 10 * time.Hour,
})

assert.NoError(t, err)

ts := httptest.NewServer(srv.routes())
defer ts.Close()

Expand Down Expand Up @@ -246,16 +250,19 @@ func TestServer_getParams(t *testing.T) {

func prepTestServer() (ts *httptest.Server, teardown func()) {
eng := store.NewInMemory(time.Second)
srv := Server{
Messager: messager.New(eng, messager.Crypt{Key: "123456789012345678901234567"}, messager.Params{

srv, _ := New(
messager.New(eng, messager.Crypt{Key: "123456789012345678901234567"}, messager.Params{
MaxDuration: 10 * time.Hour,
MaxPinAttempts: 3,
}),
PinSize: 5,
MaxPinAttempts: 3,
MaxExpire: 10 * time.Hour,
Version: "1",
}
"1",
Config{
PinSize: 5,
MaxPinAttempts: 3,
MaxExpire: 10 * time.Hour,
})

ts = httptest.NewServer(srv.routes())
return ts, ts.Close
}
32 changes: 16 additions & 16 deletions app/server/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type templateData struct {

// render renders a template
func (s Server) render(w http.ResponseWriter, status int, page, tmplName string, data any) {
ts, ok := s.TemplateCache[page]
ts, ok := s.templateCache[page]
if !ok {
err := fmt.Errorf("the template %s does not exist", page)
log.Printf("[ERROR] %v", err)
Expand Down Expand Up @@ -89,9 +89,9 @@ func (s Server) indexCtrl(w http.ResponseWriter, r *http.Request) { // nolint
data := templateData{
Form: createMsgForm{
Exp: 15,
MaxExp: humanDuration(s.MaxExpire),
MaxExp: humanDuration(s.cfg.MaxExpire),
},
PinSize: s.PinSize,
PinSize: s.cfg.PinSize,
}

s.render(w, http.StatusOK, "home.tmpl.html", baseTmpl, data)
Expand All @@ -113,13 +113,13 @@ func (s Server) generateLinkCtrl(w http.ResponseWriter, r *http.Request) {
form := createMsgForm{
Message: r.PostForm.Get(msgKey),
ExpUnit: r.PostForm.Get(expUnitKey),
MaxExp: humanDuration(s.MaxExpire),
MaxExp: humanDuration(s.cfg.MaxExpire),
}

pinValues := r.Form["pin"]
for _, p := range pinValues {
if validator.Blank(p) || !validator.IsNumber(p) {
form.AddFieldError(pinKey, fmt.Sprintf("Pin must be %d digits long without empty values", s.PinSize))
form.AddFieldError(pinKey, fmt.Sprintf("Pin must be %d digits long without empty values", s.cfg.PinSize))
break
}
}
Expand All @@ -138,12 +138,12 @@ func (s Server) generateLinkCtrl(w http.ResponseWriter, r *http.Request) {
form.Exp = expInt
expDuration := duration(expInt, r.PostFormValue(expUnitKey))

form.CheckField(validator.MaxDuration(expDuration, s.MaxExpire), expKey, fmt.Sprintf("Expire must be less than %s", humanDuration(s.MaxExpire)))
form.CheckField(validator.MaxDuration(expDuration, s.cfg.MaxExpire), expKey, fmt.Sprintf("Expire must be less than %s", humanDuration(s.cfg.MaxExpire)))

if !form.Valid() {
data := templateData{
Form: form,
PinSize: s.PinSize,
PinSize: s.cfg.PinSize,
}

// attach event listeners to pin inputs
Expand All @@ -153,13 +153,13 @@ func (s Server) generateLinkCtrl(w http.ResponseWriter, r *http.Request) {
return
}

msg, err := s.Messager.MakeMessage(expDuration, form.Message, strings.Join(pinValues, ""))
msg, err := s.messager.MakeMessage(expDuration, form.Message, strings.Join(pinValues, ""))
if err != nil {
s.render(w, http.StatusOK, "secure-link.tmpl.html", errorTmpl, err.Error())
return
}

msgURL := fmt.Sprintf("http://%s/message/%s", s.Domain, msg.Key)
msgURL := fmt.Sprintf("%s://%s/message/%s", s.cfg.Protocol, s.cfg.Domain, msg.Key)

s.render(w, http.StatusOK, "secure-link.tmpl.html", "secure-link", msgURL)
}
Expand All @@ -175,7 +175,7 @@ func (s Server) showMessageViewCtrl(w http.ResponseWriter, r *http.Request) {
Form: showMsgForm{
Key: key,
},
PinSize: s.PinSize,
PinSize: s.cfg.PinSize,
}

w.Header().Add("HX-Trigger-After-Swap", "setUpPinInputListeners")
Expand Down Expand Up @@ -208,15 +208,15 @@ func (s Server) loadMessageCtrl(w http.ResponseWriter, r *http.Request) {
pinValues := r.Form["pin"]
for _, p := range pinValues {
if validator.Blank(p) || !validator.IsNumber(p) {
form.AddFieldError(pinKey, fmt.Sprintf("Pin must be %d digits long without empty values", s.PinSize))
form.AddFieldError(pinKey, fmt.Sprintf("Pin must be %d digits long without empty values", s.cfg.PinSize))
break
}
}

if !form.Valid() {
data := templateData{
Form: form,
PinSize: s.PinSize,
PinSize: s.cfg.PinSize,
}

// attach event listeners to pin inputs
Expand All @@ -226,7 +226,7 @@ func (s Server) loadMessageCtrl(w http.ResponseWriter, r *http.Request) {
return
}

msg, err := s.Messager.LoadMessage(form.Key, strings.Join(pinValues, ""))
msg, err := s.messager.LoadMessage(form.Key, strings.Join(pinValues, ""))
if err != nil {
if errors.Is(err, messager.ErrExpired) || errors.Is(err, store.ErrLoadRejected) {
s.render(w, http.StatusOK, "error.tmpl.html", errorTmpl, err.Error())
Expand All @@ -237,7 +237,7 @@ func (s Server) loadMessageCtrl(w http.ResponseWriter, r *http.Request) {

data := templateData{
Form: form,
PinSize: s.PinSize,
PinSize: s.cfg.PinSize,
}
// attach event listeners to pin inputs
w.Header().Add("HX-Trigger-After-Swap", "setUpPinInputListeners")
Expand Down Expand Up @@ -278,8 +278,8 @@ func humanDuration(d time.Duration) string {
}
}

// NewTemplateCache creates a template cache as a map
func NewTemplateCache() (map[string]*template.Template, error) {
// newTemplateCache creates a template cache as a map
func newTemplateCache() (map[string]*template.Template, error) {
cache := map[string]*template.Template{}

pages, err := fs.Glob(ui.Files, "html/*/*.tmpl.html")
Expand Down
2 changes: 1 addition & 1 deletion app/server/web_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func TestTemplates_HumanDuration(t *testing.T) {
}

func TestTemplates_NewTemplateCache(t *testing.T) {
cache, err := NewTemplateCache()
cache, err := newTemplateCache()

assert.NoError(t, err)

Expand Down
2 changes: 1 addition & 1 deletion docker-compose-dev.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
version: '2'
services:

secrets:
Expand All @@ -22,5 +21,6 @@ services:
- PIN_ATTEMPTS=3
- MAX_EXPIRE=24h
- DOMAIN=localhost:8080
- PROTOCOL=http
ports:
- "8080:8080"

0 comments on commit 2269bc9

Please sign in to comment.