aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorMax Resnick <max@ofmax.li>2020-08-14 23:13:41 -0700
committerMax Resnick <max@ofmax.li>2020-11-08 07:57:13 -0800
commit689a57ec4a444f8233fe2e5ec7ceb0903218218d (patch)
tree1bcfe6786c38b4ae11997d5d97dc3c5fba747b97 /internal
parent77c2e6aca2dc0f851f55e30a0f49c9ee7c2c952e (diff)
downloadiserv-master.tar.gz
feat: working login gauthHEADmaster
Diffstat (limited to '')
-rw-r--r--internal/auth/handler.go105
-rw-r--r--internal/auth/handler_test.go60
-rw-r--r--internal/auth/model.go49
-rw-r--r--internal/auth/repo.go8
-rw-r--r--internal/auth/service.go97
-rw-r--r--internal/auth/service_test.go133
-rw-r--r--internal/db/redis/auth.go108
-rw-r--r--internal/image/service.go2
-rw-r--r--internal/mock/mock_auth/mock_auth.go196
-rw-r--r--internal/test/helpers.go14
10 files changed, 771 insertions, 1 deletions
diff --git a/internal/auth/handler.go b/internal/auth/handler.go
new file mode 100644
index 0000000..992608c
--- /dev/null
+++ b/internal/auth/handler.go
@@ -0,0 +1,105 @@
+package auth
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "log"
+ "net/http"
+
+ "github.com/alexedwards/scs/v2"
+ "golang.org/x/oauth2"
+)
+
+var (
+ profileURL = "https://www.googleapis.com/oauth2/v3/userinfo"
+)
+
+// Handler authentication handler
+// handles, login, oauth and registration
+type Handler interface {
+ Login(w http.ResponseWriter, r *http.Request)
+ OauthCallback(w http.ResponseWriter, r *http.Request)
+}
+
+type authHandler struct {
+ service Servicer
+ ses *scs.SessionManager
+ oclient *oauth2.Config
+}
+
+// NewHandler create new instance of handler
+// Servicer, session, and oauth client required
+func NewHandler(service Servicer,
+ session *scs.SessionManager,
+ oclient *oauth2.Config) Handler {
+ return &authHandler{
+ service,
+ session,
+ oclient,
+ }
+}
+
+func (h *authHandler) Login(w http.ResponseWriter, r *http.Request) {
+ stateValue, err := h.service.GenerateStateToken()
+ if err != nil {
+ return
+ }
+ h.ses.Put(r.Context(), "state", stateValue)
+ url := h.oclient.AuthCodeURL(stateValue, oauth2.AccessTypeOnline)
+ http.Redirect(w, r, url, 302)
+}
+
+func (h *authHandler) OauthCallback(w http.ResponseWriter, r *http.Request) {
+ // this needs to be random
+ stateFromSession := h.ses.GetString(r.Context(), "state")
+ stateFromCallback := r.FormValue("state")
+ // prevent arbitrary authorization exchanges
+ if stateFromCallback != stateFromSession {
+ http.Error(w, "state value miss match bad data", 400)
+ return
+ }
+ stateValid, err := h.service.ValidateStateToken(stateFromCallback, stateFromSession)
+ if err != nil {
+ http.Error(w, "error validating", 400)
+ return
+ }
+ if !stateValid {
+ http.Error(w, "invalid tokens", 400)
+ return
+ }
+ // valid and same as state
+ code := r.FormValue("code")
+ token, err := h.oclient.Exchange(r.Context(), code)
+ if err != nil {
+ http.Error(w, err.Error(), 400)
+ return
+ }
+ log.Printf("returned token %v", token)
+ // google profile
+ client := h.oclient.Client(r.Context(), token)
+ resp, err := client.Get(profileURL)
+ if err != nil {
+ http.Error(w, err.Error(), 400)
+ return
+ }
+ defer resp.Body.Close()
+ data, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ http.Error(w, err.Error(), 400)
+ return
+ }
+ gp := &GoogleAuthProfile{}
+ err = json.Unmarshal(data, &gp)
+ profileID, newProfile, err := h.service.LoginOrRegisterSessionID(token, gp)
+ if err != nil {
+ http.Error(w, err.Error(), 400)
+ return
+ }
+ h.ses.Put(r.Context(), "profid", profileID)
+ // send to registration
+ if newProfile == true {
+ http.Redirect(w, r, "/account/register", 302)
+ return
+ }
+ http.Redirect(w, r, "/", 302)
+}
diff --git a/internal/auth/handler_test.go b/internal/auth/handler_test.go
new file mode 100644
index 0000000..abf82a8
--- /dev/null
+++ b/internal/auth/handler_test.go
@@ -0,0 +1,60 @@
+package auth_test
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/alexedwards/scs/v2"
+ _ "github.com/brianvoe/gofakeit"
+ "github.com/golang/mock/gomock"
+
+ "git.ofmax.li/iserv/internal/auth"
+ "git.ofmax.li/iserv/internal/mock/mock_auth"
+ testhelper "git.ofmax.li/iserv/internal/test"
+)
+
+type handlerSuite struct {
+ as *mock_auth.MockServicer
+ ses *scs.SessionManager
+}
+
+func TestHandler(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+ session := scs.New()
+ ts := &handlerSuite{
+ as: mock_auth.NewMockServicer(ctrl),
+ ses: session,
+ }
+ ah := auth.NewHandler(ts.as, session, testhelper.NewConf("testhost"))
+ t.Run("test login", ts.testLogin(ah))
+}
+
+func (s *handlerSuite) testLogin(ah auth.Handler) func(t *testing.T) {
+ return func(t *testing.T) {
+ s.as.EXPECT().GenerateStateToken().Return("asfdas", nil)
+ r, _ := http.NewRequest("GET", "/auth/login/", nil)
+ w := httptest.NewRecorder()
+ handler := s.ses.LoadAndSave(http.HandlerFunc(ah.Login))
+ handler.ServeHTTP(w, r)
+ response := w.Result()
+ if response.StatusCode != http.StatusFound {
+ t.Errorf("login http status not 302")
+ }
+ redirectURL, _ := response.Location()
+ if !strings.Contains(redirectURL.String(), "REDIRECT_URL") {
+ t.Errorf("redirect url not foukd %s", redirectURL)
+ }
+ if !strings.Contains(redirectURL.String(), "asfdas") {
+ t.Errorf("state token not found in url")
+ }
+ }
+}
+
+func (s *handlerSuite) testCallBack(ah auth.Handler) func(t *testing.T) {
+ return func(t *testing.T) {
+ t.Logf("not doing this at the moment")
+ }
+}
diff --git a/internal/auth/model.go b/internal/auth/model.go
new file mode 100644
index 0000000..c51ff05
--- /dev/null
+++ b/internal/auth/model.go
@@ -0,0 +1,49 @@
+package auth
+
+import (
+ "time"
+
+ "golang.org/x/oauth2"
+)
+
+// GoogleAuthProfile auth'd user profile
+// ProfileId, Email, Name, PictureURL
+type GoogleAuthProfile struct {
+ ProfileID string `json:"sub"`
+ Email string `json:"email"`
+ Name string `json:"name"`
+ PictureURL string `json:"picture"`
+}
+
+// Profile profile and token
+// Identifier + Token
+type Profile struct {
+ ID string `json:"sub"`
+ Email string `json:"email"`
+ AccessToken string `json:"access_token"`
+ TokenType string `json:"token_type,omitempty"`
+ RefreshToken string `json:"refresh_token,omitempty"`
+ Expiry time.Time `json:"expiry,omitempty"`
+}
+
+// Token token from profile
+func (ap *Profile) Token() (*oauth2.Token, error) {
+ return &oauth2.Token{
+ AccessToken: ap.AccessToken,
+ TokenType: ap.TokenType,
+ RefreshToken: ap.RefreshToken,
+ Expiry: ap.Expiry,
+ }, nil
+}
+
+// NewAuthProfile merge token and profile
+func NewAuthProfile(t *oauth2.Token, u *GoogleAuthProfile) *Profile {
+ return &Profile{
+ u.ProfileID,
+ u.Email,
+ t.AccessToken,
+ t.TokenType,
+ t.RefreshToken,
+ t.Expiry,
+ }
+}
diff --git a/internal/auth/repo.go b/internal/auth/repo.go
new file mode 100644
index 0000000..f404c94
--- /dev/null
+++ b/internal/auth/repo.go
@@ -0,0 +1,8 @@
+package auth
+
+// Repo storage interface
+type Repo interface {
+ IsAuthorized(gp *GoogleAuthProfile) (bool, error)
+ LookUpAuthProfileID(gp *GoogleAuthProfile) (string, error)
+ SaveAuthProfile(ap *Profile) error
+}
diff --git a/internal/auth/service.go b/internal/auth/service.go
new file mode 100644
index 0000000..9997264
--- /dev/null
+++ b/internal/auth/service.go
@@ -0,0 +1,97 @@
+package auth
+
+import (
+ "errors"
+ "log"
+ "time"
+
+ "golang.org/x/oauth2"
+
+ "github.com/gbrlsnchs/jwt/v3"
+)
+
+var (
+ singingSecret = jwt.NewHS512([]byte("the wolf says moo"))
+ // ErrInvalidToken error for token
+ ErrInvalidToken = errors.New("Invalid token provided")
+ // ErrInvalidJWT error for jwt
+ ErrInvalidJWT = errors.New("Invalid JWT")
+ // ErrUnauthorized error for unauthorized access
+ ErrUnauthorized = errors.New("Unauthorized")
+)
+
+// Servicer access to auth functionality
+type Servicer interface {
+ LoginOrRegisterSessionID(t *oauth2.Token, gp *GoogleAuthProfile) (string, bool, error)
+ GenerateStateToken() (string, error)
+ ValidateStateToken(token string, sessionToken string) (bool, error)
+}
+
+// Service a container for auth deps
+type Service struct {
+ repo Repo
+}
+
+// NewService create auth service
+func NewService(repo Repo) *Service {
+ return &Service{
+ repo,
+ }
+}
+
+// GenerateStateToken create a random token for oauth exchange
+func (a *Service) GenerateStateToken() (string, error) {
+ now := time.Now()
+ pl := jwt.Payload{
+ Issuer: "iserv-state",
+ Subject: "state param",
+ IssuedAt: jwt.NumericDate(now),
+ }
+ tokenBytes, err := jwt.Sign(pl, singingSecret)
+ if err != nil {
+ return "", err
+ }
+ return string(tokenBytes[:]), err
+}
+
+// ValidateStateToken validate provided token
+func (a *Service) ValidateStateToken(token string, sessionToken string) (bool, error) {
+ if token == sessionToken {
+ p := jwt.Payload{}
+ _, err := jwt.Verify([]byte(token), singingSecret, &p)
+ if err != nil {
+ return false, ErrInvalidJWT
+ }
+ return true, nil
+ }
+ return false, ErrInvalidToken
+}
+
+// LoginOrRegisterSessionID create a login
+func (a *Service) LoginOrRegisterSessionID(t *oauth2.Token, gp *GoogleAuthProfile) (string, bool, error) {
+ isAuthorized, err := a.repo.IsAuthorized(gp)
+ newRegistration := false
+ if err != nil {
+ return "", newRegistration, err
+ }
+ if isAuthorized != true {
+ return "", newRegistration, ErrUnauthorized
+ }
+ profileID, err := a.repo.LookUpAuthProfileID(gp)
+ if err != nil {
+ return "", newRegistration, err
+ }
+ if profileID == "" {
+ // create profile
+ log.Printf("creating new profile")
+ profile := NewAuthProfile(t, gp)
+ profileID = profile.ID
+ log.Printf("new profile %+v", profile)
+ err = a.repo.SaveAuthProfile(profile)
+ if err != nil {
+ return "", newRegistration, err
+ }
+ newRegistration = true
+ }
+ return profileID, newRegistration, nil
+}
diff --git a/internal/auth/service_test.go b/internal/auth/service_test.go
new file mode 100644
index 0000000..72ff709
--- /dev/null
+++ b/internal/auth/service_test.go
@@ -0,0 +1,133 @@
+package auth_test
+
+import (
+ "errors"
+ "strings"
+ "testing"
+
+ "golang.org/x/oauth2"
+
+ "github.com/brianvoe/gofakeit"
+ "github.com/golang/mock/gomock"
+
+ "git.ofmax.li/iserv/internal/auth"
+ "git.ofmax.li/iserv/internal/mock/mock_auth"
+)
+
+type serviceSuite struct {
+ as auth.Servicer
+ repo *mock_auth.MockRepo
+}
+
+func TestServices(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ repo := mock_auth.NewMockRepo(ctrl)
+ defer ctrl.Finish()
+ ts := &serviceSuite{
+ auth.NewService(repo),
+ repo,
+ }
+ t.Run("token generation", ts.testGenStateToken())
+ t.Run("token validation", ts.testValidateStateToken())
+ t.Run("auth profile registration", ts.testLoginOrRegsiterSessionId())
+}
+
+func (s *serviceSuite) testGenStateToken() func(t *testing.T) {
+ return func(t *testing.T) {
+ token, _ := s.as.GenerateStateToken()
+ parts := strings.Split(token, ".")
+ if len(parts) != 3 {
+ t.Errorf("token doesn't match format")
+ }
+ }
+}
+
+func (s *serviceSuite) testValidateStateToken() func(t *testing.T) {
+ validToken, _ := s.as.GenerateStateToken()
+ return func(t *testing.T) {
+ cases := []struct {
+ name string
+ token string
+ sessionToken string
+ wanted bool
+ errz error
+ }{
+ {name: "valid matching token",
+ token: validToken,
+ sessionToken: validToken,
+ wanted: true, errz: nil},
+ {name: "non matching token",
+ token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
+ sessionToken: "sadfsaf",
+ wanted: false, errz: auth.ErrInvalidToken},
+ {name: "matching but not real",
+ token: "eyJhbGciOTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
+ sessionToken: "eyJhbGciOTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
+ wanted: false, errz: auth.ErrInvalidJWT},
+ }
+ for _, tc := range cases {
+ isValid, err := s.as.ValidateStateToken(tc.token, tc.sessionToken)
+ if (isValid != tc.wanted) || (err != tc.errz) {
+ t.Fatalf("%s: expected: %v, got: %v err: %v errgot: %v", tc.name, tc.wanted, isValid, tc.errz, err)
+ }
+ }
+ }
+}
+
+func (s *serviceSuite) testLoginOrRegsiterSessionId() func(t *testing.T) {
+ return func(t *testing.T) {
+ // is authorized err
+ gp := &auth.GoogleAuthProfile{}
+ token := &oauth2.Token{}
+ gofakeit.Struct(token)
+ gofakeit.Struct(gp)
+ s.repo.
+ EXPECT().
+ IsAuthorized(gomock.Any()).
+ Return(false, errors.New("foo"))
+ id, isNew, err := s.as.LoginOrRegisterSessionID(token, gp)
+ if err == nil && id == "" && isNew == false {
+ t.Fatalf("%s", id)
+ }
+ // not authorized no error
+ s.repo.
+ EXPECT().
+ IsAuthorized(gomock.Any()).
+ Return(false, nil)
+ id, isNew, err = s.as.LoginOrRegisterSessionID(token, gp)
+ if err != auth.ErrUnauthorized || id != "" || isNew != false {
+ t.Fatalf("unauthorized isnew: %v id: %v err: %v", isNew, id, err.Error())
+ }
+
+ // authorized
+ s.repo.
+ EXPECT().
+ IsAuthorized(gomock.Any()).
+ Return(true, nil)
+ s.repo.
+ EXPECT().
+ LookUpAuthProfileID(gomock.Any()).
+ Return("asdfsafaf", nil)
+ id, isNew, err = s.as.LoginOrRegisterSessionID(token, gp)
+ if err != nil && isNew == false && id == "" {
+ t.Fatalf("auth profile exists isnew: %v id: %v err: %v", isNew, id, err.Error())
+ }
+ // new registration
+ s.repo.
+ EXPECT().
+ IsAuthorized(gomock.Any()).
+ Return(true, nil)
+ s.repo.
+ EXPECT().
+ LookUpAuthProfileID(gomock.Any()).
+ Return("", nil)
+ s.repo.
+ EXPECT().
+ SaveAuthProfile(gomock.Any()).
+ Return(nil)
+ id, isNew, err = s.as.LoginOrRegisterSessionID(token, gp)
+ if err != nil && isNew == false && id == "" {
+ t.Fatalf("isnew: %v id: %v err: %v", isNew, id, err.Error())
+ }
+ }
+}
diff --git a/internal/db/redis/auth.go b/internal/db/redis/auth.go
new file mode 100644
index 0000000..0c5246c
--- /dev/null
+++ b/internal/db/redis/auth.go
@@ -0,0 +1,108 @@
+package redis
+
+import (
+ "log"
+
+ "github.com/gomodule/redigo/redis"
+ "golang.org/x/oauth2"
+
+ "git.ofmax.li/iserv/internal/auth"
+)
+
+var (
+ createAuthLua = redis.NewScript(2, `
+ if redis.call("SISMEMBER","invites",KEYS[1]) == 1 then
+ return redis.call("HMSET",KEYS[2],unpack(ARGV))
+ end
+ return nil
+ `)
+)
+
+// AuthRepo supporting auth
+type AuthRepo struct {
+ db *redis.Pool
+}
+
+// NewRedisAuthRepo redis.Pool
+// creates auth repo around redis pool
+func NewRedisAuthRepo(conn *redis.Pool) *AuthRepo {
+ return &AuthRepo{
+ conn,
+ }
+}
+
+// IsAuthorized is member of invites
+func (db *AuthRepo) IsAuthorized(gp *auth.GoogleAuthProfile) (bool, error) {
+ conn := db.db.Get()
+ defer conn.Close()
+ authProfileKey := "auth:goog:" + gp.Email
+ reply, err := redis.Bool(conn.Do("SISMEMBER", "invites", authProfileKey))
+ if err != nil {
+ return reply, err
+ }
+ return reply, err
+}
+
+// LookUpAuthProfileID get internal profileid
+func (db *AuthRepo) LookUpAuthProfileID(gp *auth.GoogleAuthProfile) (string, error) {
+ conn := db.db.Get()
+ var id string
+ defer conn.Close()
+ authProfileKey := "auth:goog:" + gp.ProfileID
+ reply, err := redis.Values(conn.Do("HMGET", authProfileKey, "id"))
+ if err == redis.ErrNil {
+ // no profile
+ return "", nil
+ } else if err != nil {
+ // some other error
+ return "", err
+ }
+ if _, err := redis.Scan(reply, &id); err != nil {
+ return "", err
+ }
+ return id, nil
+}
+
+// SaveAuthProfile save profile, validate against invites
+func (db *AuthRepo) SaveAuthProfile(ap *auth.Profile) error {
+ // TODO this will over-write refresh tokens
+ conn := db.db.Get()
+ defer conn.Close()
+ authProfileKey := "auth:goog:" + ap.ID
+ prof := redis.Args{}.Add(ap.Email).Add(authProfileKey).AddFlat(ap)
+ res, err := createAuthLua.Do(conn, prof...)
+ log.Printf("auth save response %+v", res)
+ conn.Flush()
+ return err
+}
+
+func (db *AuthRepo) getAuthProfile(id string) (*auth.Profile, error) {
+ conn := db.db.Get()
+ defer conn.Close()
+ authProfileKey := "auth:goog:" + id
+ authProfile := auth.Profile{}
+ res, err := redis.Values(conn.Do("HGETALL", authProfileKey))
+ if err != nil {
+ log.Fatal(err)
+ }
+ log.Printf("getAuthProfile: %+v", res)
+ redis.ScanStruct(res, &authProfile)
+ return &authProfile, err
+}
+
+// GetProfileToken token for profile
+func (db *AuthRepo) GetProfileToken(id string) (*oauth2.Token, error) {
+ profile, err := db.getAuthProfile(id)
+ if err != nil {
+ return nil, err
+ }
+ tkn, err := profile.Token()
+ if err != nil {
+ return nil, err
+ }
+ refreshedToken, err := oauth2.ReuseTokenSource(tkn, profile).Token()
+ if err != nil {
+ return nil, err
+ }
+ return refreshedToken, nil
+}
diff --git a/internal/image/service.go b/internal/image/service.go
index 4ffd513..10f4148 100644
--- a/internal/image/service.go
+++ b/internal/image/service.go
@@ -8,7 +8,7 @@ import (
"path"
"time"
- "github.com/matoous/go-nanoid"
+ gonanoid "github.com/matoous/go-nanoid"
"github.com/pkg/errors"
"go.ofmax.li/tmpl"
diff --git a/internal/mock/mock_auth/mock_auth.go b/internal/mock/mock_auth/mock_auth.go
new file mode 100644
index 0000000..be22934
--- /dev/null
+++ b/internal/mock/mock_auth/mock_auth.go
@@ -0,0 +1,196 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: git.ofmax.li/iserv/internal/auth (interfaces: Servicer,Repo,Handler)
+
+// Package mock_auth is a generated GoMock package.
+package mock_auth
+
+import (
+ auth "git.ofmax.li/iserv/internal/auth"
+ gomock "github.com/golang/mock/gomock"
+ oauth2 "golang.org/x/oauth2"
+ http "net/http"
+ reflect "reflect"
+)
+
+// MockServicer is a mock of Servicer interface
+type MockServicer struct {
+ ctrl *gomock.Controller
+ recorder *MockServicerMockRecorder
+}
+
+// MockServicerMockRecorder is the mock recorder for MockServicer
+type MockServicerMockRecorder struct {
+ mock *MockServicer
+}
+
+// NewMockServicer creates a new mock instance
+func NewMockServicer(ctrl *gomock.Controller) *MockServicer {
+ mock := &MockServicer{ctrl: ctrl}
+ mock.recorder = &MockServicerMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockServicer) EXPECT() *MockServicerMockRecorder {
+ return m.recorder
+}
+
+// GenerateStateToken mocks base method
+func (m *MockServicer) GenerateStateToken() (string, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GenerateStateToken")
+ ret0, _ := ret[0].(string)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GenerateStateToken indicates an expected call of GenerateStateToken
+func (mr *MockServicerMockRecorder) GenerateStateToken() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateStateToken", reflect.TypeOf((*MockServicer)(nil).GenerateStateToken))
+}
+
+// LoginOrRegisterSessionID mocks base method
+func (m *MockServicer) LoginOrRegisterSessionID(arg0 *oauth2.Token, arg1 *auth.GoogleAuthProfile) (string, bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "LoginOrRegisterSessionID", arg0, arg1)
+ ret0, _ := ret[0].(string)
+ ret1, _ := ret[1].(bool)
+ ret2, _ := ret[2].(error)
+ return ret0, ret1, ret2
+}
+
+// LoginOrRegisterSessionID indicates an expected call of LoginOrRegisterSessionID
+func (mr *MockServicerMockRecorder) LoginOrRegisterSessionID(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoginOrRegisterSessionID", reflect.TypeOf((*MockServicer)(nil).LoginOrRegisterSessionID), arg0, arg1)
+}
+
+// ValidateStateToken mocks base method
+func (m *MockServicer) ValidateStateToken(arg0, arg1 string) (bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ValidateStateToken", arg0, arg1)
+ ret0, _ := ret[0].(bool)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// ValidateStateToken indicates an expected call of ValidateStateToken
+func (mr *MockServicerMockRecorder) ValidateStateToken(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateStateToken", reflect.TypeOf((*MockServicer)(nil).ValidateStateToken), arg0, arg1)
+}
+
+// MockRepo is a mock of Repo interface
+type MockRepo struct {
+ ctrl *gomock.Controller
+ recorder *MockRepoMockRecorder
+}
+
+// MockRepoMockRecorder is the mock recorder for MockRepo
+type MockRepoMockRecorder struct {
+ mock *MockRepo
+}
+
+// NewMockRepo creates a new mock instance
+func NewMockRepo(ctrl *gomock.Controller) *MockRepo {
+ mock := &MockRepo{ctrl: ctrl}
+ mock.recorder = &MockRepoMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockRepo) EXPECT() *MockRepoMockRecorder {
+ return m.recorder
+}
+
+// IsAuthorized mocks base method
+func (m *MockRepo) IsAuthorized(arg0 *auth.GoogleAuthProfile) (bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "IsAuthorized", arg0)
+ ret0, _ := ret[0].(bool)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// IsAuthorized indicates an expected call of IsAuthorized
+func (mr *MockRepoMockRecorder) IsAuthorized(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAuthorized", reflect.TypeOf((*MockRepo)(nil).IsAuthorized), arg0)
+}
+
+// LookUpAuthProfileID mocks base method
+func (m *MockRepo) LookUpAuthProfileID(arg0 *auth.GoogleAuthProfile) (string, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "LookUpAuthProfileID", arg0)
+ ret0, _ := ret[0].(string)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// LookUpAuthProfileID indicates an expected call of LookUpAuthProfileID
+func (mr *MockRepoMockRecorder) LookUpAuthProfileID(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LookUpAuthProfileID", reflect.TypeOf((*MockRepo)(nil).LookUpAuthProfileID), arg0)
+}
+
+// SaveAuthProfile mocks base method
+func (m *MockRepo) SaveAuthProfile(arg0 *auth.Profile) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SaveAuthProfile", arg0)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// SaveAuthProfile indicates an expected call of SaveAuthProfile
+func (mr *MockRepoMockRecorder) SaveAuthProfile(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveAuthProfile", reflect.TypeOf((*MockRepo)(nil).SaveAuthProfile), arg0)
+}
+
+// MockHandler is a mock of Handler interface
+type MockHandler struct {
+ ctrl *gomock.Controller
+ recorder *MockHandlerMockRecorder
+}
+
+// MockHandlerMockRecorder is the mock recorder for MockHandler
+type MockHandlerMockRecorder struct {
+ mock *MockHandler
+}
+
+// NewMockHandler creates a new mock instance
+func NewMockHandler(ctrl *gomock.Controller) *MockHandler {
+ mock := &MockHandler{ctrl: ctrl}
+ mock.recorder = &MockHandlerMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockHandler) EXPECT() *MockHandlerMockRecorder {
+ return m.recorder
+}
+
+// Login mocks base method
+func (m *MockHandler) Login(arg0 http.ResponseWriter, arg1 *http.Request) {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "Login", arg0, arg1)
+}
+
+// Login indicates an expected call of Login
+func (mr *MockHandlerMockRecorder) Login(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockHandler)(nil).Login), arg0, arg1)
+}
+
+// OauthCallback mocks base method
+func (m *MockHandler) OauthCallback(arg0 http.ResponseWriter, arg1 *http.Request) {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "OauthCallback", arg0, arg1)
+}
+
+// OauthCallback indicates an expected call of OauthCallback
+func (mr *MockHandlerMockRecorder) OauthCallback(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OauthCallback", reflect.TypeOf((*MockHandler)(nil).OauthCallback), arg0, arg1)
+}
diff --git a/internal/test/helpers.go b/internal/test/helpers.go
new file mode 100644
index 0000000..91fcbdc
--- /dev/null
+++ b/internal/test/helpers.go
@@ -0,0 +1,14 @@
+package testhelper
+
+import (
+ "golang.org/x/oauth2"
+)
+
+func NewConf(url string) *oauth2.Config {
+ return &oauth2.Config{
+ ClientID: "CLIENT_ID",
+ ClientSecret: "CLIENT_SECRET",
+ RedirectURL: "REDIRECT_URL",
+ Scopes: []string{"scope1", "scope2"},
+ }
+}