// ValidateAuth validates that the user cookie is set up before calling the // handler passed as parameter. func ValidateAuth(h httputils.ContextHandler) httputils.ContextHandler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var ( sessionData *httputils.SessionData err error ok bool cookieStore *sessions.CookieStore session *sessions.Session cfg = ctx.Value("config").(*config.Config) ) cookieStore, ok = ctx.Value("cookieStore").(*sessions.CookieStore) if !ok { httputils.WriteError(w, http.StatusInternalServerError, "") return fmt.Errorf("validate auth: could not cast value as cookie store: %s", ctx.Value("cookieStore")) } session, err = cookieStore.Get(r, cfg.SessionCookieName) if err != nil { log.Println(err) http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return nil } sessionData, ok = session.Values["data"].(*httputils.SessionData) if !ok || sessionData.IsInvalid() { http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return nil } else if time.Now().After(sessionData.ExpiresAt) { session.Options.MaxAge = -1 session.Save(r, w) http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return nil } // Extend the session's lifetime. cfg, ok = ctx.Value("config").(*config.Config) if !ok { httputils.WriteError(w, http.StatusInternalServerError, "") return fmt.Errorf("validate auth: error casting config object: %s", ctx.Value("config")) } // Save session only if the session was extended. if extendSessionLifetime(sessionData, cfg.SessionLifeTime) { sessionData.ExpiresAt = time.Now().Add(cfg.SessionLifeTime) session.Save(r, w) } authenticatedContext := context.WithValue(ctx, "sessionData", sessionData) return h(authenticatedContext, w, r) } }
// FindByIDNumber finds student by honduran Id number or passport number. func (h *handler) FindByIDNumber(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var ( err error idNumber string vars = mux.Vars(r) ) type MappedStudent struct { ID int `json:"id"` IDNumber string `json:"idNumber"` FirstName string `json:"firstName"` LastName string `json:"lastName"` Email string `json:"email"` Status int `json:"status"` PlaceOfBirth string `json:"placeOfBirth"` Address string `json:"address"` Birthdate time.Time `json:"birthdate"` Gender bool `json:"gender"` Nationality string `json:"nationality"` PhoneNumber string `json:"phoneNumber"` } idNumber = vars["idNumber"] if idNumber == "" { httputils.WriteError(w, http.StatusBadRequest, "") return err } student, err := h.studentService.FindByIDNumber(idNumber) if err != nil { return err } else if student == nil { httputils.WriteError(w, http.StatusNotFound, "") return nil } response := &MappedStudent{ ID: student.ID, IDNumber: student.IDNumber, FirstName: student.FirstName, LastName: student.LastName, Email: student.Email, Status: student.Status, PlaceOfBirth: student.PlaceOfBirth, Address: student.Address, Birthdate: student.Birthdate, Gender: student.Gender, Nationality: student.Nationality, PhoneNumber: student.PhoneNumber, } return httputils.WriteJSON(w, http.StatusOK, response) }
// Edit a student. func (h *handler) Edit(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var ( err error payload struct { ID int IDNumber string FirstName string LastName string Email string PlaceOfBirth string Address string Birthdate time.Time Gender bool Nationality string PhoneNumber string } ) type Response struct { Success bool `json:"success"` ErrorMessage string `json:"errorMessage"` } if err = httputils.DecodeJSON(r.Body, &payload); err != nil { httputils.WriteError(w, http.StatusBadRequest, "") return nil } student := &models.Student{ ID: payload.ID, IDNumber: payload.IDNumber, FirstName: payload.FirstName, LastName: payload.LastName, Email: payload.Email, PlaceOfBirth: payload.PlaceOfBirth, Address: payload.Address, Birthdate: payload.Birthdate, Gender: payload.Gender, Nationality: payload.Nationality, PhoneNumber: payload.PhoneNumber, } err = h.studentService.Edit(student) if err != nil { if err == services.ErrDuplicatedStudentIDNumber { return httputils.WriteJSON(w, http.StatusOK, &Response{ Success: false, ErrorMessage: "El número de cédula o pasaporte ya existe!", }) } return err } return httputils.WriteJSON(w, http.StatusOK, &Response{ Success: true, }) }
// FindByID finds a single user by UserID. func (h *handler) FindByID(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var ( err error userID int vars = mux.Vars(r) ) type MappedUser struct { ID int `json:"id"` Username string `json:"username"` Email string `json:"email"` FirstName string `json:"firstName"` LastName string `json:"lastName"` Status int `json:"status"` IsAdmin bool `json:"isAdmin"` IsTeacher bool `json:"isTeacher"` } userID, err = strconv.Atoi(vars["id"]) if err != nil { httputils.WriteError(w, http.StatusBadRequest, "") return err } user, err := h.userService.FindByID(userID) if err != nil { return err } else if user == nil { httputils.WriteError(w, http.StatusNotFound, "") return nil } response := &MappedUser{ ID: user.ID, Username: user.Username, Email: user.Email, FirstName: user.FirstName, LastName: user.LastName, Status: user.Status, IsAdmin: user.IsAdmin, IsTeacher: user.IsTeacher, } return httputils.WriteJSON(w, http.StatusOK, response) }
// FindByID finds a Student by Id. func (h *handler) FindByID(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var ( err error studentID int vars = mux.Vars(r) ) type MappedStudent struct { ID int `json:"id"` Email string `json:"email"` FirstName string `json:"firstName"` LastName string `json:"lastName"` Status int `json:"status"` CreatedAt time.Time `json:"createdAt"` } studentID, err = strconv.Atoi(vars["id"]) if err != nil { httputils.WriteError(w, http.StatusBadRequest, "") return nil } student, err := h.studentService.FindByID(studentID) if err != nil { return err } else if student == nil { httputils.WriteError(w, http.StatusNotFound, "") return nil } response := &MappedStudent{ ID: student.ID, Email: student.Email, FirstName: student.FirstName, LastName: student.LastName, Status: student.Status, CreatedAt: student.CreatedAt, } return httputils.WriteJSON(w, http.StatusOK, response) }
// HandleHTTPError sets the appropriate headers to the response if a http // handler returned an error. This might be used in the future if different // types of errors are returned. func HandleHTTPError(h httputils.ContextHandler) httputils.ContextHandler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { err := h(ctx, w, r) if err != nil { httputils.WriteError(w, http.StatusInternalServerError, "") } return err } }
// Authorize validates privileges for the current user. Each route must have // an array of privileges that point which users can make a call to it. // // Note: // // It is assumed that ValidateAuth was called before this function, or at // least some other session check was done before this. func Authorize(h httputils.ContextHandler) httputils.ContextHandler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var ( requiredRoles []string sessionData *httputils.SessionData route routes.Route ok bool ) sessionData, ok = ctx.Value("sessionData").(*httputils.SessionData) if !ok { httputils.WriteError(w, http.StatusInternalServerError, "") return fmt.Errorf("authorize: could not cast value as session data: %s", ctx.Value("sessionData")) } route, ok = ctx.Value("route").(routes.Route) if !ok { httputils.WriteError(w, http.StatusInternalServerError, "") return fmt.Errorf("authorize: could not cast value as route: %s", ctx.Value("route")) } requiredRoles = route.RequiredRoles() if len(requiredRoles) == 0 { return h(ctx, w, r) } for _, role := range requiredRoles { if role == "ADMIN" && sessionData.IsAdmin { return h(ctx, w, r) } else if role == "TEACHER" && sessionData.IsTeacher { return h(ctx, w, r) } } http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) return nil } }
// Edit a user. func (h *handler) Edit(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var ( err error payload struct { ID int Username string Email string FirstName string LastName string Status int IsAdmin bool IsTeacher bool } ) type Response struct { Success bool `json:"success"` ErrorMessage string `json:"errorMessage"` } if err = httputils.DecodeJSON(r.Body, &payload); err != nil { httputils.WriteError(w, http.StatusBadRequest, "") return nil } user := &models.User{ ID: payload.ID, Username: payload.Username, Email: payload.Email, FirstName: payload.FirstName, LastName: payload.LastName, Status: payload.Status, IsAdmin: payload.IsAdmin, IsTeacher: payload.IsTeacher, } err = h.userService.Edit(user) if err != nil { if err == services.ErrDuplicatedUsername { return httputils.WriteJSON(w, http.StatusOK, &Response{ Success: false, ErrorMessage: "El nombre de usuario ya existe!", }) } return err } return httputils.WriteJSON(w, http.StatusOK, &Response{ Success: true, }) }
// Login does basic email/password login. // Checks: // - User must exist // - Passwords match // - User's status is Active // // If the checks pass, it sets up a session cookie. func (h *handler) Login(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var ( cookieStore = ctx.Value("cookieStore").(*sessions.CookieStore) err error loginForm struct { Identifier string Password string } ) if err = httputils.DecodeJSON(r.Body, &loginForm); err != nil { httputils.WriteError(w, http.StatusBadRequest, "") return nil } user, err := h.authService.BasicAuth(loginForm.Identifier, loginForm.Password) if err != nil { httputils.WriteError(w, http.StatusInternalServerError, "") return nil } else if user == nil { httputils.WriteError(w, http.StatusUnauthorized, "Usuario/clave inválidos") return nil } session, _ := cookieStore.New(r, h.cfg.SessionCookieName) session.Values["data"] = &httputils.SessionData{ UserID: user.ID, Email: user.Email, IsAdmin: user.IsAdmin, IsTeacher: user.IsTeacher, ExpiresAt: time.Now().Add(h.cfg.SessionLifeTime), } return session.Save(r, w) }
// Delete user. func (h *handler) Delete(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var ( err error payload struct { UserID int } ) if err = httputils.DecodeJSON(r.Body, &payload); err != nil { httputils.WriteError(w, http.StatusBadRequest, "") return nil } err = h.userService.Delete(payload.UserID) if err != nil { return err } return httputils.WriteJSON(w, http.StatusOK, nil) }
// ChangePasswordForCurrentUser changes the logged user's password. func (h *handler) ChangePasswordForCurrentUser(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var ( err error sessionData, _ = ctx.Value("sessionData").(*httputils.SessionData) payload struct { NewPassword string } ) if err = httputils.DecodeJSON(r.Body, &payload); err != nil { httputils.WriteError(w, http.StatusBadRequest, "") return nil } err = h.userService.ChangePassword(sessionData.UserID, payload.NewPassword) if err != nil && err != services.ErrRecordNotFound { return err } return httputils.WriteJSON(w, http.StatusOK, nil) }
// ChangePassword changes a user's password. func (h *handler) ChangePassword(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var ( err error payload struct { UserID int NewPassword string } ) if err = httputils.DecodeJSON(r.Body, &payload); err != nil { httputils.WriteError(w, http.StatusBadRequest, "") return nil } err = h.userService.ChangePassword(payload.UserID, payload.NewPassword) if err != nil && err != services.ErrRecordNotFound { return err } return httputils.WriteJSON(w, http.StatusOK, nil) }
// GetProfileForCurrentUser retrieves the logged user's information. func (h *handler) GetProfileForCurrentUser(ctx context.Context, w http.ResponseWriter, r *http.Request) error { sessionData, _ := ctx.Value("sessionData").(*httputils.SessionData) type Response struct { ID int `json:"id"` Username string `json:"username"` Email string `json:"email"` FirstName string `json:"firstName"` LastName string `json:"lastName"` Status int `json:"status"` IsAdmin bool `json:"isAdmin"` IsTeacher bool `json:"isTeacher"` } user, err := h.userService.FindByID(sessionData.UserID) if err != nil { return err } else if user == nil { httputils.WriteError(w, http.StatusNotFound, "") return nil } response := &Response{ ID: user.ID, Username: user.Username, Email: user.Email, FirstName: user.FirstName, LastName: user.LastName, Status: user.Status, IsAdmin: user.IsAdmin, IsTeacher: user.IsTeacher, } return httputils.WriteJSON(w, http.StatusOK, response) }