# HG changeset patch # User Sascha L. Teichmann # Date 1532248817 -7200 # Node ID cd6ad5eaef8dedc67ed620fa3d125f078b521741 # Parent 2a152816fc3886b82db040c0afcad815d4734b93 Renamed cmd/tokenserver to cmd/gemma. diff -r 2a152816fc38 -r cd6ad5eaef8d Makefile --- a/Makefile Sun Jul 22 10:24:28 2018 +0200 +++ b/Makefile Sun Jul 22 10:40:17 2018 +0200 @@ -11,12 +11,12 @@ export BUILDBASE -tokenserver-bin := cmd/tokenserver/tokenserver +gemma-bin := cmd/gemma/gemma 3rdpartylibs-stamp := $(BUILDBASE)/3rdpartylibs-build -.PHONY: all 3rdpartylibs tokenserver client clean +.PHONY: all 3rdpartylibs gemma client clean -all: tokenserver client +all: gemma client $(ENVWARPPER): @echo "Preparing go build environment:" @@ -37,15 +37,15 @@ 3rdpartylibs: $(3rdpartylibs-stamp) -$(tokenserver-bin): $(3rdpartylibs-stamp) $(ENVWARPPER) - cd cmd/tokenserver && "$(ENVWARPPER)" go build +$(gemma-bin): $(3rdpartylibs-stamp) $(ENVWARPPER) + cd cmd/gemma && "$(ENVWARPPER)" go build -tokenserver: $(tokenserver-bin) +gemma: $(gemma-bin) client: $(MAKE) -f Makefile.build -C client clean: $(MAKE) -f Makefile.build -C client $@ - rm "$(tokenserver-bin)" + rm "$(gemma-bin)" rm -rf "$(BUILDBASE)" diff -r 2a152816fc38 -r cd6ad5eaef8d README.md --- a/README.md Sun Jul 22 10:24:28 2018 +0200 +++ b/README.md Sun Jul 22 10:40:17 2018 +0200 @@ -10,13 +10,13 @@ Prerequesite: compile server ``` - cd cmd/tokenserver/ + cd cmd/gemma/ go build cd ../../ ``` * Run - Run server with `./cmd/tokenserver/tokenserver` + Run server with `./cmd/gemma/gemma` ## Client diff -r 2a152816fc38 -r cd6ad5eaef8d client/package.json --- a/client/package.json Sun Jul 22 10:24:28 2018 +0200 +++ b/client/package.json Sun Jul 22 10:40:17 2018 +0200 @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "run:both": "concurrently \"../cmd/tokenserver/tokenserver\" \"vue-cli-service serve\"", + "run:both": "concurrently \"../cmd/gemma/gemma\" \"vue-cli-service serve\"", "swagger": "serve ../api/server/ui", "serve": "vue-cli-service serve", "build": "vue-cli-service build", diff -r 2a152816fc38 -r cd6ad5eaef8d cmd/gemma/main.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/gemma/main.go Sun Jul 22 10:40:17 2018 +0200 @@ -0,0 +1,69 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "path/filepath" + "syscall" + + "gemma.intevation.de/gemma/auth" +) + +func sysAdmin(rw http.ResponseWriter, req *http.Request) { + session, _ := auth.GetSession(req) + rw.Header().Set("Content-Type", "text/plain") + fmt.Fprintf(rw, "%s is a sys_admin\n", session.User) +} + +func main() { + port := flag.Int("port", 8000, "port to listen at.") + host := flag.String("host", "localhost", "host to listen at.") + flag.Parse() + p, _ := filepath.Abs("./web") + mux := http.NewServeMux() + mux.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(p)))) + mux.HandleFunc("/api/token", token) + mux.Handle("/api/logout", auth.SessionMiddleware(http.HandlerFunc(token))) + mux.Handle("/api/renew", auth.SessionMiddleware(http.HandlerFunc(renew))) + mux.Handle("/api/sys_admin", + auth.SessionMiddleware( + auth.SessionChecker(http.HandlerFunc(sysAdmin), auth.HasRole("sys_admin")))) + mux.Handle("/api/create_user", + auth.SessionMiddleware( + auth.SessionChecker(http.HandlerFunc(createUser), auth.HasRole("sys_admin")))) + + addr := fmt.Sprintf("%s:%d", *host, *port) + + server := http.Server{Addr: addr, Handler: mux} + + done := make(chan error) + + go func() { + defer close(done) + done <- server.ListenAndServe() + }() + + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, os.Kill, syscall.SIGTERM) + + select { + case err := <-done: + if err != nil && err != http.ErrServerClosed { + log.Fatalf("error: %v\n", err) + } + case <-sigChan: + } + + server.Shutdown(context.Background()) + + <-done + + if err := auth.ConnPool.Shutdown(); err != nil { + log.Fatalf("error: %v\n", err) + } +} diff -r 2a152816fc38 -r cd6ad5eaef8d cmd/gemma/token.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/gemma/token.go Sun Jul 22 10:40:17 2018 +0200 @@ -0,0 +1,82 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "gemma.intevation.de/gemma/auth" +) + +func renew(rw http.ResponseWriter, req *http.Request) { + token, _ := auth.GetToken(req) + newToken, err := auth.ConnPool.Renew(token) + switch { + case err == auth.ErrNoSuchToken: + http.NotFound(rw, req) + return + case err != nil: + http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusInternalServerError) + return + } + + session, _ := auth.GetSession(req) + + var result = struct { + Token string `json:"token"` + Expires int64 `json:"expires"` + User string `json:"user"` + Roles []string `json:"roles"` + }{ + Token: newToken, + Expires: session.ExpiresAt, + User: session.User, + Roles: session.Roles, + } + + rw.Header().Set("Content-Type", "text/plain") + if err := json.NewEncoder(rw).Encode(&result); err != nil { + log.Printf("error: %v\n", err) + } +} + +func logout(rw http.ResponseWriter, req *http.Request) { + token, _ := auth.GetToken(req) + deleted := auth.ConnPool.Delete(token) + if !deleted { + http.NotFound(rw, req) + return + } + rw.Header().Set("Content-Type", "text/plain") + fmt.Fprintln(rw, "token deleted") +} + +func token(rw http.ResponseWriter, req *http.Request) { + user := req.FormValue("user") + password := req.FormValue("password") + + token, session, err := auth.GenerateSession(user, password) + + if err != nil { + http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusInternalServerError) + return + } + + var result = struct { + Token string `json:"token"` + Expires int64 `json:"expires"` + User string `json:"user"` + Roles []string `json:"roles"` + }{ + Token: token, + Expires: session.ExpiresAt, + User: session.User, + Roles: session.Roles, + } + + rw.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(rw).Encode(&result); err != nil { + log.Printf("error: %v\n", err) + } +} diff -r 2a152816fc38 -r cd6ad5eaef8d cmd/gemma/user.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/gemma/user.go Sun Jul 22 10:40:17 2018 +0200 @@ -0,0 +1,180 @@ +package main + +import ( + "database/sql" + "encoding/json" + "errors" + "log" + "net/http" + "regexp" + "strings" + + "gemma.intevation.de/gemma/auth" + "github.com/jackc/pgx" +) + +type ( + Email string + Country string + Role string + + BoundingBox struct { + X1 float64 `json:"x1"` + Y1 float64 `json:"y1"` + X2 float64 `json:"x2"` + Y2 float64 `json:"y2"` + } + + User struct { + User string `json:"user"` + Role Role `json:"role"` + Password string `json:"password"` + Email Email `json:"email"` + Country Country `json:"country"` + Extent *BoundingBox `json:"extent"` + } +) + +const ( + createUserSQL = `SELECT create_user($1, $2, $3, $4, NULL, $5)` + createUserExtentSQL = `SELECT create_user($1, $2, $3, $4, + ST_MakeBox2D(ST_Point($5, $6), ST_Point($7, $8)), $9)` +) + +var ( + // https://stackoverflow.com/questions/201323/how-to-validate-an-email-address-using-a-regular-expression + emailRe = regexp.MustCompile( + `(?:[a-z0-9!#$%&'*+/=?^_` + "`" + + `{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_` + "`" + + `{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]` + + `|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")` + + `@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?` + + `|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}` + + `(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]` + + `:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]` + + `|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])`) + errNoEmailAddress = errors.New("Not a valid email address") +) + +func (e *Email) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if !emailRe.MatchString(s) { + return errNoEmailAddress + } + *e = Email(s) + return nil +} + +var ( + validCountries = []string{ + "AT", "BG", "DE", "HU", "HR", + "MD", "RO", "RS", "SK", "UA", + } + errNoValidCountry = errors.New("Not a valid country") +) + +func (c *Country) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + s = strings.ToUpper(s) + for _, v := range validCountries { + if v == s { + *c = Country(v) + return nil + } + } + return errNoValidCountry +} + +var ( + validRoles = []string{ + "waterway_user", + "waterway_admin", + "sys_admin", + } + errNoValidRole = errors.New("Not a valid role") +) + +func (r *Role) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + s = strings.ToLower(s) + for _, v := range validRoles { + if v == s { + *r = Role(v) + return nil + } + } + return errNoValidRole +} + +func createUser(rw http.ResponseWriter, req *http.Request) { + + var user User + + defer req.Body.Close() + if err := json.NewDecoder(req.Body).Decode(&user); err != nil { + http.Error(rw, "error: "+err.Error(), http.StatusBadRequest) + return + } + + token, _ := auth.GetToken(req) + err := auth.ConnPool.Do(token, func(db *sql.DB) (err error) { + if user.Extent == nil { + _, err = db.Exec( + createUserSQL, + string(user.Role), + user.User, + user.Password, + string(user.Country), + string(user.Email), + ) + } else { + _, err = db.Exec( + createUserSQL, + string(user.Role), + user.User, + user.Password, + string(user.Country), + user.Extent.X1, user.Extent.Y1, + user.Extent.X2, user.Extent.Y2, + string(user.Email), + ) + } + return + }) + + var res struct { + Result string `json:"result"` + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + } + + if err != nil { + if pgErr, ok := err.(pgx.PgError); ok { + res.Result = "failure" + res.Code = pgErr.Code + res.Message = pgErr.Message + } else { + log.Printf("err: %v\n", err) + http.Error(rw, + "error: "+err.Error(), + http.StatusInternalServerError) + return + } + } else { + res.Result = "success" + } + + rw.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(rw).Encode(&res); err != nil { + log.Printf("error: %v\n", err) + } +} diff -r 2a152816fc38 -r cd6ad5eaef8d cmd/tokenserver/main.go --- a/cmd/tokenserver/main.go Sun Jul 22 10:24:28 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "log" - "net/http" - "os" - "os/signal" - "path/filepath" - "syscall" - - "gemma.intevation.de/gemma/auth" -) - -func sysAdmin(rw http.ResponseWriter, req *http.Request) { - session, _ := auth.GetSession(req) - rw.Header().Set("Content-Type", "text/plain") - fmt.Fprintf(rw, "%s is a sys_admin\n", session.User) -} - -func main() { - port := flag.Int("port", 8000, "port to listen at.") - host := flag.String("host", "localhost", "host to listen at.") - flag.Parse() - p, _ := filepath.Abs("./web") - mux := http.NewServeMux() - mux.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(p)))) - mux.HandleFunc("/api/token", token) - mux.Handle("/api/logout", auth.SessionMiddleware(http.HandlerFunc(token))) - mux.Handle("/api/renew", auth.SessionMiddleware(http.HandlerFunc(renew))) - mux.Handle("/api/sys_admin", - auth.SessionMiddleware( - auth.SessionChecker(http.HandlerFunc(sysAdmin), auth.HasRole("sys_admin")))) - mux.Handle("/api/create_user", - auth.SessionMiddleware( - auth.SessionChecker(http.HandlerFunc(createUser), auth.HasRole("sys_admin")))) - - addr := fmt.Sprintf("%s:%d", *host, *port) - - server := http.Server{Addr: addr, Handler: mux} - - done := make(chan error) - - go func() { - defer close(done) - done <- server.ListenAndServe() - }() - - sigChan := make(chan os.Signal) - signal.Notify(sigChan, os.Interrupt, os.Kill, syscall.SIGTERM) - - select { - case err := <-done: - if err != nil && err != http.ErrServerClosed { - log.Fatalf("error: %v\n", err) - } - case <-sigChan: - } - - server.Shutdown(context.Background()) - - <-done - - if err := auth.ConnPool.Shutdown(); err != nil { - log.Fatalf("error: %v\n", err) - } -} diff -r 2a152816fc38 -r cd6ad5eaef8d cmd/tokenserver/token.go --- a/cmd/tokenserver/token.go Sun Jul 22 10:24:28 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - - "gemma.intevation.de/gemma/auth" -) - -func renew(rw http.ResponseWriter, req *http.Request) { - token, _ := auth.GetToken(req) - newToken, err := auth.ConnPool.Renew(token) - switch { - case err == auth.ErrNoSuchToken: - http.NotFound(rw, req) - return - case err != nil: - http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusInternalServerError) - return - } - - session, _ := auth.GetSession(req) - - var result = struct { - Token string `json:"token"` - Expires int64 `json:"expires"` - User string `json:"user"` - Roles []string `json:"roles"` - }{ - Token: newToken, - Expires: session.ExpiresAt, - User: session.User, - Roles: session.Roles, - } - - rw.Header().Set("Content-Type", "text/plain") - if err := json.NewEncoder(rw).Encode(&result); err != nil { - log.Printf("error: %v\n", err) - } -} - -func logout(rw http.ResponseWriter, req *http.Request) { - token, _ := auth.GetToken(req) - deleted := auth.ConnPool.Delete(token) - if !deleted { - http.NotFound(rw, req) - return - } - rw.Header().Set("Content-Type", "text/plain") - fmt.Fprintln(rw, "token deleted") -} - -func token(rw http.ResponseWriter, req *http.Request) { - user := req.FormValue("user") - password := req.FormValue("password") - - token, session, err := auth.GenerateSession(user, password) - - if err != nil { - http.Error(rw, fmt.Sprintf("error: %v", err), http.StatusInternalServerError) - return - } - - var result = struct { - Token string `json:"token"` - Expires int64 `json:"expires"` - User string `json:"user"` - Roles []string `json:"roles"` - }{ - Token: token, - Expires: session.ExpiresAt, - User: session.User, - Roles: session.Roles, - } - - rw.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(rw).Encode(&result); err != nil { - log.Printf("error: %v\n", err) - } -} diff -r 2a152816fc38 -r cd6ad5eaef8d cmd/tokenserver/user.go --- a/cmd/tokenserver/user.go Sun Jul 22 10:24:28 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,180 +0,0 @@ -package main - -import ( - "database/sql" - "encoding/json" - "errors" - "log" - "net/http" - "regexp" - "strings" - - "gemma.intevation.de/gemma/auth" - "github.com/jackc/pgx" -) - -type ( - Email string - Country string - Role string - - BoundingBox struct { - X1 float64 `json:"x1"` - Y1 float64 `json:"y1"` - X2 float64 `json:"x2"` - Y2 float64 `json:"y2"` - } - - User struct { - User string `json:"user"` - Role Role `json:"role"` - Password string `json:"password"` - Email Email `json:"email"` - Country Country `json:"country"` - Extent *BoundingBox `json:"extent"` - } -) - -const ( - createUserSQL = `SELECT create_user($1, $2, $3, $4, NULL, $5)` - createUserExtentSQL = `SELECT create_user($1, $2, $3, $4, - ST_MakeBox2D(ST_Point($5, $6), ST_Point($7, $8)), $9)` -) - -var ( - // https://stackoverflow.com/questions/201323/how-to-validate-an-email-address-using-a-regular-expression - emailRe = regexp.MustCompile( - `(?:[a-z0-9!#$%&'*+/=?^_` + "`" + - `{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_` + "`" + - `{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]` + - `|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")` + - `@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?` + - `|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}` + - `(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]` + - `:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]` + - `|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])`) - errNoEmailAddress = errors.New("Not a valid email address") -) - -func (e *Email) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - if !emailRe.MatchString(s) { - return errNoEmailAddress - } - *e = Email(s) - return nil -} - -var ( - validCountries = []string{ - "AT", "BG", "DE", "HU", "HR", - "MD", "RO", "RS", "SK", "UA", - } - errNoValidCountry = errors.New("Not a valid country") -) - -func (c *Country) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - s = strings.ToUpper(s) - for _, v := range validCountries { - if v == s { - *c = Country(v) - return nil - } - } - return errNoValidCountry -} - -var ( - validRoles = []string{ - "waterway_user", - "waterway_admin", - "sys_admin", - } - errNoValidRole = errors.New("Not a valid role") -) - -func (r *Role) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - s = strings.ToLower(s) - for _, v := range validRoles { - if v == s { - *r = Role(v) - return nil - } - } - return errNoValidRole -} - -func createUser(rw http.ResponseWriter, req *http.Request) { - - var user User - - defer req.Body.Close() - if err := json.NewDecoder(req.Body).Decode(&user); err != nil { - http.Error(rw, "error: "+err.Error(), http.StatusBadRequest) - return - } - - token, _ := auth.GetToken(req) - err := auth.ConnPool.Do(token, func(db *sql.DB) (err error) { - if user.Extent == nil { - _, err = db.Exec( - createUserSQL, - string(user.Role), - user.User, - user.Password, - string(user.Country), - string(user.Email), - ) - } else { - _, err = db.Exec( - createUserSQL, - string(user.Role), - user.User, - user.Password, - string(user.Country), - user.Extent.X1, user.Extent.Y1, - user.Extent.X2, user.Extent.Y2, - string(user.Email), - ) - } - return - }) - - var res struct { - Result string `json:"result"` - Code string `json:"code,omitempty"` - Message string `json:"message,omitempty"` - } - - if err != nil { - if pgErr, ok := err.(pgx.PgError); ok { - res.Result = "failure" - res.Code = pgErr.Code - res.Message = pgErr.Message - } else { - log.Printf("err: %v\n", err) - http.Error(rw, - "error: "+err.Error(), - http.StatusInternalServerError) - return - } - } else { - res.Result = "success" - } - - rw.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(rw).Encode(&res); err != nil { - log.Printf("error: %v\n", err) - } -}