Skip to content

Commit

Permalink
feat: add endpoint authenticaton
Browse files Browse the repository at this point in the history
  • Loading branch information
ShwetaGhenand committed Nov 2, 2021
1 parent 9e25aec commit b4c2bee
Show file tree
Hide file tree
Showing 19 changed files with 299 additions and 137 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ PGDATABASE="userdb"
PGHOST="db"
PGPORT=5432
PGSSLMODE="disable"
SECRET="7RQOLP1d7P"
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ User Service is REST API server implementation using gorila/mux.

Endpoints:

| Method | Endpoint | Description |
| ------------- | ------------- | ------------------------ |
| POST | /users | Create a user |
| GET | /users | Get list of all users |
| GET | /users/{id} | Get user by id |
| PUT | /users/{id} | Update user by id |
| DELETE | /users/{id} | Delete user by id |
| Method | Endpoint | Description |
| ------------- | ------------- | --------------------------------------------- |
| POST | /signin | Create a user |
| POST | /login | Verify user details and generate jwt token |
| GET | /users | Get list of all users |
| GET | /users/{id} | Get user by id |
| PUT | /users/{id} | Update user by id |
| DELETE | /users/{id} | Delete user by id |

## Run locally

Expand Down
62 changes: 62 additions & 0 deletions cmds/server/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package server

import (
"log"
"net/http"
"strings"

"github.com/gorilla/mux"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwt"
)

type JWTSign struct {
key interface{}
algorithm jwa.SignatureAlgorithm
}

func NewJWTToken(name, secret string) (string, error) {
var tokenStr string
js := &JWTSign{key: []byte(secret), algorithm: jwa.HS256}
token := jwt.New()
if err := token.Set(`name`, name); err != nil {
return tokenStr, err
}
payload, err := jwt.Sign(token, js.algorithm, js.key)
if err != nil {
return tokenStr, err
}
tokenStr = string(payload)
return tokenStr, nil
}

func ParseJWTToken(tokenStr, secret string) error {
_, err := jwt.Parse(
[]byte(tokenStr),
jwt.WithValidate(true),
jwt.WithVerify(jwa.HS256, []byte(secret)))
if err != nil {
return err
}
return nil
}

func authMiddleware(secret string) mux.MiddlewareFunc {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
authorizationHeader := req.Header.Get("authorization")
bearerToken := strings.Split(authorizationHeader, " ")
if len(bearerToken) != 2 {
log.Println("missing authorization token")
http.Error(w, "forbidden", http.StatusForbidden)
return
}
if err := ParseJWTToken(bearerToken[1], secret); err != nil {
log.Printf("error parsing authorization token %v", err)
http.Error(w, "forbidden", http.StatusForbidden)
return
}
h.ServeHTTP(w, req)
})
}
}
13 changes: 9 additions & 4 deletions cmds/server/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,25 @@ import (
type server struct {
router *mux.Router
service *service
secret string
}

func newServer(conf DBConfig) *server {
db, err := initDB(conf)
func newServer(conf Config) *server {
db, err := initDB(conf.DBConfig)
if err != nil {
log.Fatalf("Database Error %v", err)
}
s := &server{
router: mux.NewRouter(),
service: &service{db: db},
secret: conf.Secret,
}
s.router.HandleFunc("/health", getHealth).Methods("GET")
s.router.HandleFunc("/signin", s.createUser).Methods("POST")
s.router.HandleFunc("/login", s.loginUser).Methods("POST")

sr := s.router.PathPrefix("/users").Subrouter()
sr.HandleFunc("", s.createUser).Methods("POST")
sr.Use(authMiddleware(conf.Secret))
sr.HandleFunc("", s.listUsers).Methods("GET")
sr.HandleFunc("/{id}", s.getUser).Methods("GET")
sr.HandleFunc("/{id}", s.updateUser).Methods("PUT")
Expand All @@ -48,7 +53,7 @@ func Cmd() *cli.Command {
Usage: "users rest apis",
Flags: flagtags.MustParseFlags(&conf),
Action: func(c *cli.Context) error {
s := newServer(conf.DBConfig)
s := newServer(conf)
log.Println("Server is listening on port : ", conf.Port)
if err := http.ListenAndServe(fmt.Sprintf(":%d", conf.Port), s); err != nil {
log.Fatalln(err)
Expand Down
1 change: 1 addition & 0 deletions cmds/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package server
type Config struct {
Port int
DBConfig
Secret string
}
4 changes: 2 additions & 2 deletions cmds/server/db_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ func (c *DBConfig) url() string {
return u.String()
}

func initDB(c DBConfig) (*Queries, error) {
cfg, err := pgx.ParseConfig(c.url())
func initDB(conf DBConfig) (*Queries, error) {
cfg, err := pgx.ParseConfig(conf.url())
if err != nil {
return nil, err
}
Expand Down
10 changes: 0 additions & 10 deletions cmds/server/dto.go

This file was deleted.

12 changes: 7 additions & 5 deletions cmds/server/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,20 @@ func checkError(err error) error {
ce.Err = errors.New("duplicate user id")
ce.Code = 400
default:
ce.Err = errors.New("database error")
ce.Err = fmt.Errorf("database error %w", err)
ce.Code = 500
}
return &ce
}

func validate(u userDto) error {
if u.ID <= 0 {
func validate(user JSONUser) error {
if user.ID <= 0 {
return &customErr{errors.New("missing id"), 400}
} else if u.Name == "" {
} else if user.Name == "" {
return &customErr{errors.New("missing name"), 400}
} else if u.Email == "" {
} else if user.Password == "" {
return &customErr{errors.New("missing password"), 400}
} else if user.Email == "" {
return &customErr{errors.New("missing email"), 400}
}
return nil
Expand Down
64 changes: 49 additions & 15 deletions cmds/server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,49 @@ func getHealth(w http.ResponseWriter, r *http.Request) {
}
}

type loginUserRequest struct {
Name string `json:"name"`
Password string `json:"password"`
}

type loginUserResponse struct {
Token string `json:"token"`
}

// loginUser : verify user details and generate jwt token
func (s *server) loginUser(w http.ResponseWriter, r *http.Request) {
log.Println("Login user endpoint called.")
req := loginUserRequest{}
_ = json.NewDecoder(r.Body).Decode(&req)
if req.Name == "" || req.Password == "" {
http.Error(w, "invalid login details", 400)
return
}
if err := s.service.UserExists(req.Name, req.Password); err != nil {
writeError(w, err)
return
}
t, err := NewJWTToken(req.Name, s.secret)
if err != nil {
writeError(w, err)
return
}
if err := json.NewEncoder(w).Encode(loginUserResponse{Token: t}); err != nil {
writeError(w, err)
return
}
w.WriteHeader(http.StatusOK)
}

// listUsers : returns list of users
func (s *server) listUsers(w http.ResponseWriter, r *http.Request) {
log.Println("Get all users endpoint called.")
dtos, err := s.service.listUsers()
res, err := s.service.listUsers()
if err != nil {
writeError(w, err)
return
}
if err := json.NewEncoder(w).Encode(dtos); err != nil {
if err := json.NewEncoder(w).Encode(res); err != nil {
writeError(w, err)
return
}
Expand All @@ -57,14 +91,14 @@ func (s *server) listUsers(w http.ResponseWriter, r *http.Request) {
func (s *server) getUser(w http.ResponseWriter, r *http.Request) {
log.Println("Get single user endpoint called.")
id, _ := strconv.ParseInt(mux.Vars(r)["id"], 10, 32)
dto, err := s.service.getUser(int32(id))
user, err := s.service.getUser(int32(id))
if err != nil {
writeError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
res, err := json.Marshal(dto)
res, err := json.Marshal(user)
if err != nil {
writeError(w, err)
return
Expand All @@ -78,21 +112,21 @@ func (s *server) getUser(w http.ResponseWriter, r *http.Request) {
// addUser : add single user
func (s *server) createUser(w http.ResponseWriter, r *http.Request) {
log.Println("Add user endpoint called.")
dtoReq := userDto{}
if err := json.NewDecoder(r.Body).Decode(&dtoReq); err != nil {
req := JSONUser{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, err)
return
}
if err := validate(dtoReq); err != nil {
if err := validate(req); err != nil {
writeError(w, err)
return
}
dtoRes, err := s.service.createUser(dtoReq)
err := s.service.createUser(req)
if err != nil {
writeError(w, err)
return
}
res, err := json.Marshal(dtoRes)
res, err := json.Marshal(req)
if err != nil {
writeError(w, err)
return
Expand All @@ -110,25 +144,25 @@ func (s *server) updateUser(w http.ResponseWriter, r *http.Request) {
log.Println("Update user endpoint called.")
id, _ := strconv.ParseInt(mux.Vars(r)["id"], 10, 32)

dtoReq := userDto{}
if err := json.NewDecoder(r.Body).Decode(&dtoReq); err != nil {
req := JSONUser{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, err)
return
}
dtoReq.ID = int(id)
user, err := s.service.updateUser(dtoReq)
req.ID = int(id)
err := s.service.updateUser(req)
if err != nil {
writeError(w, err)
return
}
dtoRes, err := json.Marshal(user)
res, err := json.Marshal(req)
if err != nil {
writeError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
if _, err := w.Write(dtoRes); err != nil {
if _, err := w.Write(res); err != nil {
writeError(w, err)
return
}
Expand Down
7 changes: 4 additions & 3 deletions cmds/server/migrator/migrations/001_create_users_table.up.sql
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
BEGIN;

CREATE TABLE IF NOT EXISTS users(
id INT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
email TEXT NOT NULL,
id INT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
password TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT,
age INT,
address TEXT
Expand Down
13 changes: 7 additions & 6 deletions cmds/server/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b4c2bee

Please sign in to comment.