diff --git a/.gitignore b/.gitignore index df30165..97cb863 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .idea/ server -sofaraum-server \ No newline at end of file +sofaraum-server +config.yml +config.yaml \ No newline at end of file diff --git a/go.mod b/go.mod index 1d5b6fe..5164d71 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,25 @@ -module git.kolaente.de/sofaraum/server +module code.sofaraum.de/server + +require ( + code.vikunja.io/web v0.0.0-20181130231148-b061c20192fb + github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc + github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/garyburd/redigo v1.6.0 // indirect + github.com/go-openapi/jsonreference v0.17.2 // indirect + github.com/go-openapi/spec v0.17.2 // indirect + github.com/go-sql-driver/mysql v1.4.1 + github.com/go-xorm/core v0.6.0 + github.com/go-xorm/xorm v0.7.1 + github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2 + github.com/labstack/echo v3.3.5+incompatible + github.com/mattn/go-sqlite3 v1.10.0 + github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 + github.com/spf13/viper v1.3.0 + github.com/swaggo/echo-swagger v0.0.0-20180315045949-97f46bb9e5a5 + github.com/swaggo/files v0.0.0-20180215091130-49c8a91ea3fa // indirect + github.com/swaggo/swag v1.4.0 + golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 + golang.org/x/net v0.0.0-20181201002055-351d144fa1fc // indirect + golang.org/x/tools v0.0.0-20181205224935-3576414c54a4 // indirect +) diff --git a/main.go b/main.go index ffe329f..7ba510b 100644 --- a/main.go +++ b/main.go @@ -17,8 +17,66 @@ package main -import "fmt" +import ( + "code.sofaraum.de/server/docs" + "code.sofaraum.de/server/pkg/config" + "code.sofaraum.de/server/pkg/log" + "code.sofaraum.de/server/pkg/models" + "code.sofaraum.de/server/pkg/routes" + "context" + "github.com/spf13/viper" + "os" + "os/signal" + "time" +) + +// Version sets the version to be printed to the user. Gets overwritten by "make release" or "make build" with last git commit or tag. +var Version = "0.1" func main() { - fmt.Println("schinken") + + // Init logging + log.InitLogger() + + // Init Config + err := config.InitConfig() + if err != nil { + log.Log.Error(err.Error()) + os.Exit(1) + } + + // Set Engine + err = models.SetEngine() + if err != nil { + log.Log.Error(err.Error()) + os.Exit(1) + } + + // Version notification + log.Log.Infof("Sofaraum version %s", Version) + + // Additional swagger information + docs.SwaggerInfo.Version = Version + + // Start the webserver + e := routes.NewEcho() + routes.RegisterRoutes(e) + // Start server + go func() { + if err := e.Start(viper.GetString("service.interface")); err != nil { + e.Logger.Info("shutting down...") + } + }() + + // Wait for interrupt signal to gracefully shutdown the server with + // a timeout of 10 seconds. + quit := make(chan os.Signal) + signal.Notify(quit, os.Interrupt) + <-quit + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + log.Log.Infof("Shutting down...") + if err := e.Shutdown(ctx); err != nil { + e.Logger.Fatal(err) + } } diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..5c9d4f9 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,90 @@ +// Sofaraum server is the server which collects the statistics +// for the sofaraum-heatmap application. +// Copyright 2018 K.Langenberg and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package config + +import ( + "crypto/rand" + "fmt" + "github.com/spf13/viper" + "os" + "path/filepath" + "strings" +) + +// InitConfig initializes the config, sets defaults etc. +func InitConfig() (err error) { + + // Set defaults + // Service config + random, err := random(32) + if err != nil { + return err + } + + // Service + viper.SetDefault("service.JWTSecret", random) + viper.SetDefault("service.interface", ":1073") + viper.SetDefault("service.frontendurl", "") + ex, err := os.Executable() + if err != nil { + panic(err) + } + exPath := filepath.Dir(ex) + viper.SetDefault("service.rootpath", exPath) + viper.SetDefault("service.pagecount", 50) + // Database + viper.SetDefault("database.type", "sqlite") + viper.SetDefault("database.host", "localhost") + viper.SetDefault("database.user", "sofaraum") + viper.SetDefault("database.password", "") + viper.SetDefault("database.database", "sofaraum") + viper.SetDefault("database.path", "./sofaraum.db") + viper.SetDefault("database.showqueries", false) + viper.SetDefault("database.openconnections", 100) + // Cacher + viper.SetDefault("cache.enabled", false) + viper.SetDefault("cache.type", "memory") + viper.SetDefault("cache.maxelementsize", 1000) + viper.SetDefault("cache.redishost", "localhost:6379") + viper.SetDefault("cache.redispassword", "") + + // Init checking for environment variables + viper.SetEnvPrefix("sofaraum") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() + + // Load the config file + viper.AddConfigPath(".") + viper.SetConfigName("config") + err = viper.ReadInConfig() + if err != nil { + fmt.Println(err) + fmt.Println("Using defaults.") + } + + return nil +} + +func random(length int) (string, error) { + b := make([]byte, length) + if _, err := rand.Read(b); err != nil { + return "", err + } + + return fmt.Sprintf("%X", b), nil +} diff --git a/pkg/log/logging.go b/pkg/log/logging.go new file mode 100644 index 0000000..5611ae4 --- /dev/null +++ b/pkg/log/logging.go @@ -0,0 +1,37 @@ +// Sofaraum server is the server which collects the statistics +// for the sofaraum-heatmap application. +// Copyright 2018 K.Langenberg and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package log + +import ( + "github.com/op/go-logging" + "os" +) + +// Log is the handler for the logger +var Log = logging.MustGetLogger("sofaraum-server") + +var format = logging.MustStringFormatter( + `%{color}%{time:2006-01-02 15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`, +) + +// InitLogger initializes the global log handler +func InitLogger() { + backend := logging.NewLogBackend(os.Stderr, "", 0) + backendFormatter := logging.NewBackendFormatter(backend, format) + logging.SetBackend(backendFormatter) +} diff --git a/pkg/models/error.go b/pkg/models/error.go new file mode 100644 index 0000000..0f4080b --- /dev/null +++ b/pkg/models/error.go @@ -0,0 +1,152 @@ +// Sofaraum server is the server which collects the statistics +// for the sofaraum-heatmap application. +// Copyright 2018 K.Langenberg and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package models + +import ( + "code.vikunja.io/web" + "fmt" + "net/http" +) + +// ErrInvalidData represents a "ErrInvalidData" kind of error. Used when a struct is invalid -> validation failed. +type ErrInvalidData struct { + Message string +} + +// IsErrInvalidData checks if an error is a ErrIDCannotBeZero. +func IsErrInvalidData(err error) bool { + _, ok := err.(ErrInvalidData) + return ok +} + +func (err ErrInvalidData) Error() string { + return fmt.Sprintf("Struct is invalid. %s", err.Message) +} + +// ErrCodeInvalidData holds the unique world-error code of this error +const ErrCodeInvalidData = 1000 + +// HTTPError holds the http error description +func (err ErrInvalidData) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeInvalidData, Message: err.Message} +} + +// ValidationHTTPError is the http error when a validation fails +type ValidationHTTPError struct { + web.HTTPError + InvalidFields []string `json:"invalid_fields"` +} + +// Error implements the Error type (so we can return it as type error) +func (err ValidationHTTPError) Error() string { + theErr := ErrInvalidData{ + Message: err.Message, + } + return theErr.Error() +} + +// ============= +// User Errors +// ============= + +// ErrUserDoesNotExist represents a "UserDoesNotExist" kind of error. +type ErrUserDoesNotExist struct { + UserID int64 +} + +// IsErrUserDoesNotExist checks if an error is a ErrUserDoesNotExist. +func IsErrUserDoesNotExist(err error) bool { + _, ok := err.(ErrUserDoesNotExist) + return ok +} + +func (err ErrUserDoesNotExist) Error() string { + return fmt.Sprintf("User does not exist [user id: %d]", err.UserID) +} + +// ErrCodeUserDoesNotExist holds the unique world-error code of this error +const ErrCodeUserDoesNotExist = 2000 + +// HTTPError holds the http error description +func (err ErrUserDoesNotExist) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeUserDoesNotExist, Message: "The user does not exist."} +} + +// ErrNoUsernamePassword represents a "NoUsernamePassword" kind of error. +type ErrNoUsernamePassword struct{} + +// IsErrNoUsernamePassword checks if an error is a ErrNoUsernamePassword. +func IsErrNoUsernamePassword(err error) bool { + _, ok := err.(ErrNoUsernamePassword) + return ok +} + +func (err ErrNoUsernamePassword) Error() string { + return fmt.Sprintf("No username and password provided") +} + +// ErrCodeNoUsernamePassword holds the unique world-error code of this error +const ErrCodeNoUsernamePassword = 2001 + +// HTTPError holds the http error description +func (err ErrNoUsernamePassword) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeNoUsernamePassword, Message: "Please specify a username and a password."} +} + +// ErrWrongUsernameOrPassword is an error where the email was not confirmed +type ErrWrongUsernameOrPassword struct { +} + +func (err ErrWrongUsernameOrPassword) Error() string { + return fmt.Sprintf("Wrong username or password") +} + +// ErrCodeWrongUsernameOrPassword holds the unique world-error code of this error +const ErrCodeWrongUsernameOrPassword = 2002 + +// HTTPError holds the http error description +func (err ErrWrongUsernameOrPassword) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeWrongUsernameOrPassword, Message: "Wrong username or password."} +} + +// IsErrWrongUsernameOrPassword checks if an error is a IsErrEmailNotConfirmed. +func IsErrWrongUsernameOrPassword(err error) bool { + _, ok := err.(ErrWrongUsernameOrPassword) + return ok +} + +// ErrCouldNotGetUserID represents a "ErrCouldNotGetUserID" kind of error. +type ErrCouldNotGetUserID struct{} + +// IsErrCouldNotGetUserID checks if an error is a ErrCouldNotGetUserID. +func IsErrCouldNotGetUserID(err error) bool { + _, ok := err.(ErrCouldNotGetUserID) + return ok +} + +func (err ErrCouldNotGetUserID) Error() string { + return fmt.Sprintf("Could not get user ID") +} + +// ErrCodeCouldNotGetUserID holds the unique world-error code of this error +const ErrCodeCouldNotGetUserID = 2003 + +// HTTPError holds the http error description +func (err ErrCouldNotGetUserID) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCouldNotGetUserID, Message: "Could not get user id."} +} diff --git a/pkg/models/models.go b/pkg/models/models.go new file mode 100644 index 0000000..c3a391b --- /dev/null +++ b/pkg/models/models.go @@ -0,0 +1,110 @@ +// Sofaraum server is the server which collects the statistics +// for the sofaraum-heatmap application. +// Copyright 2018 K.Langenberg and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package models + +import ( + "fmt" + _ "github.com/go-sql-driver/mysql" // Because. + "github.com/go-xorm/core" + "github.com/go-xorm/xorm" + xrc "github.com/go-xorm/xorm-redis-cache" + _ "github.com/mattn/go-sqlite3" // Because. + + "encoding/gob" + "github.com/spf13/viper" +) + +var ( + x *xorm.Engine + + tables []interface{} +) + +func getEngine() (*xorm.Engine, error) { + // Use Mysql if set + if viper.GetString("database.type") == "mysql" { + connStr := fmt.Sprintf( + "%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true", + viper.GetString("database.user"), + viper.GetString("database.password"), + viper.GetString("database.host"), + viper.GetString("database.database")) + e, err := xorm.NewEngine("mysql", connStr) + e.SetMaxOpenConns(viper.GetInt("database.openconnections")) + return e, err + } + + // Otherwise use sqlite + path := viper.GetString("database.path") + if path == "" { + path = "./db.db" + } + return xorm.NewEngine("sqlite3", path) +} + +func init() { + tables = append(tables) +} + +// SetEngine sets the xorm.Engine +func SetEngine() (err error) { + x, err = getEngine() + if err != nil { + return fmt.Errorf("Failed to connect to database: %v", err) + } + + // Cache + if viper.GetBool("cache.enabled") { + switch viper.GetString("cache.type") { + case "memory": + cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), viper.GetInt("cache.maxelementsize")) + x.SetDefaultCacher(cacher) + break + case "redis": + cacher := xrc.NewRedisCacher(viper.GetString("cache.redishost"), viper.GetString("cache.redispassword"), xrc.DEFAULT_EXPIRATION, x.Logger()) + x.SetDefaultCacher(cacher) + gob.Register(tables) + break + default: + fmt.Println("Did not find a valid cache type. Caching disabled. Please refer to the docs for poosible cache types.") + } + } + + x.SetMapper(core.GonicMapper{}) + + // Sync dat shit + if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil { + return fmt.Errorf("sync database struct error: %v", err) + } + + x.ShowSQL(viper.GetBool("database.showqueries")) + + return nil +} + +func getLimitFromPageIndex(page int) (limit, start int) { + + // Get everything when page index is -1 + if page < 0 { + return 0, 0 + } + + limit = viper.GetInt("service.pagecount") + start = limit * (page - 1) + return +} diff --git a/pkg/models/user.go b/pkg/models/user.go new file mode 100644 index 0000000..0b10814 --- /dev/null +++ b/pkg/models/user.go @@ -0,0 +1,157 @@ +// Sofaraum server is the server which collects the statistics +// for the sofaraum-heatmap application. +// Copyright 2018 K.Langenberg and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package models + +import ( + "code.sofaraum.de/server/pkg/log" + "code.vikunja.io/web" + "fmt" + "github.com/dgrijalva/jwt-go" + "github.com/labstack/echo" + "golang.org/x/crypto/bcrypt" + "reflect" +) + +// UserLogin Object to recive user credentials in JSON format +type UserLogin struct { + Username string `json:"username" form:"username"` + Password string `json:"password" form:"password"` +} + +// User holds information about an user +type User struct { + ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"` + Username string `xorm:"varchar(250) not null unique" json:"username" valid:"length(3|250)"` + Password string `xorm:"varchar(250) not null" json:"-"` + Email string `xorm:"varchar(250)" json:"email" valid:"email,length(0|250)"` + IsActive bool `json:"-"` + + PasswordResetToken string `xorm:"varchar(450)" json:"-"` + EmailConfirmToken string `xorm:"varchar(450)" json:"-"` + + Created int64 `xorm:"created" json:"created"` + Updated int64 `xorm:"updated" json:"updated"` + + web.Auth `xorm:"-" json:"-"` +} + +// AuthDummy implements the auth of the crud handler +func (User) AuthDummy() {} + +// TableName returns the table name for users +func (User) TableName() string { + return "users" +} + +func getUserForRights(a web.Auth) *User { + u, err := getUserWithError(a) + if err != nil { + log.Log.Error(err.Error()) + } + return u +} + +func getUserWithError(a web.Auth) (*User, error) { + u, is := a.(*User) + if !is { + return &User{}, fmt.Errorf("user is not user element, is %s", reflect.TypeOf(a)) + } + return u, nil +} + +// APIUserPassword represents a user object without timestamps and a json password field. +type APIUserPassword struct { + ID int64 `json:"id"` + Username string `json:"username"` + Password string `json:"password"` + Email string `json:"email"` +} + +// APIFormat formats an API User into a normal user struct +func (apiUser *APIUserPassword) APIFormat() User { + return User{ + ID: apiUser.ID, + Username: apiUser.Username, + Password: apiUser.Password, + Email: apiUser.Email, + } +} + +// GetUserByID gets informations about a user by its ID +func GetUserByID(id int64) (user *User, err error) { + // Apparently xorm does otherwise look for all users but return only one, which leads to returing one even if the ID is 0 + if id < 1 { + return &User{}, ErrUserDoesNotExist{} + } + + return GetUser(&User{ID: id}) +} + +// GetUser gets a user object +func GetUser(user *User) (userOut *User, err error) { + userOut = user + exists, err := x.Get(&userOut) + + if !exists { + return &User{}, ErrUserDoesNotExist{UserID: user.ID} + } + + return userOut, err +} + +// CheckUserCredentials checks user credentials +func CheckUserCredentials(u *UserLogin) (*User, error) { + // Check if we have any credentials + if u.Password == "" || u.Username == "" { + return &User{}, ErrNoUsernamePassword{} + } + + // Check if the user exists + user, err := GetUser(&User{Username: u.Username}) + if err != nil { + return &User{}, err + } + + // Check the users password + err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(u.Password)) + if err != nil { + if err == bcrypt.ErrMismatchedHashAndPassword { + return &User{}, ErrWrongUsernameOrPassword{} + } + return &User{}, err + } + + return user, nil +} + +// GetCurrentUser returns the current user based on its jwt token +func GetCurrentUser(c echo.Context) (user *User, err error) { + jwtinf := c.Get("user").(*jwt.Token) + claims := jwtinf.Claims.(jwt.MapClaims) + userID, ok := claims["id"].(float64) + if !ok { + return user, ErrCouldNotGetUserID{} + } + user = &User{ + ID: int64(userID), + Email: claims["email"].(string), + Username: claims["username"].(string), + } + + return +} diff --git a/pkg/routes/api/v1/login.go b/pkg/routes/api/v1/login.go new file mode 100644 index 0000000..2b908e9 --- /dev/null +++ b/pkg/routes/api/v1/login.go @@ -0,0 +1,80 @@ +// Sofaraum server is the server which collects the statistics +// for the sofaraum-heatmap application. +// Copyright 2018 K.Langenberg and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package v1 + +import ( + "code.sofaraum.de/server/pkg/models" + "code.vikunja.io/web/handler" + "crypto/md5" + "encoding/hex" + "github.com/dgrijalva/jwt-go" + "github.com/labstack/echo" + "github.com/spf13/viper" + "net/http" + "time" +) + +// Token represents an authentification token +type Token struct { + Token string `json:"token"` +} + +// Login is the login handler +// @Summary Login +// @Description Logs a user in. Returns a JWT-Token to authenticate further requests. +// @tags user +// @Accept json +// @Produce json +// @Param credentials body models.UserLogin true "The login credentials" +// @Success 200 {object} v1.Token +// @Failure 400 {object} models.Message "Invalid user password model." +// @Failure 403 {object} models.Message "Invalid username or password." +// @Router /login [post] +func Login(c echo.Context) error { + u := models.UserLogin{} + if err := c.Bind(&u); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Please provide a username and password.") + } + + // Check user + user, err := models.CheckUserCredentials(&u) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + // Create token + token := jwt.New(jwt.SigningMethodHS256) + + // Set claims + claims := token.Claims.(jwt.MapClaims) + claims["username"] = user.Username + claims["email"] = user.Email + claims["id"] = user.ID + claims["exp"] = time.Now().Add(time.Hour * 72).Unix() + + avatar := md5.Sum([]byte(user.Email)) + claims["avatar"] = hex.EncodeToString(avatar[:]) + + // Generate encoded token and send it as response. + t, err := token.SignedString([]byte(viper.GetString("service.JWTSecret"))) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, Token{Token: t}) +} diff --git a/pkg/routes/api/v1/token_check.go b/pkg/routes/api/v1/token_check.go new file mode 100644 index 0000000..8291be9 --- /dev/null +++ b/pkg/routes/api/v1/token_check.go @@ -0,0 +1,34 @@ +// Sofaraum server is the server which collects the statistics +// for the sofaraum-heatmap application. +// Copyright 2018 K.Langenberg and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package v1 + +import ( + "fmt" + "github.com/dgrijalva/jwt-go" + "github.com/labstack/echo" +) + +// CheckToken checks prints a message if the token is valid or not. Currently only used for testing pourposes. +func CheckToken(c echo.Context) error { + + user := c.Get("user").(*jwt.Token) + + fmt.Println(user.Valid) + + return c.String(418, "🍵") +} diff --git a/pkg/routes/api/v1/user_show.go b/pkg/routes/api/v1/user_show.go new file mode 100644 index 0000000..156c3cd --- /dev/null +++ b/pkg/routes/api/v1/user_show.go @@ -0,0 +1,50 @@ +// Sofaraum server is the server which collects the statistics +// for the sofaraum-heatmap application. +// Copyright 2018 K.Langenberg and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package v1 + +import ( + "code.sofaraum.de/server/pkg/models" + "code.vikunja.io/web/handler" + "github.com/labstack/echo" + "net/http" +) + +// UserShow gets all informations about the current user +// @Summary Get user information +// @Description Returns the current user object. +// @tags user +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Success 200 {object} models.User +// @Failure 404 {object} code.vikunja.io/web.HTTPError "User does not exist." +// @Failure 500 {object} models.Message "Internal server error." +// @Router /user [get] +func UserShow(c echo.Context) error { + userInfos, err := models.GetCurrentUser(c) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Error getting current user.") + } + + user, err := models.GetUserByID(userInfos.ID) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + return c.JSON(http.StatusOK, user) +} diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go new file mode 100644 index 0000000..3527a60 --- /dev/null +++ b/pkg/routes/routes.go @@ -0,0 +1,120 @@ +// Sofaraum server is the server which collects the statistics +// for the sofaraum-heatmap application. +// Copyright 2018 K.Langenberg and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// @title Sofaraum Server API +// @license.name GPLv3 +// @BasePath /api/v1 + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization + +package routes + +import ( + _ "code.sofaraum.de/server/docs" // To generate swagger docs + "code.sofaraum.de/server/pkg/log" + "code.sofaraum.de/server/pkg/models" + apiv1 "code.sofaraum.de/server/pkg/routes/api/v1" + "code.vikunja.io/web" + "github.com/asaskevich/govalidator" + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" + "github.com/spf13/viper" + "github.com/swaggo/echo-swagger" +) + +// CustomValidator is a dummy struct to use govalidator with echo +type CustomValidator struct{} + +// Validate validates stuff +func (cv *CustomValidator) Validate(i interface{}) error { + if _, err := govalidator.ValidateStruct(i); err != nil { + + var errs []string + for field, e := range govalidator.ErrorsByField(err) { + errs = append(errs, field+": "+e) + } + + httperr := models.ValidationHTTPError{ + web.HTTPError{ + Code: models.ErrCodeInvalidData, + Message: "Invalid Data", + }, + errs, + } + + return httperr + } + return nil +} + +// NewEcho registers a new Echo instance +func NewEcho() *echo.Echo { + e := echo.New() + + e.HideBanner = true + + // Logger + e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ + Format: "${time_rfc3339_nano}: ${remote_ip} ${method} ${status} ${uri} ${latency_human} - ${user_agent}\n", + })) + + // Validation + e.Validator = &CustomValidator{} + + return e +} + +// RegisterRoutes registers all routes for the application +func RegisterRoutes(e *echo.Echo) { + + // CORS_SHIT + e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ + AllowOrigins: []string{"*"}, + })) + + // API Routes + a := e.Group("/api/v1") + + // Swagger UI + a.GET("/swagger/*", echoSwagger.WrapHandler) + + a.POST("/login", apiv1.Login) + + // ===== Routes with Authetification ===== + // Authetification + a.Use(middleware.JWT([]byte(viper.GetString("service.JWTSecret")))) + + // Put the authprovider in the context to be able to use it later + e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + c.Set("AuthProvider", &web.Auths{ + AuthObject: func(echo.Context) (web.Auth, error) { + return models.GetCurrentUser(c) + }, + }) + c.Set("LoggingProvider", log.Log) + return next(c) + } + }) + + a.POST("/tokenTest", apiv1.CheckToken) + + // User stuff + a.GET("/user", apiv1.UserShow) +}