Compare commits

...

2 Commits

Author SHA1 Message Date
c1ae0706f9 Route now gets user ID from context with custom middleware
All checks were successful
gitea-deepak/gogmagog/pipeline/head This commit looks good
2021-01-17 17:33:40 -06:00
8eff1115c5 Fixes failing tests, but incompletely 2021-01-17 15:10:24 -06:00
22 changed files with 314 additions and 158 deletions

1
go.mod
View File

@@ -9,6 +9,7 @@ require (
github.com/golang-migrate/migrate/v4 v4.14.1
github.com/jackc/pgx/v4 v4.10.1
github.com/jmoiron/sqlx v1.2.0
github.com/lestrrat-go/jwx v1.0.6-0.20201127121120-26218808f029
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.5.1
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899

View File

@@ -2,57 +2,10 @@ package models_test
import (
"gitea.deepak.science/deepak/gogmagog/models"
"gitea.deepak.science/deepak/gogmagog/store"
)
func (e *errorStore) SelectActions() ([]*models.Action, error) {
return nil, e.error
}
func (e *errorStore) SelectActionByID(id int) (*models.Action, error) {
return nil, e.error
}
func (e *errorStore) InsertAction(action *models.Action) (int, error) {
return 0, e.error
}
func (e *errorStore) UpdateAction(action *models.Action) error {
return e.error
}
func (e *errorStore) SelectPlans() ([]*models.Plan, error) {
return nil, e.error
}
func (e *errorStore) SelectPlanByID(id int) (*models.Plan, error) {
return nil, e.error
}
func (e *errorStore) InsertPlan(plan *models.Plan) (int, error) {
return 0, e.error
}
func (e *errorStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action, error) {
return nil, e.error
}
func (e *errorStore) SelectUserByUsername(username string) (*models.User, error) {
return nil, e.error
}
func (e *errorStore) InsertUser(user *models.User) (int, error) {
return 0, e.error
}
func (e *errorStore) ConnectionLive() error {
return e.error
}
type errorStore struct {
error error
}
func getErrorModel(err error) *models.Model {
e := &errorStore{error: err}
return models.New(e)
str := store.GetErrorStoreForError(err, true)
return models.New(str)
}

View File

@@ -35,7 +35,7 @@ func TestErrorModelWrapping(t *testing.T) {
assert := assert.New(t)
m := getErrorModel(sql.ErrNoRows)
_, err := m.Plan(0)
_, err := m.Plan(0, 0)
assert.True(models.IsNotFoundError(err))
_, err = m.Action(0)
assert.True(models.IsNotFoundError(err))

View File

@@ -11,9 +11,9 @@ type Store interface {
SelectActionByID(id int) (*Action, error)
InsertAction(action *Action) (int, error)
UpdateAction(action *Action) error
SelectPlans() ([]*Plan, error)
SelectPlanByID(id int) (*Plan, error)
InsertPlan(plan *Plan) (int, error)
SelectPlans(userID int) ([]*Plan, error)
SelectPlanByID(id int, userID int) (*Plan, error)
InsertPlan(plan *Plan, userID int) (int, error)
SelectActionsByPlanID(plan *Plan) ([]*Action, error)
SelectUserByUsername(username string) (*User, error)
InsertUser(user *User) (int, error)

View File

@@ -11,11 +11,12 @@ func TestModelActions(t *testing.T) {
assert := assert.New(t)
a1 := &models.Action{ActionID: 3}
a2 := &models.Action{ActionID: 4}
userID := 3
p := &models.Plan{PlanID: 6}
str, _ := store.GetInMemoryStore()
str.InsertAction(a1)
str.InsertPlan(p)
str.InsertPlan(p, userID)
m := models.New(str)
actions, err := m.Actions()
@@ -36,21 +37,22 @@ func TestModelActions(t *testing.T) {
func TestModelPlanMethods(t *testing.T) {
assert := assert.New(t)
userID := 3
a1 := &models.Action{ActionID: 3, PlanID: 1}
a2 := &models.Action{ActionID: 4}
p := &models.Plan{}
str, _ := store.GetInMemoryStore()
str.InsertPlan(p)
str.InsertPlan(p, userID)
str.InsertAction(a1)
str.InsertAction(a2)
m := models.New(str)
plans, err := m.Plans()
plans, err := m.Plans(userID)
assert.Nil(err)
assert.Equal(1, len(plans))
firstPlan, err := m.Plan(1)
firstPlan, err := m.Plan(1, userID)
assert.Nil(err)
assert.EqualValues(1, firstPlan.PlanID)
@@ -58,7 +60,7 @@ func TestModelPlanMethods(t *testing.T) {
assert.Nil(err)
assert.Equal(1, len(actions))
planId, err := m.AddPlan(&models.Plan{})
planId, err := m.AddPlan(&models.Plan{}, userID)
assert.Nil(err)
assert.EqualValues(2, planId)
}

View File

@@ -12,19 +12,19 @@ type Plan struct {
}
// Plans returns all plans in the model.
func (m *Model) Plans() ([]*Plan, error) {
return m.SelectPlans()
func (m *Model) Plans(userID int) ([]*Plan, error) {
return m.SelectPlans(userID)
}
// Plan returns a single plan from the store by plan_id.
func (m *Model) Plan(id int) (*Plan, error) {
plan, err := m.SelectPlanByID(id)
func (m *Model) Plan(id int, userID int) (*Plan, error) {
plan, err := m.SelectPlanByID(id, userID)
return plan, wrapNotFound(err)
}
// AddPlan inserts a given plan into the store, returning the generated PlanID. The provided PlanID is ignored.
func (m *Model) AddPlan(plan *Plan) (int, error) {
return m.InsertPlan(plan)
func (m *Model) AddPlan(plan *Plan, userID int) (int, error) {
return m.InsertPlan(plan, userID)
}
// GetActions returns the actions associated with a particular plan.

View File

@@ -19,7 +19,7 @@ func TestModelUsers(t *testing.T) {
// password := password
user1 := &models.User{Username: username, DisplayName: "Ted Est", Password: []byte("$2y$05$6SVV35GX4cB4PDPhRaDD/exsL.HV8QtMMr60YL6dLyqtX4l58q.cy")}
str, _ := store.GetInMemoryStore()
str.InsertPlan(p)
str.InsertPlan(p, 3)
str.InsertAction(a1)
str.InsertAction(a2)
str.InsertUser(user1)

View File

@@ -3,8 +3,10 @@ package routes
import (
"encoding/json"
"gitea.deepak.science/deepak/gogmagog/models"
"gitea.deepak.science/deepak/gogmagog/tokens"
"github.com/go-chi/chi"
"io"
"log"
"net/http"
"strconv"
)
@@ -20,7 +22,14 @@ func NewPlanRouter(m *models.Model) http.Handler {
func getAllPlansFunc(m *models.Model) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
plans, err := m.Plans()
userID, err := tokens.GetUserID(r.Context())
if err != nil {
log.Print(err)
unauthorizedHandler(w, r)
return
}
plans, err := m.Plans(userID)
if err != nil {
serverError(w, err)
return
@@ -34,12 +43,20 @@ func getAllPlansFunc(m *models.Model) http.HandlerFunc {
func getPlanByIDFunc(m *models.Model) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userID, err := tokens.GetUserID(r.Context())
if err != nil {
log.Print(err)
unauthorizedHandler(w, r)
return
}
id, err := strconv.Atoi(chi.URLParam(r, "planid"))
if err != nil {
notFoundHandler(w, r)
return
}
plan, err := m.Plan(id)
// todo get real user id
plan, err := m.Plan(id, userID)
if err != nil {
if models.IsNotFoundError(err) {
notFoundHandler(w, r)
@@ -63,12 +80,18 @@ type createPlanResponse struct {
func postPlanFunc(m *models.Model) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userID, err := tokens.GetUserID(r.Context())
if err != nil {
log.Print(err)
unauthorizedHandler(w, r)
return
}
r.Body = http.MaxBytesReader(w, r.Body, 1024)
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
var p models.Plan
err := dec.Decode(&p)
err = dec.Decode(&p)
if err != nil {
badRequestError(w, err)
return
@@ -81,12 +104,12 @@ func postPlanFunc(m *models.Model) http.HandlerFunc {
// Map the fields we allow to be set to the plan to be created.
plan := &models.Plan{PlanDate: p.PlanDate, UserID: p.UserID}
id, err := m.AddPlan(plan)
id, err := m.AddPlan(plan, userID)
if err != nil {
serverError(w, err)
return
}
plan, err = m.Plan(id)
plan, err = m.Plan(id, userID)
if err != nil {
serverError(w, err)
return

View File

@@ -3,6 +3,7 @@ package routes_test
import (
"gitea.deepak.science/deepak/gogmagog/models"
"gitea.deepak.science/deepak/gogmagog/routes"
"gitea.deepak.science/deepak/gogmagog/tokens"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
@@ -11,12 +12,14 @@ import (
"time"
)
var sampleContext = tokens.GetContextForUserID(3)
func TestEmptyPlans(t *testing.T) {
// set up
assert := assert.New(t)
m := getEmptyModel()
router := routes.NewPlanRouter(m)
req, _ := http.NewRequest("GET", "/", nil)
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
rr := httptest.NewRecorder()
@@ -39,9 +42,9 @@ func TestOnePlan(t *testing.T) {
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: 3}
m := getEmptyModel()
m.AddPlan(p)
m.AddPlan(p, 3)
router := routes.NewPlanRouter(m)
req, _ := http.NewRequest("GET", "/", nil)
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
rr := httptest.NewRecorder()
@@ -71,7 +74,7 @@ func TestErrorPlan(t *testing.T) {
m := getErrorModel("Model always errors")
router := routes.NewPlanRouter(m)
req, _ := http.NewRequest("GET", "/", nil)
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
rr := httptest.NewRecorder()
@@ -93,7 +96,7 @@ func TestEmptyPlanErrorWriter(t *testing.T) {
m := getEmptyModel()
router := routes.NewPlanRouter(m)
req, _ := http.NewRequest("GET", "/", nil)
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/", nil)
rr := NewBadWriter()
@@ -112,9 +115,9 @@ func TestOnePlanByID(t *testing.T) {
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: 3}
m := getEmptyModel()
m.AddPlan(p)
m.AddPlan(p, 3)
router := routes.NewPlanRouter(m)
req, _ := http.NewRequest("GET", "/1", nil)
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/1", nil)
rr := httptest.NewRecorder()
@@ -142,7 +145,7 @@ func TestErrorPlanByID(t *testing.T) {
m := getErrorModel("Model always errors")
router := routes.NewPlanRouter(m)
req, _ := http.NewRequest("GET", "/5", nil)
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/5", nil)
rr := httptest.NewRecorder()
@@ -164,10 +167,10 @@ func TestEmptyPlanErrorWriterByID(t *testing.T) {
p := &models.Plan{PlanID: 1, PlanDate: &planDate}
m := getEmptyModel()
m.AddPlan(p)
m.AddPlan(p, 3)
router := routes.NewPlanRouter(m)
req, _ := http.NewRequest("GET", "/1", nil)
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/1", nil)
rr := NewBadWriter()
@@ -187,7 +190,7 @@ func TestNotFoundPlanByIDText(t *testing.T) {
m := getEmptyModel()
router := routes.NewPlanRouter(m)
req, _ := http.NewRequest("GET", "/wo", nil)
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/wo", nil)
rr := httptest.NewRecorder()
@@ -206,7 +209,7 @@ func TestNotFoundPlanByIDEmpty(t *testing.T) {
m := getEmptyModel()
router := routes.NewPlanRouter(m)
req, _ := http.NewRequest("GET", "/1", nil)
req, _ := http.NewRequestWithContext(sampleContext, "GET", "/1", nil)
rr := httptest.NewRecorder()

View File

@@ -67,7 +67,7 @@ func TestExtraFieldActionPostJSON(t *testing.T) {
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
p := &models.Plan{PlanID: 6, PlanDate: &planDate}
m := getEmptyModel()
m.AddPlan(p)
m.AddPlan(p, 3)
router := routes.NewActionRouter(m)
data := []byte(`{
"completed_on": "2021-01-01T00:00:00Z",
@@ -94,7 +94,7 @@ func TestEmptyBodyActionPost(t *testing.T) {
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
p := &models.Plan{PlanID: 6, PlanDate: &planDate}
m := getEmptyModel()
m.AddPlan(p)
m.AddPlan(p, 3)
router := routes.NewActionRouter(m)
data := []byte(``)
req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data))
@@ -118,7 +118,7 @@ func TestTwoBodyActionPost(t *testing.T) {
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
p := &models.Plan{PlanID: 6, PlanDate: &planDate}
m := getEmptyModel()
m.AddPlan(p)
m.AddPlan(p, 3)
router := routes.NewActionRouter(m)
data := []byte(`{
"plan_id": 5

View File

@@ -17,12 +17,13 @@ func TestCreatePlanRoute(t *testing.T) {
// set up
assert := assert.New(t)
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: 3}
userID := 3
p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: int64(userID)}
m := getEmptyModel()
m.AddPlan(p)
m.AddPlan(p, userID)
router := routes.NewPlanRouter(m)
data, _ := json.Marshal(p)
req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data))
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
@@ -50,16 +51,17 @@ func TestPureJSON(t *testing.T) {
// set up
assert := assert.New(t)
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
p := &models.Plan{PlanID: 1, PlanDate: &planDate, UserID: 3}
userID := 3
p := &models.Plan{PlanID: 1, PlanDate: &planDate, UserID: int64(userID)}
m := getEmptyModel()
m.AddPlan(p)
m.AddPlan(p, userID)
router := routes.NewPlanRouter(m)
data := []byte(`{
"plan_date": "2021-01-01T00:00:00Z",
"plan_id": 1,
"user_id": 3
}`)
req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data))
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
@@ -87,16 +89,17 @@ func TestExtraFieldJSON(t *testing.T) {
// set up
assert := assert.New(t)
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
p := &models.Plan{PlanID: 6, PlanDate: &planDate}
userID := 3
p := &models.Plan{PlanID: 6, PlanDate: &planDate, UserID: int64(userID)}
m := getEmptyModel()
m.AddPlan(p)
m.AddPlan(p, userID)
router := routes.NewPlanRouter(m)
data := []byte(`{
"plan_date": "2021-01-01T00:00:00Z",
"plan_id": 5,
"plan_sabotage": "omg"
}`)
req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data))
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
@@ -114,12 +117,13 @@ func TestEmptyBody(t *testing.T) {
// set up
assert := assert.New(t)
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
userID := 3
p := &models.Plan{PlanID: 6, PlanDate: &planDate}
m := getEmptyModel()
m.AddPlan(p)
m.AddPlan(p, userID)
router := routes.NewPlanRouter(m)
data := []byte(``)
req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data))
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
@@ -140,7 +144,7 @@ func TestTwoBody(t *testing.T) {
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
p := &models.Plan{PlanID: 6, PlanDate: &planDate}
m := getEmptyModel()
m.AddPlan(p)
m.AddPlan(p, 3)
router := routes.NewPlanRouter(m)
data := []byte(`{
"plan_date": "2021-01-01T00:00:00Z",
@@ -149,7 +153,7 @@ func TestTwoBody(t *testing.T) {
"plan_date": "2021-01-01T00:00:00Z",
"plan_id": 6
}`)
req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data))
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
@@ -174,7 +178,7 @@ func TestErrorCreatePlan(t *testing.T) {
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
p := &models.Plan{PlanID: 6, PlanDate: &planDate}
data, _ := json.Marshal(p)
req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data))
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
@@ -200,7 +204,7 @@ func TestErrorOnRetrieveCreatePlan(t *testing.T) {
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
p := &models.Plan{PlanID: 6, PlanDate: &planDate}
data, _ := json.Marshal(p)
req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data))
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
@@ -222,10 +226,10 @@ func TestErrorWriterCreatePlan(t *testing.T) {
p := &models.Plan{PlanID: 6, PlanDate: &planDate}
m := getEmptyModel()
m.AddPlan(p)
m.AddPlan(p, 3)
router := routes.NewPlanRouter(m)
data, _ := json.Marshal(p)
req, _ := http.NewRequest("POST", "/", bytes.NewBuffer(data))
req, _ := http.NewRequestWithContext(sampleContext, "POST", "/", bytes.NewBuffer(data))
req.Header.Set("Content-Type", "application/json")
rr := NewBadWriter()

View File

@@ -5,7 +5,6 @@ import (
"gitea.deepak.science/deepak/gogmagog/models"
"gitea.deepak.science/deepak/gogmagog/tokens"
"github.com/go-chi/chi"
"github.com/go-chi/jwtauth"
"net/http"
)
@@ -15,12 +14,7 @@ func NewRouter(m *models.Model, tok tokens.Toker) http.Handler {
router.MethodNotAllowed(methodNotAllowedHandler)
router.NotFound(notFoundHandler)
router.Group(func(r chi.Router) {
r.Use(tok.Verifier())
// Handle valid / invalid tokens. In this example, we use
// the provided authenticator middleware, but you can write your
// own very easily, look at the Authenticator method in jwtauth.go
// and tweak it, its not scary.
r.Use(jwtauth.Authenticator)
r.Use(tok.Authenticator)
r.Mount("/actions", NewActionRouter(m))
r.Mount("/plans", NewPlanRouter(m))
})

View File

@@ -27,15 +27,15 @@ func (e *errorStore) UpdateAction(action *models.Action) error {
return nil
}
func (e *errorStore) SelectPlans() ([]*models.Plan, error) {
func (e *errorStore) SelectPlans(userID int) ([]*models.Plan, error) {
return nil, e.error
}
func (e *errorStore) SelectPlanByID(id int) (*models.Plan, error) {
func (e *errorStore) SelectPlanByID(id int, userID int) (*models.Plan, error) {
return nil, e.error
}
func (e *errorStore) InsertPlan(plan *models.Plan) (int, error) {
func (e *errorStore) InsertPlan(plan *models.Plan, userID int) (int, error) {
if e.errorOnInsert {
return 0, e.error
}
@@ -66,8 +66,14 @@ type errorStore struct {
errorOnInsert bool
}
// GetErrorStore returns a models.Store that always errors. This is useful for testirng purposes.
// GetErrorStore returns a models.Store that always errors. This is useful for testing purposes.
func GetErrorStore(errorMsg string, errorOnInsert bool) models.Store {
e := &errorStore{error: fmt.Errorf(errorMsg), errorOnInsert: errorOnInsert}
return e
}
// GetErrorStoreForError returns a models.Store that always errors with the provided error.
func GetErrorStoreForError(err error, errorOnInsert bool) models.Store {
e := &errorStore{error: err, errorOnInsert: errorOnInsert}
return e
}

View File

@@ -41,15 +41,15 @@ func TestErrorPlanMethods(t *testing.T) {
str := store.GetErrorStore("sntahoeu", true)
str2 := store.GetErrorStore("sntahoeu", false)
_, err := str.SelectPlans()
_, err := str.SelectPlans(3)
assert.NotNil(err)
_, err = str.InsertPlan(&models.Plan{})
_, err = str.InsertPlan(&models.Plan{}, 3)
assert.NotNil(err)
_, err = str2.InsertPlan(&models.Plan{})
_, err = str2.InsertPlan(&models.Plan{}, 3)
assert.Nil(err)
_, err = str.SelectPlanByID(5)
_, err = str.SelectPlanByID(5, 3)
assert.NotNil(err)
}

View File

@@ -64,22 +64,29 @@ func (store *inMemoryStore) UpdateAction(action *models.Action) error {
}
func (store *inMemoryStore) SelectPlans() ([]*models.Plan, error) {
return store.plans, nil
func (store *inMemoryStore) SelectPlans(userID int) ([]*models.Plan, error) {
ret := make([]*models.Plan, 0)
for _, plan := range store.plans {
if int(plan.UserID) == userID {
ret = append(ret, plan)
}
}
return ret, nil
}
func (store *inMemoryStore) SelectPlanByID(id int) (*models.Plan, error) {
func (store *inMemoryStore) SelectPlanByID(id int, userID int) (*models.Plan, error) {
for _, plan := range store.plans {
if id == int(plan.PlanID) {
if id == int(plan.PlanID) && (userID == int(plan.UserID)) {
return plan, nil
}
}
return nil, sql.ErrNoRows
}
func (store *inMemoryStore) InsertPlan(plan *models.Plan) (int, error) {
func (store *inMemoryStore) InsertPlan(plan *models.Plan, userID int) (int, error) {
id := len(store.plans) + 1
plan.PlanID = int64(id)
plan.UserID = int64(userID)
store.plans = append(store.plans, plan)
return id, nil
}

View File

@@ -55,24 +55,24 @@ func TestInMemoryActionMethods(t *testing.T) {
func TestInMemoryPlanMethods(t *testing.T) {
assert := assert.New(t)
str, _ := store.GetInMemoryStore()
userID := 1
p := &models.Plan{}
plans, err := str.SelectPlans()
plans, err := str.SelectPlans(userID)
assert.Nil(err)
assert.EqualValues(0, len(plans))
id, err := str.InsertPlan(p)
plans, err = str.SelectPlans()
id, err := str.InsertPlan(p, userID)
plans, err = str.SelectPlans(userID)
assert.Nil(err)
assert.EqualValues(1, len(plans))
retrievedPlan, err := str.SelectPlanByID(id)
retrievedPlan, err := str.SelectPlanByID(id, userID)
assert.Nil(err)
assert.Equal(retrievedPlan, p)
_, err = str.SelectPlanByID(135135)
_, err = str.SelectPlanByID(135135, userID)
assert.NotNil(err)
}

View File

@@ -97,29 +97,30 @@ func (store *postgresStore) UpdateAction(action *models.Action) error {
}
func (store *postgresStore) SelectPlans() ([]*models.Plan, error) {
func (store *postgresStore) SelectPlans(userID int) ([]*models.Plan, error) {
queryString := store.db.Rebind("SELECT plan_id, plan_date, user_id FROM plans WHERE user_id = ?")
plans := make([]*models.Plan, 0)
err := store.db.Select(&plans, "SELECT plan_id, plan_date, user_id FROM plans")
err := store.db.Select(&plans, queryString, userID)
if err != nil {
return nil, err
}
return plans, nil
}
func (store *postgresStore) SelectPlanByID(id int) (*models.Plan, error) {
func (store *postgresStore) SelectPlanByID(id int, userID int) (*models.Plan, error) {
plan := models.Plan{}
err := store.db.Get(&plan, store.db.Rebind("SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = ?"), id)
err := store.db.Get(&plan, store.db.Rebind("SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = ? AND user_id = ?"), id, userID)
if err != nil {
return nil, err
}
return &plan, nil
}
func (store *postgresStore) InsertPlan(plan *models.Plan) (int, error) {
func (store *postgresStore) InsertPlan(plan *models.Plan, userID int) (int, error) {
queryString := store.db.Rebind("INSERT INTO plans (plan_date, user_id) VALUES (?, ?) RETURNING plan_id")
tx := store.db.MustBegin()
var id int
err := tx.Get(&id, queryString, plan.PlanDate, plan.UserID)
err := tx.Get(&id, queryString, plan.PlanDate, userID)
if err != nil {
tx.Rollback()
return -1, err

View File

@@ -19,9 +19,11 @@ func TestSelectPlans(t *testing.T) {
str, mock := getDbMock(t)
rows := sqlmock.NewRows([]string{"plan_id", "plan_date", "user_id"}).AddRow(idToUse, currentTime, userIDToUse)
mock.ExpectQuery("^SELECT plan_id, plan_date, user_id FROM plans$").WillReturnRows(rows)
mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans WHERE user_id = \$1`).
WithArgs(userIDToUse).
WillReturnRows(rows)
plans, err := str.SelectPlans()
plans, err := str.SelectPlans(userIDToUse)
assert.Nil(err)
assert.Equal(1, len(plans))
plan := plans[0]
@@ -44,9 +46,11 @@ func TestSelectPlanByID(t *testing.T) {
str, mock := getDbMock(t)
rows := sqlmock.NewRows([]string{"plan_id", "plan_date", "user_id"}).AddRow(idToUse, currentTime, userIDToUse)
mock.ExpectQuery("^SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = \\$1$").WithArgs(idToUse).WillReturnRows(rows)
mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = \$1 AND user_id = \$2$`).
WithArgs(idToUse, userIDToUse).
WillReturnRows(rows)
plan, err := str.SelectPlanByID(idToUse)
plan, err := str.SelectPlanByID(idToUse, userIDToUse)
assert.Nil(err)
assert.EqualValues(idToUse, plan.PlanID)
assert.Equal(currentTime, *plan.PlanDate)
@@ -64,8 +68,9 @@ func TestInsertPlan(t *testing.T) {
str, mock := getDbMock(t)
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
userID := 2
badUserID := 7
plan := &models.Plan{PlanDate: &planDate, UserID: int64(userID)}
plan := &models.Plan{PlanDate: &planDate, UserID: int64(badUserID)}
idToUse := 8
@@ -78,7 +83,7 @@ func TestInsertPlan(t *testing.T) {
mock.ExpectCommit()
// function under test
insertedId, err := str.InsertPlan(plan)
insertedId, err := str.InsertPlan(plan, userID)
// check results
assert.Nil(err)
assert.EqualValues(idToUse, insertedId)
@@ -95,7 +100,8 @@ func TestInsertPlanErr(t *testing.T) {
str, mock := getDbMock(t)
planDate, _ := time.Parse("2006-01-02", "2021-01-01")
userID := 2
plan := &models.Plan{PlanDate: &planDate, UserID: 2}
badUserID := 7
plan := &models.Plan{PlanDate: &planDate, UserID: int64(badUserID)}
mock.ExpectBegin()
mock.ExpectQuery(`^INSERT INTO plans \(plan_date, user_id\) VALUES \(\$1, \$2\) RETURNING plan_id$`).
@@ -104,7 +110,7 @@ func TestInsertPlanErr(t *testing.T) {
mock.ExpectRollback()
// function under test
_, err := str.InsertPlan(plan)
_, err := str.InsertPlan(plan, userID)
// check results
assert.NotNil(err)
if err := mock.ExpectationsWereMet(); err != nil {
@@ -132,7 +138,7 @@ func TestInsertPlanCommitErr(t *testing.T) {
mock.ExpectCommit().WillReturnError(fmt.Errorf("another error example"))
// function under test
_, err := str.InsertPlan(plan)
_, err := str.InsertPlan(plan, userID)
// check results
assert.NotNil(err)
if err := mock.ExpectationsWereMet(); err != nil {
@@ -148,9 +154,11 @@ func TestErrPlanByID(t *testing.T) {
str, mock := getDbMock(t)
mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = \$1$`).WithArgs(idToUse).WillReturnError(fmt.Errorf("example error"))
mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans WHERE plan_id = \$1 AND user_id = \$2$`).
WithArgs(idToUse, 8).
WillReturnError(fmt.Errorf("example error"))
plan, err := str.SelectPlanByID(idToUse)
plan, err := str.SelectPlanByID(idToUse, 8)
assert.NotNil(err)
assert.Nil(plan)
@@ -164,9 +172,11 @@ func TestErrPlans(t *testing.T) {
assert := assert.New(t)
str, mock := getDbMock(t)
mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans$`).WillReturnError(fmt.Errorf("example error"))
mock.ExpectQuery(`^SELECT plan_id, plan_date, user_id FROM plans WHERE user_id = \$1$`).
WithArgs(8).
WillReturnError(fmt.Errorf("example error"))
// function under test
plans, err := str.SelectPlans()
plans, err := str.SelectPlans(8)
// test results
assert.Nil(plans)
assert.NotNil(err)

71
tokens/middleware.go Normal file
View File

@@ -0,0 +1,71 @@
package tokens
import (
"context"
"fmt"
"log"
"net/http"
"strings"
)
type contextKey struct {
name string
}
var userIDCtxKey = &contextKey{"UserID"}
func unauthorized(w http.ResponseWriter, r *http.Request) {
code := http.StatusUnauthorized
http.Error(w, http.StatusText(code), code)
}
// TokenFromHeader tries to retreive the token string from the
// "Authorization" reqeust header: "Authorization: BEARER T".
func TokenFromHeader(r *http.Request) string {
// Get token from authorization header.
bearer := r.Header.Get("Authorization")
if len(bearer) > 7 && strings.ToUpper(bearer[0:6]) == "BEARER" {
return bearer[7:]
}
return ""
}
func (tok *jwtToker) Authenticator(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenString := TokenFromHeader(r)
if tokenString == "" {
log.Print("No valid token found")
unauthorized(w, r)
return
}
userID, err := tok.DecodeTokenString(tokenString)
if err != nil {
log.Printf("Error while verifying token: %s", err)
unauthorized(w, r)
return
}
log.Printf("Got user with ID: [%d]", userID)
ctx := context.WithValue(r.Context(), userIDCtxKey, userID)
// Authenticated
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// GetUserID is a convenience method that gets the user ID from the context.
// I hate the fact that we're passing user ID on the context, but it is more
// idiomatic Go than any type shenanigans.
func GetUserID(ctx context.Context) (int, error) {
userID, ok := ctx.Value(userIDCtxKey).(int64)
if !ok {
return -1, fmt.Errorf("Could not parse user ID [%s] from context", ctx.Value(userIDCtxKey))
}
return int(userID), nil
}
// GetContextForUserID is a test helper method that creates a context with user ID set.
func GetContextForUserID(userID int) context.Context {
return context.WithValue(context.Background(), userIDCtxKey, int64(userID))
}

56
tokens/middleware_test.go Normal file
View File

@@ -0,0 +1,56 @@
package tokens_test
import (
"gitea.deepak.science/deepak/gogmagog/tokens"
"github.com/stretchr/testify/assert"
"net/http"
"testing"
)
var (
url = ""
authKey = "Authorization"
)
func requestWithAuth(header string) *http.Request {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add(authKey, header)
return req
}
func TestHeaderParseBasic(t *testing.T) {
assert := assert.New(t)
header := "Bearer testing"
req := requestWithAuth(header)
assert.Equal("testing", tokens.TokenFromHeader(req))
}
func TestHeaderParseNoSpace(t *testing.T) {
assert := assert.New(t)
header := "Bearerxtesting"
req := requestWithAuth(header)
assert.Equal("testing", tokens.TokenFromHeader(req))
}
func TestHeaderParseUnicode(t *testing.T) {
assert := assert.New(t)
header := "Bearer 🌸"
req := requestWithAuth(header)
assert.Equal("🌸", tokens.TokenFromHeader(req))
}
func TestHeaderParseMalformed(t *testing.T) {
assert := assert.New(t)
header := "testing"
req := requestWithAuth(header)
assert.Equal("", tokens.TokenFromHeader(req))
}

View File

@@ -1,8 +1,10 @@
package tokens
import (
"fmt"
"gitea.deepak.science/deepak/gogmagog/models"
"github.com/go-chi/jwtauth"
"github.com/lestrrat-go/jwx/jwt"
"net/http"
"time"
)
@@ -10,8 +12,8 @@ import (
// Toker represents a tokenizer, capable of encoding and verifying tokens.
type Toker interface {
EncodeUser(user *models.UserNoPassword) string
VerifyTokenString(tokenString string) error
Verifier() func(http.Handler) http.Handler
DecodeTokenString(tokenString string) (int64, error)
Authenticator(http.Handler) http.Handler
}
type jwtToker struct {
@@ -37,11 +39,32 @@ func (tok *jwtToker) EncodeUser(user *models.UserNoPassword) string {
return tokenString
}
func (tok *jwtToker) VerifyTokenString(tokenString string) error {
_, err := jwtauth.VerifyToken(tok.tokenAuth, tokenString)
return err
}
func (tok *jwtToker) DecodeTokenString(tokenString string) (int64, error) {
token, err := tok.tokenAuth.Decode(tokenString)
if err != nil {
return -1, fmt.Errorf("Error decoding token")
}
func (tok *jwtToker) Verifier() func(http.Handler) http.Handler {
return jwtauth.Verifier(tok.tokenAuth)
if token == nil {
return -1, fmt.Errorf("Token was nil")
}
err = jwt.Validate(
token,
jwt.WithIssuer("gogmagog.deepak.science"),
jwt.WithAudience("gogmagog.deepak.science"),
)
if err != nil {
return -1, err
}
userIDRaw, ok := token.Get("user_id")
if !ok {
return -1, fmt.Errorf("error finding user_id claim")
}
userID, ok := userIDRaw.(float64)
if !ok {
return -1, fmt.Errorf("Could not parse [%s] as userID", userIDRaw)
}
return int64(userID), nil
}

View File

@@ -17,6 +17,8 @@ func TestBasic(t *testing.T) {
}
token := toker.EncodeUser(usr)
assert.Nil(toker.VerifyTokenString(token))
assert.NotNil(tokens.New("bad secret").VerifyTokenString(token))
_, err := toker.DecodeTokenString(token)
assert.Nil(err)
_, err = tokens.New("bad secret").DecodeTokenString(token)
assert.NotNil(err)
}