├── bin └── .gitkeep ├── docs ├── .gitkeep └── httpie.txt ├── api ├── dbs │ ├── .gitkeep │ ├── logger.go │ ├── dispatch.go │ └── mongodb.go ├── config │ └── .gitkeep ├── models │ ├── .gitkeep │ ├── user.go │ ├── jwt.go │ ├── project.go │ └── permission.go ├── routes │ ├── .gitkeep │ ├── public.go │ ├── main.go │ └── protected.go ├── services │ ├── .gitkeep │ ├── user.go │ └── jwtauth.go ├── shared │ ├── .gitkeep │ ├── split.go │ ├── pathproject.go │ └── bcrypt.go ├── controllers │ ├── .gitkeep │ ├── admin.go │ ├── auth.go │ ├── permission.go │ ├── user.go │ └── project.go ├── middlewares │ ├── .gitkeep │ ├── loggerrequest.go │ ├── mongodb.go │ ├── project.go │ ├── token.go │ └── permission.go └── server.go ├── scripts └── .gitkeep ├── .gitignore ├── token.txt ├── glide.yaml ├── glide.lock └── README.md /bin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/dbs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/config/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/services/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/shared/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/middlewares/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | 3 | bin/* 4 | !bin/.gitkeep 5 | 6 | scripts/* 7 | !scripts/.gitkeep 8 | 9 | .env 10 | -------------------------------------------------------------------------------- /token.txt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiNThhYWRmMmNlM2JkZWEyZTAwZjk1NjNiIiwiYWRtaW4iOnRydWUsImV4cCI6MTQ4NzcwMjAwNCwiaXNzIjoibG9jYWxob3N0OjMzMzMifQ.Uk1n5gzXwLU5b5xc2gNcf0O9N_bbiJyTWGkiQOvCfgc -------------------------------------------------------------------------------- /api/shared/split.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // GetRootEndpoint return a root of endpoint 8 | func GetRootEndpoint(endpoint string) string { 9 | s := strings.Split(endpoint, "/") 10 | return s[1] 11 | } 12 | -------------------------------------------------------------------------------- /api/shared/pathproject.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | //GetPath return a path of binary 10 | func GetPath() string { 11 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | return dir 16 | } 17 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: . 2 | import: 3 | - package: github.com/dgrijalva/jwt-go 4 | version: ~3.0.0 5 | - package: github.com/pressly/chi 6 | version: ~2.0.0 7 | subpackages: 8 | - middleware 9 | - package: github.com/rs/cors 10 | version: ~1.0.0 11 | - package: gopkg.in/mgo.v2 12 | subpackages: 13 | - bson 14 | -------------------------------------------------------------------------------- /api/dbs/logger.go: -------------------------------------------------------------------------------- 1 | package dbs 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Sirupsen/logrus" 7 | "github.com/weekface/mgorus" 8 | ) 9 | 10 | //Logger hook 11 | func Logger() *logrus.Logger { 12 | logger := logrus.New() 13 | hooker, err := mgorus.NewHooker("localhost:27017", "login", "logs") 14 | if err == nil { 15 | logger.Hooks.Add(hooker) 16 | } else { 17 | fmt.Print(err) 18 | } 19 | 20 | return logger 21 | } 22 | -------------------------------------------------------------------------------- /api/shared/bcrypt.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import "golang.org/x/crypto/bcrypt" 4 | 5 | // Encrypt plain text with bcrypt. 6 | func Encrypt(source string) (string, error) { 7 | hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) 8 | return string(hashedBytes), err 9 | } 10 | 11 | // Compare the encrypted text with the plain text. 12 | func Compare(hashedPassword, password string) error { 13 | return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) 14 | } 15 | -------------------------------------------------------------------------------- /api/models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | // User model 10 | type User struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | Email string `json:"email" bson:"email"` 13 | Password string `json:"password" bson:"password"` 14 | Admin bool `json:"admin" bson:"admin"` 15 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 16 | UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` 17 | } 18 | -------------------------------------------------------------------------------- /api/controllers/admin.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | model "../models" 8 | ) 9 | 10 | //Admin GET teste 11 | func Admin() http.HandlerFunc { 12 | return func(w http.ResponseWriter, r *http.Request) { 13 | claims, ok := r.Context().Value(model.JwtKey).(model.Claims) 14 | if !ok { 15 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 16 | w.WriteHeader(500) 17 | fmt.Fprintf(w, `{"message":"Error on decode Context JWT"}`) 18 | return 19 | } 20 | w.Write([]byte(fmt.Sprintf("protected area. USER ID = %v", claims.UserID))) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /api/dbs/dispatch.go: -------------------------------------------------------------------------------- 1 | package dbs 2 | 3 | import ( 4 | "github.com/Sirupsen/logrus" 5 | "gopkg.in/mgo.v2" 6 | ) 7 | 8 | //Dispatch choose a db session 9 | // add new dispath of other database just put here 10 | // the session. 11 | type Dispatch struct { 12 | MongoDB *mgo.Session 13 | Logger *logrus.Logger 14 | } 15 | 16 | //StartDispatch load up connections 17 | func StartDispatch() *Dispatch { 18 | //add session of mongodb 19 | mongosession := StartMongoDB("Dispatch Service").Session 20 | // add logger for dispatch 21 | logger := Logger() 22 | 23 | return &Dispatch{MongoDB: mongosession, Logger: logger} 24 | 25 | } 26 | -------------------------------------------------------------------------------- /api/models/jwt.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import jwt "github.com/dgrijalva/jwt-go" 4 | 5 | //Claims jwt 6 | type Claims struct { 7 | UserID string `json:"user_id"` 8 | Admin bool `json:"admin"` 9 | // recommended having 10 | jwt.StandardClaims 11 | } 12 | 13 | //TokenEndExpire return a token and expire timestamp 14 | type TokenEndExpire struct { 15 | Token string 16 | Expire string 17 | } 18 | 19 | // Key model for JWT authorization 20 | type Key int 21 | 22 | //MyKey jwt handdler 23 | const ( 24 | JwtKey Key = 100000 25 | DbKey Key = 200000 26 | UserKey Key = 300000 27 | ProjKey Key = 400000 28 | SecretKey = "My-temporary-SECRETKey-2017" 29 | ) 30 | -------------------------------------------------------------------------------- /api/models/project.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | //Project model 10 | type Project struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | Label string `json:"label" bson:"label"` 13 | Slug string `json:"slug" bson:"slug"` 14 | Description string `json:"description" bson:"description"` 15 | Owner bson.ObjectId `json:"owner" bson:"owner"` 16 | Users []bson.ObjectId `json:"users" bson:"users"` 17 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 18 | UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` 19 | } 20 | -------------------------------------------------------------------------------- /api/models/permission.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | //Permission model 10 | type Permission struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | EndPoint string `json:"endpoint" bson:"endpoint"` 13 | Permission []permAction `json:"permissions" bson:"permissions"` 14 | Owner bson.ObjectId `json:"owner" bson:"owner"` 15 | Project bson.ObjectId `json:"project" bson:"project"` 16 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 17 | UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` 18 | } 19 | 20 | type permAction struct { 21 | Method string `json:"method" bson:"method"` 22 | Value int `json:"value" bson:"value"` 23 | } 24 | -------------------------------------------------------------------------------- /api/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | 10 | "log" 11 | 12 | db "./dbs" 13 | route "./routes" 14 | lib "./shared" 15 | ) 16 | 17 | func init() { 18 | // load config file 19 | if err := godotenv.Load(); err != nil { 20 | log.Printf("Error loading .env file! Try get a path...") 21 | if err2 := godotenv.Load(lib.GetPath() + "/.env"); err2 != nil { 22 | log.Printf("Fail...") 23 | os.Exit(1) 24 | } 25 | } 26 | } 27 | 28 | func main() { 29 | sessions := db.StartDispatch() 30 | addr := os.Getenv("API_URL") 31 | 32 | log.Printf("[Server] Path: %s", lib.GetPath()) 33 | fmt.Printf("[Server] Starting server on %v\n", addr) 34 | http.ListenAndServe(addr, route.Router(sessions)) 35 | } 36 | -------------------------------------------------------------------------------- /api/routes/public.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/pressly/chi" 5 | "github.com/pressly/chi/middleware" 6 | "github.com/rs/cors" 7 | 8 | controller "../controllers" 9 | db "../dbs" 10 | mid "../middlewares" 11 | ) 12 | 13 | //Public Routes 14 | func Public(s *db.Dispatch, cors *cors.Cors) func(r chi.Router) { 15 | return func(r chi.Router) { 16 | r.Use(middleware.DefaultCompress) 17 | r.Use(middleware.RequestID) 18 | r.Use(middleware.Logger) 19 | r.Use(middleware.Recoverer) 20 | r.Use(cors.Handler) 21 | r.Use(mid.LoggerRequest) 22 | 23 | // home 24 | r.Get("/", controller.Home()) 25 | 26 | // Authenticate user 27 | r.Post("/auth", controller.Auth(s)) 28 | 29 | //CRUD User 30 | r.Post("/user", controller.CreateUser(s)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /api/routes/main.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/pressly/chi" 7 | "github.com/rs/cors" 8 | 9 | db "../dbs" 10 | ) 11 | 12 | //Router main rules of routes 13 | func Router(s *db.Dispatch) http.Handler { 14 | r := chi.NewRouter() 15 | 16 | //CORS setup 17 | cors := cors.New(cors.Options{ 18 | AllowedOrigins: []string{"*"}, 19 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, 20 | AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, 21 | ExposedHeaders: []string{"Link"}, 22 | AllowCredentials: true, 23 | MaxAge: 300, 24 | }) 25 | 26 | // Protected routes 27 | r.Group(Protected(s, cors)) 28 | // Public routes 29 | r.Group(Public(s, cors)) 30 | 31 | return r 32 | } 33 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: 09a55266993115e0aa9d91aad2c6a212f84085d342f5f36df3a7aaaccc7353a4 2 | updated: 2017-02-16T11:08:45.916052474-02:00 3 | imports: 4 | - name: github.com/dgrijalva/jwt-go 5 | version: d2709f9f1f31ebcda9651b03077758c1f3a0018c 6 | - name: github.com/pressly/chi 7 | version: 54f435d539226571eab1987ed862b1c0fdfdc892 8 | subpackages: 9 | - middleware 10 | - name: github.com/rs/cors 11 | version: a62a804a8a009876ca59105f7899938a1349f4b3 12 | - name: github.com/rs/xhandler 13 | version: ed27b6fd65218132ee50cd95f38474a3d8a2cd12 14 | - name: golang.org/x/net 15 | version: b4690f45fa1cafc47b1c280c2e75116efe40cc13 16 | subpackages: 17 | - context 18 | - name: gopkg.in/mgo.v2 19 | version: 3f83fa5005286a7fe593b055f0d7771a7dce4655 20 | subpackages: 21 | - bson 22 | - internal/json 23 | testImports: [] 24 | -------------------------------------------------------------------------------- /api/middlewares/loggerrequest.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/Sirupsen/logrus" 8 | 9 | db "../dbs" 10 | model "../models" 11 | ) 12 | 13 | var logger *logrus.Logger 14 | 15 | func init() { 16 | log.Printf("[LoggerRequest] loaded!") 17 | logger = db.Logger() 18 | } 19 | 20 | // LoggerRequest middleware for logger all request maded 21 | func LoggerRequest(next http.Handler) http.Handler { 22 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 | 24 | claims, ok := r.Context().Value(model.JwtKey).(model.Claims) 25 | if !ok { 26 | claims.UserID = "Unknow" 27 | } 28 | 29 | logger.WithFields(logrus.Fields{ 30 | "user_id": claims.UserID, 31 | "method": r.Method, 32 | "endpoint": r.URL.RequestURI(), 33 | }).Info("LoggerRequest") 34 | 35 | next.ServeHTTP(w, r) 36 | 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /api/dbs/mongodb.go: -------------------------------------------------------------------------------- 1 | package dbs 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "os" 8 | 9 | mgo "gopkg.in/mgo.v2" 10 | ) 11 | 12 | //MgoSession and session 13 | type MgoSession struct { 14 | Session *mgo.Session 15 | } 16 | 17 | func newMgoSession(s *mgo.Session) *MgoSession { 18 | return &MgoSession{s} 19 | } 20 | 21 | //StartMongoDB initialize session on mongodb 22 | func StartMongoDB(msg string) *MgoSession { 23 | 24 | mongoDBDialInfo := &mgo.DialInfo{ 25 | Addrs: []string{os.Getenv("MONGODB_URL")}, 26 | Timeout: 60 * time.Second, 27 | //Database: "", 28 | //Username: "", 29 | //Password: "", 30 | } 31 | 32 | mongoSession, err := mgo.DialWithInfo(mongoDBDialInfo) 33 | if err != nil { 34 | log.Fatalf("[MongoDB] CreateSession: %s\n", err) 35 | } 36 | mongoSession.SetMode(mgo.Monotonic, true) 37 | 38 | log.Printf("[MongoDB] connected! %s", msg) 39 | return newMgoSession(mongoSession) 40 | } 41 | -------------------------------------------------------------------------------- /api/middlewares/mongodb.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | model "../models" 10 | mgo "gopkg.in/mgo.v2" 11 | ) 12 | 13 | // MongoMiddleware adds mgo MongoDB to context 14 | func MongoMiddleware(next http.Handler) http.Handler { 15 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 | 17 | log.Printf("MongoDB on request!") 18 | 19 | mongoDBDialInfo := &mgo.DialInfo{ 20 | Addrs: []string{"localhost:27017"}, 21 | Timeout: 60 * time.Second, 22 | //Database: "", 23 | //Username: "", 24 | //Password: "", 25 | } 26 | 27 | mongoSession, err := mgo.DialWithInfo(mongoDBDialInfo) 28 | if err != nil { 29 | log.Fatalf("[MongoDB] CreateSession: %s\n", err) 30 | } 31 | mongoSession.SetMode(mgo.Monotonic, true) 32 | 33 | rs := mongoSession.Clone() 34 | defer rs.Close() 35 | 36 | db := rs.DB("login") 37 | ctx := context.WithValue(r.Context(), model.DbKey, db) 38 | 39 | next.ServeHTTP(w, r.WithContext(ctx)) 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /docs/httpie.txt: -------------------------------------------------------------------------------- 1 | Consult protected endpoint with token 2 | ------------------------------------- 3 | http --verbose --auth-type=jwt --auth=$(cat token.txt): get http://localhost:3333/user/58aadf2ce3bdea2e00f9563b 4 | 5 | 6 | Login and Generate Token 7 | ------------------------ 8 | http --verbose post http://localhost:3333/auth email=tzilli@inviron.com.br password=1233 9 | 10 | 11 | Create a user set admin 12 | ------------------------ 13 | http --verbose post http://localhost:3333/user email=tzilli@inviron.com.br password=1233 admin:=true 14 | 15 | Create a project 16 | ------------------------------------ 17 | http --verbose --auth-type=jwt --auth=$(cat token.txt): post http://localhost:3333/project \ 18 | owner="58aadf2ce3bdea2e00f9563b" \ 19 | users:='["58aadf2ce3bdea2e00f9563b"]' \ 20 | slug="test-project" \ 21 | label="Test Project" \ 22 | description="Test Project crud" 23 | 24 | 25 | Create a user permission by endpoint 26 | ------------------------------------ 27 | http --verbose --auth-type=jwt --auth=$(cat token.txt): post http://localhost:3333/permission \ 28 | owner="58aadf2ce3bdea2e00f9563b" \ 29 | project="58ab1f5d846dd72723990def" \ 30 | endpoint="project" \ 31 | permissions:='[{"method":"ALL", "value":16}]' 32 | 33 | 34 | -------------------------------------------------------------------------------- /api/controllers/auth.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | 9 | db "../dbs" 10 | model "../models" 11 | service "../services" 12 | ) 13 | 14 | //Home a home API 15 | func Home() http.HandlerFunc { 16 | return func(w http.ResponseWriter, r *http.Request) { 17 | w.Write([]byte("Public routes!")) 18 | } 19 | } 20 | 21 | //Auth get a valid token and expire 22 | func Auth(s *db.Dispatch) http.HandlerFunc { 23 | return func(w http.ResponseWriter, r *http.Request) { 24 | 25 | var user model.User 26 | decoder := json.NewDecoder(r.Body) 27 | errDecoder := decoder.Decode(&user) 28 | 29 | if errDecoder != nil { 30 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 31 | w.WriteHeader(http.StatusForbidden) 32 | fmt.Fprintf(w, `{"message":"Incorrect Decode JSON on body"}`) 33 | return 34 | } 35 | 36 | t, err := service.GenerateToken(s, user) 37 | if err != nil { 38 | log.Printf("Error : %q", err) 39 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 40 | w.WriteHeader(http.StatusForbidden) 41 | fmt.Fprintf(w, `{"message": %q}`, err) 42 | return 43 | } 44 | 45 | //write json 46 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 47 | w.WriteHeader(http.StatusCreated) 48 | fmt.Fprintf(w, `{"token":%q, "expire":%s}`, t.Token, t.Expire) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /api/middlewares/project.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | 9 | "github.com/pressly/chi" 10 | 11 | model "../models" 12 | service "../services" 13 | ) 14 | 15 | // UserValidOnProject middleware for validate permission of user 16 | func UserValidOnProject(next http.Handler) http.Handler { 17 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 18 | 19 | slug := chi.URLParam(r, "slug") 20 | 21 | if slug == "" { 22 | log.Printf("[UserValidOnProject] Slug empty!") 23 | w.WriteHeader(http.StatusUnauthorized) 24 | return 25 | } 26 | 27 | claims, ok := r.Context().Value(model.JwtKey).(model.Claims) 28 | if !ok { 29 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 30 | w.WriteHeader(500) 31 | fmt.Fprintf(w, `{"message":"Error on decode Context JWT - middleware UserValidOnProject "}`) 32 | return 33 | } 34 | 35 | log.Printf("[UserValidOnProject] method=%s EndPoint=%s SLUG=%s", r.Method, r.URL.RequestURI(), slug) 36 | 37 | project, err := service.UserIsValidOnProject(slug, claims.UserID) 38 | if err != nil { 39 | log.Printf("[UserValidOnProject] Err: %s", err) 40 | w.WriteHeader(http.StatusUnauthorized) 41 | return 42 | } 43 | 44 | ctx := context.WithValue(r.Context(), model.ProjKey, project.ID.Hex()) 45 | next.ServeHTTP(w, r.WithContext(ctx)) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /api/services/user.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "gopkg.in/mgo.v2/bson" 5 | 6 | "fmt" 7 | 8 | db "../dbs" 9 | model "../models" 10 | lib "../shared" 11 | ) 12 | 13 | var session = db.StartMongoDB("Middleware / User Service").Session 14 | 15 | //UserIsValidOnProject seek the user on the project profile 16 | func UserIsValidOnProject(slug string, userID string) (model.Project, error) { 17 | 18 | ss := session.Copy() 19 | defer ss.Close() 20 | 21 | oid := bson.ObjectIdHex(userID) 22 | 23 | //find user 24 | u := model.Project{} 25 | if err := ss.DB("login").C("projects").Find(bson.M{"slug": slug, "users": oid}).One(&u); err != nil { 26 | return u, fmt.Errorf("User not valid on project") 27 | } 28 | 29 | return u, nil 30 | } 31 | 32 | // UserGetPermissions return a permisson of user by project and endpoint 33 | func UserGetPermissions(userID string, projectID string, endpoint string) (model.Permission, error) { 34 | 35 | ss := session.Copy() 36 | defer ss.Close() 37 | 38 | //change objectId 39 | endp := lib.GetRootEndpoint(endpoint) 40 | oid := bson.ObjectIdHex(userID) 41 | oidp := bson.ObjectIdHex(projectID) 42 | 43 | //find user 44 | u := model.Permission{} 45 | if err := ss.DB("login").C("permissions").Find(bson.M{"owner": oid, "project": oidp, "endpoint": endp}).One(&u); err != nil { 46 | return u, fmt.Errorf("Permission not found") 47 | } 48 | 49 | return u, nil 50 | } 51 | -------------------------------------------------------------------------------- /api/services/jwtauth.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "gopkg.in/mgo.v2/bson" 8 | 9 | jwt "github.com/dgrijalva/jwt-go" 10 | 11 | db "../dbs" 12 | model "../models" 13 | lib "../shared" 14 | ) 15 | 16 | //GenerateToken for a valid user 17 | func GenerateToken(s *db.Dispatch, user model.User) (model.TokenEndExpire, error) { 18 | 19 | ss := s.MongoDB.Copy() 20 | defer ss.Close() 21 | 22 | // moc to return 23 | var te model.TokenEndExpire 24 | // expire unix 25 | var expir = time.Now().Add(time.Minute * 60).Unix() 26 | 27 | //find user 28 | u := model.User{} 29 | if err := ss.DB("login").C("users").Find(bson.M{"email": user.Email}).One(&u); err != nil { 30 | return te, fmt.Errorf("Password not match or user not found") 31 | } 32 | 33 | if errPwd := lib.Compare(u.Password, user.Password); errPwd != nil { 34 | return te, fmt.Errorf("Invalid Password") 35 | } 36 | 37 | //build claims 38 | claims := model.Claims{ 39 | u.ID.Hex(), 40 | u.Admin, 41 | jwt.StandardClaims{ 42 | ExpiresAt: expir, 43 | Issuer: "localhost:3333", 44 | }, 45 | } 46 | 47 | // create token & sign token 48 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 49 | t, err := token.SignedString([]byte(model.SecretKey)) 50 | if err != nil { 51 | return te, fmt.Errorf("%q", err) 52 | } 53 | 54 | te.Token = t 55 | te.Expire = fmt.Sprintf("%q", time.Unix(expir, 0)) 56 | 57 | return te, nil 58 | } 59 | -------------------------------------------------------------------------------- /api/routes/protected.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/pressly/chi" 5 | "github.com/pressly/chi/middleware" 6 | "github.com/rs/cors" 7 | 8 | controller "../controllers" 9 | db "../dbs" 10 | mid "../middlewares" 11 | ) 12 | 13 | //Protected Routes 14 | func Protected(s *db.Dispatch, cors *cors.Cors) func(r chi.Router) { 15 | return func(r chi.Router) { 16 | r.Use(middleware.DefaultCompress) 17 | r.Use(middleware.RequestID) 18 | r.Use(middleware.Logger) 19 | r.Use(middleware.Recoverer) 20 | r.Use(cors.Handler) 21 | //Chain of validation user 22 | r.Use(mid.TokenAuthentication) //If token ok 23 | r.Use(mid.UserValidOnProject) //if user belong to project ok 24 | r.Use(mid.UserHavePermission) //if user has permisson on endpoint ok 25 | r.Use(mid.LoggerRequest) //log any request ok 26 | 27 | //endpoint protected 28 | r.Get("/admin/:slug", controller.Admin()) 29 | 30 | //CRUD User 31 | r.Get("/user/:slug/:id", controller.GetUser(s)) 32 | r.Put("/user/:slug/:id", controller.UpdateUser(s)) 33 | r.Delete("/user/:slug/:id", controller.GetUser(s)) 34 | 35 | //CRUD Permission 36 | r.Post("/permission", controller.CreatePermission(s)) 37 | r.Get("/permission/:id", controller.GetPermission(s)) 38 | r.Put("/permission/:id", controller.UpdatePermission(s)) 39 | r.Delete("/permission/:id", controller.DeletePermission(s)) 40 | 41 | //CRUD Project 42 | r.Post("/project", controller.CreateProject(s)) 43 | r.Get("/project/:id", controller.GetProject(s)) 44 | r.Put("/project/:id", controller.UpdateProject(s)) 45 | r.Delete("/project/:id", controller.DeleteProject(s)) 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/middlewares/token.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "strings" 8 | 9 | jwt "github.com/dgrijalva/jwt-go" 10 | 11 | mid "../models" 12 | ) 13 | 14 | // TokenAuthentication middleware for vaidation token 15 | func TokenAuthentication(next http.Handler) http.Handler { 16 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | 18 | var tokenStr string 19 | 20 | // Get token from query params 21 | tokenStr = r.URL.Query().Get("jwt") 22 | 23 | // Get token from authorization header 24 | if tokenStr == "" { 25 | bearer := r.Header.Get("Authorization") 26 | if len(bearer) > 7 && strings.ToUpper(bearer[0:6]) == "BEARER" { 27 | tokenStr = bearer[7:] 28 | } 29 | } 30 | 31 | // Get token from cookie 32 | if tokenStr == "" { 33 | cookie, err := r.Cookie("jwt") 34 | if err == nil { 35 | tokenStr = cookie.Value 36 | } 37 | } 38 | 39 | //if token not recovered on the last 3 steps 40 | if tokenStr == "" { 41 | log.Printf("[RequireTokenAuthentication] Not have Token to validate") 42 | w.WriteHeader(http.StatusUnauthorized) 43 | return 44 | } 45 | 46 | token, err := jwt.ParseWithClaims(tokenStr, &mid.Claims{}, func(token *jwt.Token) (interface{}, error) { 47 | return []byte(mid.SecretKey), nil 48 | }) 49 | 50 | if claims, ok := token.Claims.(*mid.Claims); ok && token.Valid { 51 | log.Printf("[RequireTokenAuthentication] Token valid! Go forward") 52 | ctx := context.WithValue(r.Context(), mid.JwtKey, *claims) 53 | next.ServeHTTP(w, r.WithContext(ctx)) 54 | 55 | } else if ve, ok := err.(*jwt.ValidationError); ok { 56 | 57 | if ve.Errors&jwt.ValidationErrorMalformed != 0 { 58 | log.Printf("[RequireTokenAuthentication] Thats not even token") 59 | } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { 60 | log.Printf("[RequireTokenAuthentication] Token is expired or not active") 61 | } else { 62 | log.Printf("[RequireTokenAuthentication] Couldn't handle token: %q", err) 63 | } 64 | 65 | } else { 66 | log.Printf("[RequireTokenAuthentication] Couldn't handle token: %q", err) 67 | } 68 | 69 | w.WriteHeader(http.StatusUnauthorized) 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /api/middlewares/permission.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "strings" 8 | 9 | model "../models" 10 | service "../services" 11 | ) 12 | 13 | //Flags for biwise 14 | const ( 15 | FLAGREAD int = 1 // 000001 16 | FLAGWRITE int = 2 // 000010 17 | FLAGUPDATE int = 4 // 000100 18 | FLAGDELETE int = 8 // 001000 19 | OWNER int = 16 // 010000 20 | ADMIN int = 32 // 100000 21 | ) 22 | 23 | // CheckFlags, true for correspond action 24 | func checkFlags(route string, perms int) bool { 25 | var flag = false 26 | route = strings.ToUpper(route) 27 | 28 | switch route { 29 | case "GET": 30 | 31 | if (perms & FLAGREAD) > 0 { 32 | flag = true 33 | } else if (perms & OWNER) > 0 { 34 | flag = true 35 | } 36 | break 37 | case "POST": 38 | 39 | if (perms & FLAGWRITE) > 0 { 40 | flag = true 41 | } else if (perms & OWNER) > 0 { 42 | flag = true 43 | } 44 | 45 | break 46 | case "UPDATE": 47 | 48 | if (perms & FLAGUPDATE) > 0 { 49 | flag = true 50 | } else if (perms & OWNER) > 0 { 51 | flag = true 52 | } 53 | 54 | break 55 | case "DELETE": 56 | 57 | if (perms & FLAGDELETE) > 0 { 58 | flag = true 59 | } else if (perms & OWNER) > 0 { 60 | flag = true 61 | } 62 | 63 | break 64 | case "OWNER": 65 | if (perms & OWNER) > 0 { 66 | flag = true 67 | } 68 | 69 | break 70 | case "ADMIN": 71 | if (perms & ADMIN) > 0 { 72 | flag = true 73 | } 74 | 75 | break 76 | } 77 | 78 | return flag 79 | } 80 | 81 | // CheckUserPermission, get a method used and endpoint and check 82 | // if user have permissions granted 83 | func checkUserPermisson(action string, endpoint string, projectID string, claims model.Claims) bool { 84 | 85 | log.Printf("[UserHavePermission] UserID = %q", claims.UserID) 86 | log.Printf("[UserHavePermission] ProjectID = %q", projectID) 87 | 88 | perm, perr := service.UserGetPermissions(claims.UserID, projectID, endpoint) 89 | if perr != nil { 90 | log.Printf("[UserHavePermission] Err: %s", perr) 91 | return false 92 | } 93 | 94 | log.Printf("[UserHavePermission] = %+v \n", perm) 95 | 96 | //interate each permission and sum, bit each bit 97 | var perms = 0 98 | for _, vv := range perm.Permission { 99 | perms |= vv.Value 100 | } 101 | 102 | //check if return something and the flags are ok 103 | if perms > 0 && checkFlags(action, perms) { 104 | log.Printf("[UserHavePermission] User Authorized") 105 | return true 106 | } 107 | 108 | log.Printf("[UserHavePermission] User Unauthorized") 109 | return false 110 | } 111 | 112 | // UserHavePermission middleware for validate permission of user 113 | func UserHavePermission(next http.Handler) http.Handler { 114 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 115 | 116 | claims, ok := r.Context().Value(model.JwtKey).(model.Claims) 117 | if !ok { 118 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 119 | w.WriteHeader(500) 120 | fmt.Fprintf(w, `{"message":"Error on decode Context JWT"}`) 121 | return 122 | } 123 | 124 | projectID, ok := r.Context().Value(model.ProjKey).(string) 125 | if !ok { 126 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 127 | w.WriteHeader(500) 128 | fmt.Fprintf(w, `{"message":"Error on decode Context ProjectID"}`) 129 | return 130 | } 131 | 132 | // Check in future 133 | // mgos, ok := r.Context().Value(model.DbKey).(*mgo.Database) 134 | // if !ok { 135 | // w.Header().Set("Content-Type", "application/json; charset=utf-8") 136 | // w.WriteHeader(500) 137 | // fmt.Fprintf(w, `{"message":"Error on decode Session MongoDB"}`) 138 | // return 139 | // } 140 | 141 | log.Printf("[UserHavePermission] method=%s EndPoint=%s", r.Method, r.URL.RequestURI()) 142 | 143 | if checkUserPermisson(r.Method, r.URL.RequestURI(), projectID, claims) { 144 | next.ServeHTTP(w, r) 145 | } 146 | 147 | w.WriteHeader(http.StatusUnauthorized) 148 | return 149 | }) 150 | } 151 | -------------------------------------------------------------------------------- /api/controllers/permission.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/pressly/chi" 10 | mgo "gopkg.in/mgo.v2" 11 | "gopkg.in/mgo.v2/bson" 12 | 13 | db "../dbs" 14 | model "../models" 15 | ) 16 | 17 | //GetPermission get a Permission by Id 18 | func GetPermission(s *db.Dispatch) http.HandlerFunc { 19 | return func(w http.ResponseWriter, r *http.Request) { 20 | 21 | ss := s.MongoDB.Copy() 22 | defer ss.Close() 23 | 24 | // Grab id 25 | id := chi.URLParam(r, "id") 26 | 27 | // Verify id is ObjectId, otherwise bail 28 | if !bson.IsObjectIdHex(id) { 29 | w.WriteHeader(http.StatusNotFound) 30 | return 31 | } 32 | 33 | oid := bson.ObjectIdHex(id) 34 | u := model.Permission{} 35 | if err := ss.DB("login").C("permissions").FindId(oid).One(&u); err != nil { 36 | w.WriteHeader(http.StatusNotFound) 37 | return 38 | } 39 | 40 | uj, _ := json.Marshal(u) 41 | w.Header().Set("Content-Type", "application/json") 42 | w.WriteHeader(http.StatusOK) 43 | fmt.Fprintf(w, "%s", uj) 44 | } 45 | } 46 | 47 | //CreatePermission create a new Permission 48 | func CreatePermission(s *db.Dispatch) http.HandlerFunc { 49 | return func(w http.ResponseWriter, r *http.Request) { 50 | 51 | ss := s.MongoDB.Copy() 52 | defer ss.Close() 53 | 54 | // Stub an Permission to be populated from the body 55 | u := model.Permission{} 56 | json.NewDecoder(r.Body).Decode(&u) 57 | 58 | // Add an Id 59 | u.ID = bson.NewObjectId() 60 | u.CreatedAt = time.Now() 61 | u.UpdatedAt = time.Now() 62 | 63 | ss.DB("login").C("permissions").Insert(u) 64 | uj, _ := json.Marshal(u) 65 | 66 | w.Header().Set("Content-Type", "application/json") 67 | w.WriteHeader(http.StatusCreated) 68 | fmt.Fprintf(w, "%s", uj) 69 | } 70 | } 71 | 72 | // DeletePermission remove Permission from database 73 | func DeletePermission(s *db.Dispatch) http.HandlerFunc { 74 | return func(w http.ResponseWriter, r *http.Request) { 75 | 76 | ss := s.MongoDB.Copy() 77 | defer ss.Close() 78 | 79 | id := chi.URLParam(r, "id") 80 | 81 | if !bson.IsObjectIdHex(id) { 82 | msg := []byte(`{"message":"ObjectId invalid"}`) 83 | w.Header().Set("Content-Type", "application/json") 84 | w.WriteHeader(http.StatusNotFound) 85 | fmt.Fprintf(w, "%s", msg) 86 | return 87 | } 88 | 89 | c := ss.DB("login").C("permissions") 90 | 91 | if err := c.Remove(bson.M{"_id": bson.ObjectIdHex(id)}); err != nil { 92 | switch err { 93 | default: 94 | msg := []byte(`{"message":"ObjectId invalid"}`) 95 | w.Header().Set("Content-Type", "application/json") 96 | w.WriteHeader(http.StatusNotFound) 97 | fmt.Fprintf(w, "%s", msg) 98 | 99 | case mgo.ErrNotFound: 100 | msg := []byte(`{"message":"ObjectId not found"}`) 101 | w.Header().Set("Content-Type", "application/json") 102 | w.WriteHeader(http.StatusNotFound) 103 | fmt.Fprintf(w, "%s", msg) 104 | } 105 | return 106 | } 107 | 108 | w.WriteHeader(http.StatusNoContent) 109 | } 110 | } 111 | 112 | // UpdatePermission update Permission 113 | func UpdatePermission(s *db.Dispatch) http.HandlerFunc { 114 | return func(w http.ResponseWriter, r *http.Request) { 115 | ss := s.MongoDB.Copy() 116 | defer ss.Close() 117 | 118 | id := chi.URLParam(r, "id") 119 | 120 | // Verify id is ObjectId, otherwise bail 121 | if !bson.IsObjectIdHex(id) { 122 | msg := []byte(`{"message":"ObjectId invalid"}`) 123 | w.Header().Set("Content-Type", "application/json") 124 | w.WriteHeader(http.StatusNotFound) 125 | fmt.Fprintf(w, "%s", msg) 126 | return 127 | } 128 | 129 | // Stub an Permission to be populated from the body 130 | u := model.Permission{} 131 | json.NewDecoder(r.Body).Decode(&u) 132 | u.UpdatedAt = time.Now() 133 | 134 | c := ss.DB("login").C("permissions") 135 | 136 | if err := c.Update(bson.M{"_id": bson.ObjectIdHex(id)}, &u); err != nil { 137 | switch err { 138 | default: 139 | msg := []byte(`{"message":"ObjectId invalid"}`) 140 | w.Header().Set("Content-Type", "application/json") 141 | w.WriteHeader(http.StatusNotFound) 142 | fmt.Fprintf(w, "%s", msg) 143 | 144 | case mgo.ErrNotFound: 145 | msg := []byte(`{"message":"ObjectId not found"}`) 146 | w.Header().Set("Content-Type", "application/json") 147 | w.WriteHeader(http.StatusNotFound) 148 | fmt.Fprintf(w, "%s", msg) 149 | } 150 | return 151 | } 152 | 153 | w.WriteHeader(http.StatusOK) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /api/controllers/user.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/pressly/chi" 10 | mgo "gopkg.in/mgo.v2" 11 | "gopkg.in/mgo.v2/bson" 12 | 13 | db "../dbs" 14 | model "../models" 15 | lib "../shared" 16 | ) 17 | 18 | //GetUser get a user by Id 19 | func GetUser(s *db.Dispatch) http.HandlerFunc { 20 | return func(w http.ResponseWriter, r *http.Request) { 21 | 22 | ss := s.MongoDB.Copy() 23 | defer ss.Close() 24 | 25 | // Grab id 26 | id := chi.URLParam(r, "id") 27 | 28 | // Verify id is ObjectId, otherwise bail 29 | if !bson.IsObjectIdHex(id) { 30 | w.WriteHeader(http.StatusNotFound) 31 | return 32 | } 33 | 34 | oid := bson.ObjectIdHex(id) 35 | u := model.User{} 36 | if err := ss.DB("login").C("users").FindId(oid).One(&u); err != nil { 37 | w.WriteHeader(http.StatusNotFound) 38 | return 39 | } 40 | 41 | uj, _ := json.Marshal(u) 42 | w.Header().Set("Content-Type", "application/json") 43 | w.WriteHeader(http.StatusOK) 44 | fmt.Fprintf(w, "%s", uj) 45 | } 46 | } 47 | 48 | //CreateUser create a new user 49 | func CreateUser(s *db.Dispatch) http.HandlerFunc { 50 | return func(w http.ResponseWriter, r *http.Request) { 51 | 52 | ss := s.MongoDB.Copy() 53 | defer ss.Close() 54 | 55 | // Stub an user to be populated from the body 56 | u := model.User{} 57 | json.NewDecoder(r.Body).Decode(&u) 58 | 59 | // Add an Id 60 | u.ID = bson.NewObjectId() 61 | u.CreatedAt = time.Now() 62 | u.UpdatedAt = time.Now() 63 | 64 | if passwd, err := lib.Encrypt(u.Password); err == nil { 65 | u.Password = passwd 66 | } 67 | 68 | ss.DB("login").C("users").Insert(u) 69 | uj, _ := json.Marshal(u) 70 | 71 | w.Header().Set("Content-Type", "application/json") 72 | w.WriteHeader(http.StatusCreated) 73 | fmt.Fprintf(w, "%s", uj) 74 | } 75 | } 76 | 77 | // DeleteUser remove user from database 78 | func DeleteUser(s *db.Dispatch) http.HandlerFunc { 79 | return func(w http.ResponseWriter, r *http.Request) { 80 | 81 | ss := s.MongoDB.Copy() 82 | defer ss.Close() 83 | 84 | id := chi.URLParam(r, "id") 85 | 86 | if !bson.IsObjectIdHex(id) { 87 | msg := []byte(`{"message":"ObjectId invalid"}`) 88 | w.Header().Set("Content-Type", "application/json") 89 | w.WriteHeader(http.StatusNotFound) 90 | fmt.Fprintf(w, "%s", msg) 91 | return 92 | } 93 | 94 | c := ss.DB("login").C("users") 95 | 96 | if err := c.Remove(bson.M{"_id": bson.ObjectIdHex(id)}); err != nil { 97 | switch err { 98 | default: 99 | msg := []byte(`{"message":"ObjectId invalid"}`) 100 | w.Header().Set("Content-Type", "application/json") 101 | w.WriteHeader(http.StatusNotFound) 102 | fmt.Fprintf(w, "%s", msg) 103 | 104 | case mgo.ErrNotFound: 105 | msg := []byte(`{"message":"ObjectId not found"}`) 106 | w.Header().Set("Content-Type", "application/json") 107 | w.WriteHeader(http.StatusNotFound) 108 | fmt.Fprintf(w, "%s", msg) 109 | } 110 | return 111 | } 112 | 113 | w.WriteHeader(http.StatusNoContent) 114 | } 115 | } 116 | 117 | // UpdateUser update user 118 | func UpdateUser(s *db.Dispatch) http.HandlerFunc { 119 | return func(w http.ResponseWriter, r *http.Request) { 120 | ss := s.MongoDB.Copy() 121 | defer ss.Close() 122 | 123 | id := chi.URLParam(r, "id") 124 | 125 | // Verify id is ObjectId, otherwise bail 126 | if !bson.IsObjectIdHex(id) { 127 | msg := []byte(`{"message":"ObjectId invalid"}`) 128 | w.Header().Set("Content-Type", "application/json") 129 | w.WriteHeader(http.StatusNotFound) 130 | fmt.Fprintf(w, "%s", msg) 131 | return 132 | } 133 | 134 | // Stub an user to be populated from the body 135 | u := model.User{} 136 | json.NewDecoder(r.Body).Decode(&u) 137 | u.UpdatedAt = time.Now() 138 | 139 | c := ss.DB("login").C("users") 140 | 141 | if err := c.Update(bson.M{"_id": bson.ObjectIdHex(id)}, &u); err != nil { 142 | switch err { 143 | default: 144 | msg := []byte(`{"message":"ObjectId invalid"}`) 145 | w.Header().Set("Content-Type", "application/json") 146 | w.WriteHeader(http.StatusNotFound) 147 | fmt.Fprintf(w, "%s", msg) 148 | 149 | case mgo.ErrNotFound: 150 | msg := []byte(`{"message":"ObjectId not found"}`) 151 | w.Header().Set("Content-Type", "application/json") 152 | w.WriteHeader(http.StatusNotFound) 153 | fmt.Fprintf(w, "%s", msg) 154 | } 155 | return 156 | } 157 | 158 | w.WriteHeader(http.StatusOK) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /api/controllers/project.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gosimple/slug" 10 | "github.com/pressly/chi" 11 | mgo "gopkg.in/mgo.v2" 12 | "gopkg.in/mgo.v2/bson" 13 | 14 | db "../dbs" 15 | model "../models" 16 | ) 17 | 18 | //GetProject get a Project by Id 19 | func GetProject(s *db.Dispatch) http.HandlerFunc { 20 | return func(w http.ResponseWriter, r *http.Request) { 21 | 22 | ss := s.MongoDB.Copy() 23 | defer ss.Close() 24 | 25 | // Grab id 26 | id := chi.URLParam(r, "id") 27 | 28 | // Verify id is ObjectId, otherwise bail 29 | if !bson.IsObjectIdHex(id) { 30 | w.WriteHeader(http.StatusNotFound) 31 | return 32 | } 33 | 34 | oid := bson.ObjectIdHex(id) 35 | u := model.Project{} 36 | if err := ss.DB("login").C("projects").FindId(oid).One(&u); err != nil { 37 | w.WriteHeader(http.StatusNotFound) 38 | return 39 | } 40 | 41 | uj, _ := json.Marshal(u) 42 | w.Header().Set("Content-Type", "application/json") 43 | w.WriteHeader(http.StatusOK) 44 | fmt.Fprintf(w, "%s", uj) 45 | } 46 | } 47 | 48 | //CreateProject create a new Project 49 | func CreateProject(s *db.Dispatch) http.HandlerFunc { 50 | return func(w http.ResponseWriter, r *http.Request) { 51 | 52 | ss := s.MongoDB.Copy() 53 | defer ss.Close() 54 | 55 | // Stub an Project to be populated from the body 56 | u := model.Project{} 57 | json.NewDecoder(r.Body).Decode(&u) 58 | 59 | // Add an Id 60 | u.ID = bson.NewObjectId() 61 | u.CreatedAt = time.Now() 62 | u.UpdatedAt = time.Now() 63 | u.Slug = slug.Make(u.Label) 64 | 65 | ss.DB("login").C("projects").Insert(u) 66 | uj, _ := json.Marshal(u) 67 | 68 | w.Header().Set("Content-Type", "application/json") 69 | w.WriteHeader(http.StatusCreated) 70 | fmt.Fprintf(w, "%s", uj) 71 | } 72 | } 73 | 74 | // DeleteProject remove Project from database 75 | func DeleteProject(s *db.Dispatch) http.HandlerFunc { 76 | return func(w http.ResponseWriter, r *http.Request) { 77 | 78 | ss := s.MongoDB.Copy() 79 | defer ss.Close() 80 | 81 | id := chi.URLParam(r, "id") 82 | 83 | if !bson.IsObjectIdHex(id) { 84 | msg := []byte(`{"message":"ObjectId invalid"}`) 85 | w.Header().Set("Content-Type", "application/json") 86 | w.WriteHeader(http.StatusNotFound) 87 | fmt.Fprintf(w, "%s", msg) 88 | return 89 | } 90 | 91 | c := ss.DB("login").C("projects") 92 | 93 | if err := c.Remove(bson.M{"_id": bson.ObjectIdHex(id)}); err != nil { 94 | switch err { 95 | default: 96 | msg := []byte(`{"message":"ObjectId invalid"}`) 97 | w.Header().Set("Content-Type", "application/json") 98 | w.WriteHeader(http.StatusNotFound) 99 | fmt.Fprintf(w, "%s", msg) 100 | 101 | case mgo.ErrNotFound: 102 | msg := []byte(`{"message":"ObjectId not found"}`) 103 | w.Header().Set("Content-Type", "application/json") 104 | w.WriteHeader(http.StatusNotFound) 105 | fmt.Fprintf(w, "%s", msg) 106 | } 107 | return 108 | } 109 | 110 | w.WriteHeader(http.StatusNoContent) 111 | } 112 | } 113 | 114 | // UpdateProject update Project 115 | func UpdateProject(s *db.Dispatch) http.HandlerFunc { 116 | return func(w http.ResponseWriter, r *http.Request) { 117 | ss := s.MongoDB.Copy() 118 | defer ss.Close() 119 | 120 | id := chi.URLParam(r, "id") 121 | 122 | // Verify id is ObjectId, otherwise bail 123 | if !bson.IsObjectIdHex(id) { 124 | msg := []byte(`{"message":"ObjectId invalid"}`) 125 | w.Header().Set("Content-Type", "application/json") 126 | w.WriteHeader(http.StatusNotFound) 127 | fmt.Fprintf(w, "%s", msg) 128 | return 129 | } 130 | 131 | // Stub an Project to be populated from the body 132 | u := model.Project{} 133 | json.NewDecoder(r.Body).Decode(&u) 134 | u.UpdatedAt = time.Now() 135 | u.Slug = slug.Make(u.Label) 136 | 137 | c := ss.DB("login").C("projects") 138 | 139 | if err := c.Update(bson.M{"_id": bson.ObjectIdHex(id)}, &u); err != nil { 140 | switch err { 141 | default: 142 | msg := []byte(`{"message":"ObjectId invalid"}`) 143 | w.Header().Set("Content-Type", "application/json") 144 | w.WriteHeader(http.StatusNotFound) 145 | fmt.Fprintf(w, "%s", msg) 146 | 147 | case mgo.ErrNotFound: 148 | msg := []byte(`{"message":"ObjectId not found"}`) 149 | w.Header().Set("Content-Type", "application/json") 150 | w.WriteHeader(http.StatusNotFound) 151 | fmt.Fprintf(w, "%s", msg) 152 | } 153 | return 154 | } 155 | 156 | w.WriteHeader(http.StatusOK) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API-Golang-jwt 2 | 3 | ### Bateries of POC 4 | * Golang 1.7.3 5 | * JWT 6 | * Linux Ubuntu 7 | * MongoDB / Redis 8 | * HTTPie framework 9 | * Glide 10 | 11 | ### Update project dependencies 12 | Before you running the server. Use the `glide` for update all packages of project. 13 | 14 | ### DB Dispatch 15 | We can use one or more database / cache in same time, just add the session of db on `db/dispatch.go` 16 | 17 | ### POC Running 18 | POC of `API with jwt` more `Permission(bitwise)` middleware per route. 19 | 20 | Generate a valid token. Post `/auth` with fields ***email*** and ***password*** 21 | ```sh 22 | http --verbose post http://localhost:3333/auth email=tzilli@inviron.com.br password=1233 23 | 24 | POST /auth HTTP/1.1 25 | Accept: application/json, */* 26 | Accept-Encoding: gzip, deflate 27 | Connection: keep-alive 28 | Content-Length: 53 29 | Content-Type: application/json 30 | Host: localhost:3333 31 | User-Agent: HTTPie/0.9.9 32 | 33 | { 34 | "email": "tzilli@inviron.com.br", 35 | "password": "1233" 36 | } 37 | ``` 38 | 39 | We got the response. 40 | ```sh 41 | HTTP/1.1 201 Created 42 | Content-Length: 203 43 | Content-Type: application/json; charset=utf-8 44 | Date: Mon, 13 Feb 2017 12:22:17 GMT 45 | Vary: Origin 46 | 47 | { 48 | "expire": "2017-02-14 15:14:25 -0200 BRST", 49 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic3VwZXItaWQtb2YtbW9uZ29kYi11c2VyIiwiYWRtaW4iOnRydWUsImV4cCI6MTQ4NzA5MjQ2NSwiaXNzIjoibG9jYWxob3N0OjMzMzMifQ.lEy23l89sAe03g9Dg24FUiqUKEopSt61f-CE-1U6SpM" 50 | } 51 | 52 | ``` 53 | 54 | 55 | Save your token on a txt file, like `token.txt` and use a `GET` on `/admin/:slug` endpoint to test token. 56 | ```sh 57 | $ http --verbose --auth-type=jwt --auth=$(cat token.txt): get http://localhost:3333/admin 58 | ``` 59 | 60 | Request 61 | 62 | ```sh 63 | GET /admin HTTP/1.1 64 | Accept: */* 65 | Accept-Encoding: gzip, deflate 66 | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiaWQtb2YtdXNlci1uaWNlIiwiYWRtaW4iOnRydWUsImV4cCI6MTQ4NzA3NDkzNywiaXNzIjoibG9jYWxob3N0OjMzMzMifQ.6Xxg678o6WrhQULMtYA9Z7GXICsruFrXIcHPIqQy6cw 67 | Connection: keep-alive 68 | Host: localhost:3333 69 | User-Agent: HTTPie/0.9.9 70 | 71 | 72 | HTTP/1.1 200 OK 73 | Content-Length: 41 74 | Content-Type: text/plain; charset=utf-8 75 | Date: Mon, 13 Feb 2017 12:35:09 GMT 76 | Vary: Origin 77 | 78 | protected area. USER ID = 58aadf2ce3bdea2e00f9563b 79 | ``` 80 | 81 | ### Project tree 82 | ```sh 83 | ├── api 84 | │   ├── config 85 | │   ├── controllers 86 | │   │   ├── admin.go 87 | │   │   ├── auth.go 88 | │   │   ├── permission.go 89 | │   │   ├── project.go 90 | │   │   └── user.go 91 | │   ├── dbs 92 | │   │   ├── dispatch.go 93 | │   │   ├── logger.go 94 | │   │   └── mongodb.go 95 | │   ├── middlewares 96 | │   │   ├── loggerrequest.go 97 | │   │   ├── mongodb.go 98 | │   │   ├── permission.go 99 | │   │   ├── project.go 100 | │   │   └── token.go 101 | │   ├── models 102 | │   │   ├── jwt.go 103 | │   │   ├── permission.go 104 | │   │   ├── project.go 105 | │   │   └── user.go 106 | │   ├── routes 107 | │   │   ├── main.go 108 | │   │   ├── protected.go 109 | │   │   └── public.go 110 | │   ├── server.go 111 | │   ├── services 112 | │   │   ├── jwtauth.go 113 | │   │   └── user.go 114 | │   └── shared 115 | │   ├── bcrypt.go 116 | │   ├── pathproject.go 117 | │   └── split.go 118 | ├── bin 119 | │   ├── api-server-arm 120 | │   ├── api-server-x32 121 | │   └── api-server-x64 122 | ├── docs 123 | │   └── httpie.txt 124 | ├── glide.lock 125 | ├── glide.yaml 126 | ├── README.md 127 | ├── scripts 128 | │   ├── build-all.sh 129 | │   ├── build-arm.sh 130 | │   ├── build-docker-image.sh 131 | │   ├── build-linux-x32.sh 132 | │   └── build-linux-x64.sh 133 | └── token.txt 134 | ``` 135 | 136 | ### MongoDB models 137 | 138 | #### Permissons 139 | ``` 140 | { 141 | "_id" : ObjectId("58ab1f5d846dd72723990def"), 142 | "permissions" : [ 143 | { 144 | "method" : "ALL", 145 | "value" : NumberInt(16) 146 | } 147 | ], 148 | "owner" : ObjectId("58aadf2ce3bdea2e00f9563b"), 149 | "endpoint" : "admin", 150 | "project" : ObjectId("58ab26d3e3bdea342a9571d8"), 151 | "created_at" : ISODate("2017-02-20T17:02:11.452+0000"), 152 | "updated_at" : ISODate("2017-02-20T17:03:11.506+0000") 153 | } 154 | ``` 155 | #### Projects 156 | ``` 157 | { 158 | "_id" : ObjectId("58ab26d3e3bdea342a9571d8"), 159 | "label" : "Test Project", 160 | "slug" : "test-project", 161 | "description" : "Test Project crud", 162 | "owner" : ObjectId("58aadf2ce3bdea2e00f9563b"), 163 | "users" : [ 164 | ObjectId("58aadf2ce3bdea2e00f9563b") 165 | ], 166 | "created_at" : ISODate("2017-02-20T17:26:43.083+0000"), 167 | "updated_at" : ISODate("2017-02-20T17:26:43.083+0000") 168 | } 169 | ``` 170 | #### Users 171 | ``` 172 | { 173 | "_id" : ObjectId("58aadf2ce3bdea2e00f9563b"), 174 | "email" : "tzilli@inviron.com.br", 175 | "password" : "$2a$10$gMmv6mFAxcJwqT1fef6Uj.KB.A.FL5DTnn2ytrKzcgHc/GXGIxKhe", 176 | "admin" : true, 177 | "created_at" : ISODate("2017-02-20T12:21:00.333+0000"), 178 | "updated_at" : ISODate("2017-02-20T12:21:00.333+0000") 179 | } 180 | ``` 181 | 182 | ### Routes 183 | You can crate a new methods or distinct kind of routes, just edit the package on folder `routes`, file `main.go`. 184 | For this project, I'm created two routes, one for public access and other need a `JWT` authentication. By default we need a route with a `slug` on the URL request, to check the users belongs to the project, and than check permission of owner. 185 | ``` 186 | ├── api 187 | │   ├── routes 188 | │   │   ├── main.go 189 | │   │   ├── protected.go 190 | │   │   └── public.go 191 | ``` 192 | 193 | ### Middlewares 194 | Early data check and others purpose. 195 | ``` 196 | ├── api 197 | │   ├── middlewares 198 | │   │   ├── loggerrequest.go 199 | │   │   ├── mongodb.go 200 | │   │   ├── permission.go 201 | │   │   ├── project.go 202 | │   │   └── token.go 203 | ``` 204 | 205 | ### Controllers 206 | For more actions of endpoint just create a CRUD functions on `ROOT of folder` or you can create a `ditinct folder`. 207 | ``` 208 | ├── api 209 | │   ├── controllers 210 | │   │   ├── admin.go 211 | │   │   ├── auth.go 212 | │   │   ├── permission.go 213 | │   │   ├── project.go 214 | │   │   └── user.go 215 | ``` 216 | 217 | ### Models 218 | Models, struct, inteface and wrapper. 219 | ``` 220 | ├── api 221 | │   ├── models 222 | │   │   ├── jwt.go 223 | │   │   ├── permission.go 224 | │   │   ├── project.go 225 | │   │   └── user.go 226 | ``` 227 | 228 | ### DataBase Connections and loggers 229 | In this section we concentrate a sessions for all dbs, all in wrapped by `dispatch.go`. 230 | ``` 231 | ├── api 232 | │   ├── dbs 233 | │   │   ├── dispatch.go 234 | │   │   ├── logger.go 235 | │   │   └── mongodb.go 236 | ``` 237 | 238 | 239 | --- 240 | 241 | The MIT License (MIT) 242 | 243 | Copyright (c) 2017 THIAGO ZILLI SARMENTO 244 | 245 | Permission is hereby granted, free of charge, to any person obtaining a copy 246 | of this software and associated documentation files (the "Software"), to deal 247 | in the Software without restriction, including without limitation the rights 248 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 249 | copies of the Software, and to permit persons to whom the Software is 250 | furnished to do so, subject to the following conditions: 251 | 252 | The above copyright notice and this permission notice shall be included in all 253 | copies or substantial portions of the Software. 254 | 255 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 256 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 257 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 258 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 259 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 260 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 261 | SOFTWARE. 262 | 263 | 264 | 265 | --------------------------------------------------------------------------------