├── .env.Example ├── .gitignore ├── LICENSE.md ├── README.md ├── app └── auth.go ├── controllers ├── authControllers.go ├── fileController.go └── permissionController.go ├── go.mod ├── go.sum ├── main.go └── models ├── accounts.go ├── base.go ├── files.go └── permissions.go /.env.Example: -------------------------------------------------------------------------------- 1 | db_name = test 2 | db_pass = test 3 | db_user = test 4 | db_type = test 5 | db_host = test 6 | db_port = test 7 | token_password = test 8 | database_url = postgres://"db_name":"db_pass"@"db_host:"db_port"/tests -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .env -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Adigun Hammed Olalekan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTP-server-with-auth# HTTP Server With Authentication 2 | 3 | # Introduction 4 | 5 | You are to use gin framework package and concurrency in golang and jwt-go to implement a simple http server. 6 | You should implement an rest api with authentication on some endpoints to upload or download a file. 7 | All registered users can upload and download files. no user can download other user's files. users can 8 | give access to other specific users to download their files. 9 | REST APIs that you should implement are listed below: 10 | 11 | ## `localhost:8080/register` 12 | 1. **json** : 13 | ```json 14 | { 15 | "username" : "string", 16 | "password" : "string" 17 | } 18 | ``` 19 | create new user 20 | ## `localhost:8080/login` 21 | 1. **json** : 22 | ```json 23 | { 24 | "username" : "string", 25 | "password" : "string" 26 | } 27 | ``` 28 | logins and gives access token 29 | 30 | ## `localhost:8080/uploadFile` with auth 31 | 32 | ***input formats*** : 33 | 34 | 35 | 1. **form** : 36 | 37 | file : []byte 38 | 39 | In this format, `file` is a byte array of the actual file. 40 | uploads file in filesystem. 41 | 42 | 43 | ***output format*** : 44 | 45 | 1. **json** : 46 | 47 | *successful upload* : 48 | ```json 49 | { 50 | "download_url" : "string" 51 | } 52 | ``` 53 | 54 | `download_url` is the path of saved file in filesystem. 55 | 56 | *failure upload* : 57 | ```json 58 | { 59 | "error" : "string" 60 | } 61 | ``` 62 | 63 | `error` is a description of the occurred error. 64 | 65 | 66 | 67 |
68 | 69 | ## `localhost:8080/downloadFile` with auth 70 | 71 | 1. **json** : 72 | ```json 73 | { 74 | "download_url" : "string" 75 | } 76 | ``` 77 | 78 | In this format, `download_url` is an id that we got in successful upload request. it will download the file if user has download premission. 79 | 80 | 81 |
82 | 83 | ***output format*** : 84 | 85 | 1. **json** : 86 | 87 | *successful download* : 88 | 89 | http response with actual file 90 | \ 91 | *failure download* : 92 | ```json 93 | { 94 | "error" : "string" 95 | } 96 | ``` 97 | 98 | `error` is a description of the occurred error. 99 | 100 | 101 | ## `localhost:8080/addPremission` with auth 102 | 103 | 1. **json** : 104 | ```json 105 | { 106 | "download_url" : "string", 107 | "user_to_be_add" : "string" 108 | } 109 | ``` 110 | 111 | 112 | 113 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /app/auth.go: -------------------------------------------------------------------------------- 1 | package app 2 | import ( 3 | "fmt" 4 | "github.com/dgrijalva/jwt-go" 5 | "github.com/gin-gonic/gin" 6 | "http-server-with-auth/models" 7 | "net/http" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | func AuthorizeJWT(c *gin.Context) { 13 | tokenHeader := c.GetHeader("Authorization") 14 | if tokenHeader == "" { 15 | c.Header("Content-Type", "application/json") 16 | c.JSON(http.StatusForbidden, gin.H{ 17 | "error": true, 18 | "message": "missing token", 19 | }) 20 | return 21 | } 22 | //The token normally comes in format `Bearer {token-body}`, we check if the retrieved token matched this requirement 23 | splitted := strings.Split(tokenHeader, " ") 24 | if len(splitted) != 2 { 25 | c.JSON(http.StatusForbidden, gin.H{ 26 | "message": "Invalid/Malformed auth token", 27 | }) 28 | c.Header("Content-Type", "application/json") 29 | return 30 | } 31 | //Grab the token part 32 | tokenPart := splitted[1] 33 | tk := &models.Token{} 34 | token, err := jwt.ParseWithClaims(tokenPart, tk, func(token *jwt.Token) (interface{}, error) { 35 | return []byte(os.Getenv("token_password")), nil 36 | }) 37 | 38 | if err != nil { //Malformed token, returns with http code 403 as usual 39 | c.JSON(403, gin.H{ 40 | "error": true, 41 | "message": "Malformed authentication token", 42 | }) 43 | c.Header("Content-Type", "application/json") 44 | return 45 | } 46 | 47 | if !token.Valid { //Token is invalid, maybe not signed on this server 48 | c.JSON(http.StatusForbidden, gin.H{ 49 | "message": "Malformed authentication token", 50 | }) 51 | c.Header("Content-Type", "application/json") 52 | return 53 | } 54 | //Everything went well, proceed with the request and set the caller to the user retrieved from the parsed token 55 | fmt.Sprintf("User %", tk.UserId) //Useful for monitoring 56 | 57 | c.Set("user", tk.UserId) 58 | //c.Request.WithContext(context.WithValue(c.Request.Context(), "user", tk.UserId)) 59 | c.Next() 60 | //ctx := context.WithValue(r.Context(), "user", tk.UserId) 61 | //r = r.WithContext(ctx) 62 | //next.ServeHTTP(w, r) //proceed in the middleware chain! 63 | } 64 | 65 | 66 | -------------------------------------------------------------------------------- /controllers/authControllers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "http-server-with-auth/models" 6 | ) 7 | 8 | var Register = func (c *gin.Context) { 9 | account := &models.Account{} 10 | err := c.ShouldBindJSON(account) 11 | if err != nil { 12 | c.JSON(422, gin.H{ 13 | "error": true, 14 | "message": "invalid request body", 15 | }) 16 | return 17 | } 18 | account.Validate(c) 19 | } 20 | 21 | 22 | var Authenticate = func(c *gin.Context) { 23 | account := &models.Account{} 24 | err := c.ShouldBindJSON(account) 25 | if err != nil { 26 | c.JSON(404, "invalid request body") 27 | return 28 | } 29 | models.Login(account.Email, account.Password, c) 30 | } 31 | -------------------------------------------------------------------------------- /controllers/fileController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "http-server-with-auth/models" 7 | "io" 8 | "log" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | var Upload = func(c *gin.Context) { 14 | file, header, err := c.Request.FormFile("file") 15 | if err != nil { 16 | c.String(http.StatusBadRequest, fmt.Sprintf("file err : %s", err.Error())) 17 | return 18 | } 19 | filename := header.Filename 20 | out, err := os.Create("public/" + filename) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | defer func(out *os.File) { 25 | err := out.Close() 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | }(out) 30 | _, err = io.Copy(out, file) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | 35 | filepath := "http://localhost:8080/file/" + filename 36 | 37 | c.JSON(http.StatusOK, gin.H{ 38 | "message": "file uploaded successfully", 39 | "filepath": filepath, 40 | }) 41 | models.Validate(c, filename) 42 | } 43 | 44 | var Download = func(c *gin.Context) { 45 | //fmt.Println(c.Request.Context().Value("user").(uint)) 46 | id := c.Value("user").(uint) 47 | file := &models.File{} 48 | err := c.ShouldBindJSON(file) 49 | if err != nil { 50 | c.JSON(422, gin.H{ 51 | "error": true, 52 | "message": "invalid request body", 53 | }) 54 | return 55 | } 56 | if !models.IsPermitted(file.FileName, id) { 57 | c.JSON(http.StatusForbidden, gin.H{ 58 | "error": true, 59 | "message": "you are not permitted", 60 | }) 61 | return 62 | } 63 | c.JSON(200, gin.H{ 64 | "error": false, 65 | "message": "you are allowed", 66 | }) 67 | 68 | c.File("./public/" + file.FileName) 69 | } 70 | -------------------------------------------------------------------------------- /controllers/permissionController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "http-server-with-auth/models" 6 | ) 7 | 8 | var GivePermission = func(c* gin.Context) { 9 | permission := &models.Permission{} 10 | err := c.ShouldBindJSON(permission) 11 | if err != nil{ 12 | c.JSON(422, gin.H{ 13 | "error": true, 14 | "message": "invalid request body", 15 | }) 16 | return 17 | } 18 | permission.Create(c) 19 | } 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module http-server-with-auth 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd // indirect 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 8 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect 9 | github.com/go-sql-driver/mysql v1.5.0 // indirect 10 | github.com/gorilla/context v1.1.1 // indirect 11 | github.com/gorilla/mux v1.8.0 12 | github.com/jinzhu/gorm v1.9.16 13 | github.com/jinzhu/inflection v1.0.0 // indirect 14 | github.com/jinzhu/now v1.0.1 // indirect 15 | github.com/joho/godotenv v1.4.0 16 | github.com/lib/pq v1.1.1 // indirect 17 | github.com/mattn/go-sqlite3 v1.14.0 // indirect 18 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 19 | google.golang.org/appengine v1.6.4 // indirect 20 | ) 21 | 22 | require ( 23 | github.com/gin-contrib/sse v0.1.0 // indirect 24 | github.com/gin-gonic/gin v1.7.4 // indirect 25 | github.com/go-playground/locales v0.13.0 // indirect 26 | github.com/go-playground/universal-translator v0.17.0 // indirect 27 | github.com/go-playground/validator/v10 v10.4.1 // indirect 28 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect 29 | github.com/golang/protobuf v1.3.3 // indirect 30 | github.com/json-iterator/go v1.1.9 // indirect 31 | github.com/leodido/go-urn v1.2.0 // indirect 32 | github.com/mattn/go-isatty v0.0.12 // indirect 33 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 34 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect 35 | github.com/ugorji/go/codec v1.1.7 // indirect 36 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect 37 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect 38 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect 39 | golang.org/x/text v0.3.2 // indirect 40 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b // indirect 41 | gopkg.in/yaml.v2 v2.2.8 // indirect 42 | ) 43 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= 2 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0 h1:epsH3lb7KVbXHYk7LYGN5EiE0MxcevHU85CKITJ0wUY= 6 | github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 7 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 9 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 10 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= 11 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 12 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 13 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 14 | github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM= 15 | github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= 16 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 17 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 18 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 19 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 20 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 21 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 22 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 23 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 24 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 25 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 26 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= 27 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 28 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 29 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 30 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 31 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 32 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= 33 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 34 | github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= 35 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 36 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 37 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 38 | github.com/jinzhu/gorm v1.9.1 h1:lDSDtsCt5AGGSKTs8AHlSDbbgif4G4+CKJ8ETBDVHTA= 39 | github.com/jinzhu/gorm v1.9.1/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= 40 | github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= 41 | github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= 42 | github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k= 43 | github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 44 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 45 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 46 | github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= 47 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 48 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 49 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 50 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= 51 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 52 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 53 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 54 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 55 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 56 | github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= 57 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 58 | github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= 59 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 60 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 61 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 62 | github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= 63 | github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 64 | github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= 65 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 66 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 67 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 68 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 69 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 70 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 71 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 72 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 73 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 74 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 75 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 76 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 77 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 78 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= 79 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 80 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= 81 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 82 | golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 83 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 84 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 85 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 86 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 87 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 88 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 89 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 90 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 91 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 92 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 93 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 94 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 95 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 96 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= 97 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 98 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 99 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 100 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 101 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 102 | google.golang.org/appengine v1.6.4 h1:WiKh4+/eMB2HaY7QhCfW/R7MuRAoA8QMCSJA6jP5/fo= 103 | google.golang.org/appengine v1.6.4/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 104 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 105 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 106 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 107 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 108 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | _ "github.com/gorilla/mux" 7 | "http-server-with-auth/app" 8 | "http-server-with-auth/controllers" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | router := gin.Default() 15 | router.POST("/user/new", controllers.Register) 16 | router.POST("/user/login", app.AuthorizeJWT, controllers.Authenticate) 17 | router.POST("/user/upload", controllers.Upload) 18 | router.GET("/user/download", app.AuthorizeJWT, controllers.Download) 19 | router.POST("/user/give/permission", controllers.GivePermission) 20 | 21 | port := os.Getenv("PORT") 22 | if port == "" { 23 | port = "8040" //localhost 24 | } 25 | 26 | fmt.Println(port) 27 | 28 | err := http.ListenAndServe(":"+port, router) //Launch the app, visit localhost:8000/api 29 | if err != nil { 30 | fmt.Print(err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /models/accounts.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | "github.com/gin-gonic/gin" 6 | "github.com/jinzhu/gorm" 7 | "golang.org/x/crypto/bcrypt" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | /* 13 | JWT claims struct 14 | */ 15 | type Token struct { 16 | UserId uint 17 | Email string 18 | jwt.StandardClaims 19 | } 20 | 21 | //a struct to rep user account 22 | type Account struct { 23 | gorm.Model 24 | Email string `json:"email"` 25 | Password string `json:"password"` 26 | Token string `json:"token"` 27 | } 28 | 29 | //Validate incoming user details... 30 | func (account *Account) Validate(c *gin.Context) { 31 | 32 | if !strings.Contains(account.Email, "@") { 33 | c.JSON(422, gin.H{ 34 | "error": true, 35 | "message": "email must contain @", 36 | }) 37 | return 38 | } 39 | 40 | if len(account.Password) < 6 { 41 | c.JSON(422, gin.H{ 42 | "error": true, 43 | "message": "password is too short", 44 | }) 45 | return 46 | } 47 | //Email must be unique 48 | temp := &Account{} 49 | 50 | //check for errors and duplicate emails 51 | err := GetDB().Table("accounts").Where("email = ?", account.Email).First(temp).Error 52 | 53 | if err != nil && err != gorm.ErrRecordNotFound { 54 | c.JSON(422, gin.H{ 55 | "error": true, 56 | "message": "connection error, try again", 57 | }) 58 | return 59 | } 60 | 61 | if temp.Email != "" { 62 | c.JSON(422, gin.H{ 63 | "error": true, 64 | "message": "email address in use", 65 | }) 66 | return 67 | } 68 | account.Create(c) 69 | 70 | c.JSON(200, gin.H{ 71 | "error": false, 72 | "message": "requirement passed", 73 | }) 74 | return 75 | } 76 | 77 | func (account *Account) Create(c *gin.Context) { 78 | 79 | hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(account.Password), bcrypt.DefaultCost) 80 | account.Password = string(hashedPassword) 81 | 82 | GetDB().Create(account) 83 | 84 | if account.ID <= 0 { 85 | c.JSON(422, gin.H{ 86 | "error": true, 87 | "message": "connection error, try again", 88 | }) 89 | } 90 | 91 | //Create new JWT token for the newly registered account 92 | tk := &Token{UserId: account.ID} 93 | token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) 94 | tokenString, _ := token.SignedString([]byte(os.Getenv("token_password"))) 95 | account.Token = tokenString 96 | account.Password = "" //delete password 97 | c.JSON(200, gin.H{ 98 | "error": false, 99 | "message": "account has been created", 100 | }) 101 | c.Header("Postman-Token", account.Token) 102 | c.JSON(200, account) 103 | 104 | } 105 | 106 | func Login(email, password string, c *gin.Context) { 107 | 108 | account := &Account{} 109 | err := GetDB().Table("accounts").Where("email = ?", email).First(account).Error 110 | if err != nil { 111 | if err == gorm.ErrRecordNotFound { 112 | c.JSON(420, gin.H{ 113 | "error": true, 114 | "message": "email address not found", 115 | }) 116 | return 117 | } 118 | c.JSON(420, gin.H{ 119 | "error": true, 120 | "message": "connection error, try again", 121 | }) 122 | return 123 | } 124 | 125 | err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(password)) 126 | if err != nil && err == bcrypt.ErrMismatchedHashAndPassword { //Password does not match! 127 | c.JSON(420, gin.H{ 128 | "error": true, 129 | "message": "Invalid login credentials. Please try again", 130 | }) 131 | return 132 | } 133 | //Worked! Logged In 134 | account.Password = "" 135 | //Create JWT token 136 | tk := &Token{UserId: account.ID, Email: account.Email} 137 | token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) 138 | tokenString, _ := token.SignedString([]byte(os.Getenv("token_password"))) 139 | account.Token = tokenString //Store the token in the response 140 | c.JSON(200, gin.H{ 141 | "error": false, 142 | "message": "you are logged in", 143 | }) 144 | } 145 | 146 | func GetUser(u uint) *Account { 147 | acc := &Account{} 148 | GetDB().Table("accounts").Where("id = ?", u).First(acc) 149 | if acc.Email == "" { //User not found! 150 | return nil 151 | } 152 | acc.Password = "" 153 | return acc 154 | } 155 | -------------------------------------------------------------------------------- /models/base.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jinzhu/gorm" 6 | _ "github.com/jinzhu/gorm/dialects/postgres" 7 | "github.com/joho/godotenv" 8 | "os" 9 | ) 10 | 11 | var db *gorm.DB 12 | 13 | func init() { 14 | 15 | e := godotenv.Load() 16 | if e != nil { 17 | fmt.Print(e) 18 | } 19 | 20 | username := os.Getenv("db_user") 21 | password := os.Getenv("db_pass") 22 | dbName := os.Getenv("db_name") 23 | dbHost := os.Getenv("db_host") 24 | 25 | dbUri := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable password=%s", dbHost, username, dbName, password) 26 | fmt.Println(dbUri) 27 | 28 | conn, err := gorm.Open("postgres", dbUri) 29 | if err != nil { 30 | fmt.Print(err) 31 | } 32 | 33 | db = conn 34 | db.Debug().AutoMigrate(&Account{}, &File{}, &Permission{}) 35 | } 36 | 37 | func GetDB() *gorm.DB { 38 | return db 39 | } 40 | -------------------------------------------------------------------------------- /models/files.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | type File struct { 8 | FileName string `json:"file_name"` 9 | } 10 | 11 | func Validate(c *gin.Context, fileName string){ 12 | temp := &File{} 13 | GetDB().Table("files").Where("file_name =?", fileName).First(temp) 14 | if temp.FileName != "" { 15 | c.JSON(422, gin.H{ 16 | "error": true, 17 | "message": "filename is already taken", 18 | }) 19 | return 20 | } 21 | c.JSON(200, gin.H{ 22 | "error": false, 23 | "message": "file is created successfully", 24 | }) 25 | Create(c, fileName) 26 | } 27 | 28 | func Create(c *gin.Context, fileName string) { 29 | file := File{FileName: fileName} 30 | GetDB().Create(&file) 31 | c.JSON(200, gin.H{ 32 | "error": false, 33 | "message": file.FileName, 34 | }) 35 | } -------------------------------------------------------------------------------- /models/permissions.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | type Permission struct { 10 | gorm.Model 11 | FileName string 12 | UserName string 13 | } 14 | 15 | func (permission *Permission) Validate(c *gin.Context) { 16 | temp := &Permission{} 17 | err := GetDB().Table("permissions").Where("username = ?", permission.UserName).First(temp).Error 18 | if err != nil { 19 | c.JSON(422, gin.H{ 20 | "error": true, 21 | "message": "connection error, please try again", 22 | }) 23 | return 24 | } 25 | err = GetDB().Table("files").Where("permission = ?", permission.FileName).First(temp).Error 26 | if err != nil { 27 | c.JSON(422, gin.H{ 28 | "error": true, 29 | "message": "connection error, please try again", 30 | }) 31 | return 32 | } 33 | 34 | if temp.UserName == "" { 35 | c.JSON(422, gin.H{ 36 | "error": true, 37 | "message": "this username is not registered", 38 | }) 39 | return 40 | } 41 | 42 | if temp.FileName == "" { 43 | c.JSON(422, gin.H{ 44 | "error": true, 45 | "message": "this file name is not registered", 46 | }) 47 | return 48 | } 49 | 50 | c.JSON(200, gin.H{ 51 | "error": false, 52 | "message": "permission is given successfully", 53 | }) 54 | permission.Create(c) 55 | } 56 | 57 | func (permission *Permission) Create(c *gin.Context) { 58 | GetDB().Create(permission) 59 | c.JSON(200, permission) 60 | } 61 | 62 | func IsPermitted(fileName string, id uint) bool { 63 | account := &Account{} 64 | userName := db.Table("accounts").Select("email").Where("id = ?", id).Find(account) 65 | fmt.Println(account) 66 | err := db.Where( 67 | db.Where("file_name = ?", fileName).Where(db.Where("user_name = ?", userName)), 68 | ).Find(&Permission{}).Error 69 | if err != nil { 70 | fmt.Println(err) 71 | return false 72 | } 73 | return true 74 | } 75 | --------------------------------------------------------------------------------