Skip to content

Commit

Permalink
Adds implementation to manage http cookie on server
Browse files Browse the repository at this point in the history
Adds endpoints for logout and get an accessToken
Updates rating api and use manual validation of jwt which
is fetched from cookie

Signed-off-by: Shiv Verma <[email protected]>
  • Loading branch information
pratap0007 committed May 12, 2022
1 parent 5900a09 commit f2ca2ac
Show file tree
Hide file tree
Showing 10 changed files with 373 additions and 54 deletions.
47 changes: 47 additions & 0 deletions api/pkg/auth/service/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ type Service interface {
HubAuthenticate(res http.ResponseWriter, req *http.Request)
}

type cookie struct {
Name string
Value string
MaxAge int
Path string
HttpOnly bool
}

const (
RefreshToken = "refreshToken"
AccessToken = "accessToken"
Path = "/"
)

// New returns the auth service implementation.
func New(api app.Config) Service {
return &service{
Expand Down Expand Up @@ -192,6 +206,27 @@ func (s *service) HubAuthenticate(res http.ResponseWriter, req *http.Request) {
return
}

//Add cookie and response and send it to in header

refreshToken := cookie{
Name: RefreshToken,
Value: userTokens.Data.Refresh.Token,
MaxAge: int(r.jwtConfig.RefreshExpiresIn.Seconds()),
Path: Path,
HttpOnly: true,
}

accessToken := cookie{
Name: AccessToken,
Value: userTokens.Data.Access.Token,
MaxAge: int(r.jwtConfig.AccessExpiresIn.Seconds()),
Path: Path,
HttpOnly: true,
}

refreshToken.createCookie(res)
accessToken.createCookie(res)

res.WriteHeader(http.StatusOK)
if err := json.NewEncoder(res).Encode(userTokens); err != nil {
r.log.Error(err)
Expand Down Expand Up @@ -229,3 +264,15 @@ func List(res http.ResponseWriter, req *http.Request) {
return
}
}

func (c cookie) createCookie(res http.ResponseWriter) {

cookie := &http.Cookie{
Name: c.Name,
Value: c.Value,
MaxAge: c.MaxAge,
Path: c.Path,
HttpOnly: c.HttpOnly,
}
http.SetCookie(res, cookie)
}
36 changes: 30 additions & 6 deletions api/pkg/service/rating/rating.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/tektoncd/hub/api/pkg/app"
"github.com/tektoncd/hub/api/pkg/db/model"
"github.com/tektoncd/hub/api/pkg/service/validator"
"goa.design/goa/v3/security"
"gorm.io/gorm"
)

Expand Down Expand Up @@ -50,10 +51,21 @@ func New(api app.Config) rating.Service {
// Find user's rating for a resource
func (s *service) Get(ctx context.Context, p *rating.GetPayload) (*rating.GetResult, error) {

// Required scope to get the user rating for a resource
requiredScope := &security.JWTScheme{
RequiredScopes: []string{"rating:read"},
}

//Verify and validate the required scopes
newctx, err := s.JWTAuth(ctx, p.Session, requiredScope)
if err != nil {
return &rating.GetResult{Rating: 0}, err
}

req := request{
db: s.DB(ctx),
log: s.Logger(ctx),
userID: validator.UserID(ctx),
db: s.DB(newctx),
log: s.Logger(newctx),
userID: validator.UserID(newctx),
}

return req.getRating(p.ID)
Expand All @@ -62,10 +74,22 @@ func (s *service) Get(ctx context.Context, p *rating.GetPayload) (*rating.GetRes
// Update user's rating for a resource
func (s *service) Update(ctx context.Context, p *rating.UpdatePayload) error {

// Required scope to get the user rating for a resource
requiredScope := &security.JWTScheme{
RequiredScopes: []string{"rating:write"},
}

//Verify and validate the required scopes
newctx, err := s.JWTAuth(ctx, p.Session, requiredScope)

if err != nil {
return err
}

req := request{
db: s.DB(ctx),
log: s.Logger(ctx),
userID: validator.UserID(ctx),
db: s.DB(newctx),
log: s.Logger(newctx),
userID: validator.UserID(newctx),
}

return req.updateRating(p.ID, p.Rating)
Expand Down
27 changes: 13 additions & 14 deletions api/pkg/service/rating/rating_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,16 @@ import (
"github.com/tektoncd/hub/api/gen/http/rating/server"
"github.com/tektoncd/hub/api/gen/rating"
"github.com/tektoncd/hub/api/pkg/db/model"
"github.com/tektoncd/hub/api/pkg/service/validator"
"github.com/tektoncd/hub/api/pkg/testutils"
goa "goa.design/goa/v3/pkg"
)

func GetChecker(tc *testutils.TestConfig) *goahttpcheck.APIChecker {
service := validator.NewService(tc.APIConfig, "rating")
// service := validator.NewService(tc.APIConfig, "rating")
checker := goahttpcheck.New()
checker.Mount(server.NewGetHandler,
server.MountGetHandler,
rating.NewGetEndpoint(New(tc), service.JWTAuth))
rating.NewGetEndpoint(New(tc)))
return checker
}

Expand All @@ -45,7 +44,7 @@ func TestGet_Http_InvalidToken(t *testing.T) {
testutils.LoadFixtures(t, tc.FixturePath())

GetChecker(tc).Test(t, http.MethodGet, "/resource/1/rating").
WithHeader("Authorization", "invalidToken").Check().
WithCookie("accessToken", "invalidToken").Check().
HasStatus(401).Cb(func(r *http.Response) {
b, readErr := ioutil.ReadAll(r.Body)
assert.NoError(t, readErr)
Expand Down Expand Up @@ -73,7 +72,7 @@ func TestGet_Http_ExpiredToken(t *testing.T) {
jwt.TimeFunc = testutils.NowAfterDuration(tc.JWTConfig().AccessExpiresIn)

GetChecker(tc).Test(t, http.MethodGet, "/resource/1/rating").
WithHeader("Authorization", accessToken).Check().
WithCookie("accessToken", accessToken).Check().
HasStatus(401).Cb(func(r *http.Response) {
b, readErr := ioutil.ReadAll(r.Body)
assert.NoError(t, readErr)
Expand All @@ -100,7 +99,7 @@ func TestGet_Http_InvalidScopes(t *testing.T) {
jwt.TimeFunc = testutils.Now

GetChecker(tc).Test(t, http.MethodGet, "/resource/1/rating").
WithHeader("Authorization", accessToken).Check().
WithCookie("accessToken", accessToken).Check().
HasStatus(403).Cb(func(r *http.Response) {
b, readErr := ioutil.ReadAll(r.Body)
assert.NoError(t, readErr)
Expand All @@ -127,7 +126,7 @@ func TestGet_Http(t *testing.T) {
jwt.TimeFunc = testutils.Now

GetChecker(tc).Test(t, http.MethodGet, "/resource/1/rating").
WithHeader("Authorization", accessToken).Check().
WithCookie("accessToken", accessToken).Check().
HasStatus(200).Cb(func(r *http.Response) {
b, readErr := ioutil.ReadAll(r.Body)
assert.NoError(t, readErr)
Expand All @@ -154,7 +153,7 @@ func TestGet_Http_RatingNotFound(t *testing.T) {
jwt.TimeFunc = testutils.Now

GetChecker(tc).Test(t, http.MethodGet, "/resource/3/rating").
WithHeader("Authorization", accessToken).Check().
WithCookie("accessToken", accessToken).Check().
HasStatus(200).Cb(func(r *http.Response) {
b, readErr := ioutil.ReadAll(r.Body)
assert.NoError(t, readErr)
Expand All @@ -181,7 +180,7 @@ func TestGet_Http_ResourceNotFound(t *testing.T) {
jwt.TimeFunc = testutils.Now

GetChecker(tc).Test(t, http.MethodGet, "/resource/99/rating").
WithHeader("Authorization", accessToken).Check().
WithCookie("accessToken", accessToken).Check().
HasStatus(404).Cb(func(r *http.Response) {
b, readErr := ioutil.ReadAll(r.Body)
assert.NoError(t, readErr)
Expand All @@ -195,11 +194,11 @@ func TestGet_Http_ResourceNotFound(t *testing.T) {
}

func UpdateChecker(tc *testutils.TestConfig) *goahttpcheck.APIChecker {
service := validator.NewService(tc.APIConfig, "rating")
// service := validator.NewService(tc.APIConfig, "rating")
checker := goahttpcheck.New()
checker.Mount(server.NewUpdateHandler,
server.MountUpdateHandler,
rating.NewUpdateEndpoint(New(tc), service.JWTAuth))
rating.NewUpdateEndpoint(New(tc)))
return checker
}

Expand All @@ -218,7 +217,7 @@ func TestUpdate_Http(t *testing.T) {
data := []byte(`{"rating": 5}`)

UpdateChecker(tc).Test(t, http.MethodPut, "/resource/3/rating").
WithHeader("Authorization", accessToken).
WithCookie("accessToken", accessToken).
WithBody(data).Check().
HasStatus(200)

Expand All @@ -244,7 +243,7 @@ func TestUpdate_Http_Existing(t *testing.T) {
data := []byte(`{"rating": 2}`)

UpdateChecker(tc).Test(t, http.MethodPut, "/resource/1/rating").
WithHeader("Authorization", accessToken).
WithCookie("accessToken", accessToken).
WithBody(data).Check().
HasStatus(200)

Expand All @@ -270,7 +269,7 @@ func TestUpdate_Http_ResourceNotFound(t *testing.T) {
data := []byte(`{"rating": 2}`)

UpdateChecker(tc).Test(t, http.MethodPut, "/resource/99/rating").
WithHeader("Authorization", accessToken).
WithCookie("accessToken", accessToken).
WithBody(data).Check().
HasStatus(404).Cb(func(r *http.Response) {
b, readErr := ioutil.ReadAll(r.Body)
Expand Down
38 changes: 28 additions & 10 deletions api/pkg/service/rating/rating_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"testing"

"github.com/golang-jwt/jwt"
"github.com/stretchr/testify/assert"
"github.com/tektoncd/hub/api/gen/rating"
"github.com/tektoncd/hub/api/pkg/service/validator"
Expand All @@ -29,13 +30,18 @@ func TestGet(t *testing.T) {
testutils.LoadFixtures(t, tc.FixturePath())

// user with rating:read scope
user, _, err := tc.UserWithScopes("foo", "[email protected]", "rating:read")
user, accessToken, err := tc.UserWithScopes("foo", "[email protected]", "rating:read")

assert.Equal(t, user.Email, "[email protected]")
assert.NoError(t, err)

// Mocks the time
jwt.TimeFunc = testutils.Now

ratingSvc := New(tc)

ctx := validator.WithUserID(context.Background(), user.ID)
payload := &rating.GetPayload{ID: 1}
payload := &rating.GetPayload{ID: 1, Session: accessToken}
rat, err := ratingSvc.Get(ctx, payload)
assert.NoError(t, err)
assert.Equal(t, 5, rat.Rating)
Expand All @@ -46,13 +52,16 @@ func TestGet_RatingNotFound(t *testing.T) {
testutils.LoadFixtures(t, tc.FixturePath())

// user with rating:read scope
user, _, err := tc.UserWithScopes("foo", "[email protected]", "rating:read")
user, accessToken, err := tc.UserWithScopes("foo", "[email protected]", "rating:read")
assert.Equal(t, user.Email, "[email protected]")
assert.NoError(t, err)

// Mocks the time
jwt.TimeFunc = testutils.Now

ratingSvc := New(tc)
ctx := validator.WithUserID(context.Background(), user.ID)
payload := &rating.GetPayload{ID: 3}
payload := &rating.GetPayload{ID: 3, Session: accessToken}
rat, err := ratingSvc.Get(ctx, payload)
assert.NoError(t, err)
assert.Equal(t, -1, rat.Rating)
Expand All @@ -63,13 +72,16 @@ func TestGet_ResourceNotFound(t *testing.T) {
testutils.LoadFixtures(t, tc.FixturePath())

// user with rating:read scope
user, _, err := tc.UserWithScopes("foo", "[email protected]", "rating:read")
user, accessToken, err := tc.UserWithScopes("foo", "[email protected]", "rating:read")
assert.Equal(t, user.Email, "[email protected]")
assert.NoError(t, err)

// Mocks the time
jwt.TimeFunc = testutils.Now

ratingSvc := New(tc)
ctx := validator.WithUserID(context.Background(), user.ID)
payload := &rating.GetPayload{ID: 99}
payload := &rating.GetPayload{ID: 99, Session: accessToken}
_, err = ratingSvc.Get(ctx, payload)
assert.Error(t, err)
assert.EqualError(t, err, "resource not found")
Expand All @@ -80,13 +92,16 @@ func TestUpdate(t *testing.T) {
testutils.LoadFixtures(t, tc.FixturePath())

// user with rating:write scope
user, _, err := tc.UserWithScopes("foo", "[email protected]", "rating:write")
user, accessToken, err := tc.UserWithScopes("foo", "[email protected]", "rating:write")
assert.Equal(t, user.Email, "[email protected]")
assert.NoError(t, err)

// Mocks the time
jwt.TimeFunc = testutils.Now

ratingSvc := New(tc)
ctx := validator.WithUserID(context.Background(), user.ID)
payload := &rating.UpdatePayload{ID: 1, Rating: 3}
payload := &rating.UpdatePayload{ID: 1, Rating: 3, Session: accessToken}
err = ratingSvc.Update(ctx, payload)
assert.NoError(t, err)
}
Expand All @@ -96,13 +111,16 @@ func TestUpdate_ResourceNotFound(t *testing.T) {
testutils.LoadFixtures(t, tc.FixturePath())

// user with rating:write scope
user, _, err := tc.UserWithScopes("foo", "[email protected]", "rating:write")
user, accessToken, err := tc.UserWithScopes("foo", "[email protected]", "rating:write")
assert.Equal(t, user.Email, "[email protected]")
assert.NoError(t, err)

// Mocks the time
jwt.TimeFunc = testutils.Now

ratingSvc := New(tc)
ctx := validator.WithUserID(context.Background(), user.ID)
payload := &rating.UpdatePayload{ID: 99, Rating: 3}
payload := &rating.UpdatePayload{ID: 99, Rating: 3, Session: accessToken}
err = ratingSvc.Update(ctx, payload)
assert.Error(t, err)
assert.EqualError(t, err, "resource not found")
Expand Down
33 changes: 33 additions & 0 deletions api/pkg/testutils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package testutils
import (
"bytes"
"encoding/json"
"net/http"
"time"

"github.com/tektoncd/hub/api/pkg/db/model"
Expand Down Expand Up @@ -76,6 +77,16 @@ func (tc *TestConfig) UserWithScopes(name, email string, scopes ...string) (*mod
return user, accessToken, nil
}

// It returns a cookie object
func (tc *TestConfig) CreateCookie(name, value string) *http.Cookie {
return &http.Cookie{
Name: name,
Value: value,
MaxAge: 300,
Path: "/",
}
}

// RefreshTokenForUser returns refresh JWT for user with refresh:token scope
// User will have same github login and github name in db
func (tc *TestConfig) RefreshTokenForUser(name, email string) (*model.User, string, error) {
Expand Down Expand Up @@ -143,3 +154,25 @@ func (tc *TestConfig) AddScopesForUser(userID uint, scopes []string) error {
}
return nil
}

// AccessTokenForUser returns access JWT for user with access:token scope
// User will have same github login and github name in db
func (tc *TestConfig) AccessTokenForUser(name, email string) (*model.User, string, error) {

user := &model.User{Type: model.NormalUserType, Email: email}
if err := tc.DB().Where(&model.User{Email: email}).
FirstOrCreate(user).Error; err != nil {
return nil, "", err
}

token.Now = Now

req := token.Request{User: user, JWTConfig: tc.JWTConfig(), Provider: "github"}
accessToken, _, err := req.AccessJWT()

if err != nil {
return nil, "", err
}

return user, accessToken, nil
}
Loading

0 comments on commit f2ca2ac

Please sign in to comment.