diff options
Diffstat (limited to '')
| -rw-r--r-- | internal/auth/handler.go | 35 | ||||
| -rw-r--r-- | internal/auth/middleware.go | 45 | ||||
| -rw-r--r-- | internal/auth/model.go | 15 | ||||
| -rw-r--r-- | internal/auth/repo.go | 13 | ||||
| -rw-r--r-- | internal/auth/service.go | 36 | ||||
| -rw-r--r-- | internal/auth/service_test.go | 3 |
6 files changed, 104 insertions, 43 deletions
diff --git a/internal/auth/handler.go b/internal/auth/handler.go index 992608c..d57d47e 100644 --- a/internal/auth/handler.go +++ b/internal/auth/handler.go @@ -3,15 +3,12 @@ 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" + "git.ofmax.li/iserv/internal/goog" ) // Handler authentication handler @@ -22,30 +19,28 @@ type Handler interface { } type authHandler struct { - service Servicer - ses *scs.SessionManager - oclient *oauth2.Config + svc Servicer + ses *scs.SessionManager } +// TODO migrate to Goog // NewHandler create new instance of handler // Servicer, session, and oauth client required func NewHandler(service Servicer, - session *scs.SessionManager, - oclient *oauth2.Config) Handler { + session *scs.SessionManager) Handler { return &authHandler{ service, session, - oclient, } } func (h *authHandler) Login(w http.ResponseWriter, r *http.Request) { - stateValue, err := h.service.GenerateStateToken() + stateValue, err := h.svc.GenerateStateToken() if err != nil { return } h.ses.Put(r.Context(), "state", stateValue) - url := h.oclient.AuthCodeURL(stateValue, oauth2.AccessTypeOnline) + url := h.svc.Goog().Config().AuthCodeURL(stateValue, oauth2.AccessTypeOnline) http.Redirect(w, r, url, 302) } @@ -58,7 +53,7 @@ func (h *authHandler) OauthCallback(w http.ResponseWriter, r *http.Request) { http.Error(w, "state value miss match bad data", 400) return } - stateValid, err := h.service.ValidateStateToken(stateFromCallback, stateFromSession) + stateValid, err := h.svc.ValidateStateToken(stateFromCallback, stateFromSession) if err != nil { http.Error(w, "error validating", 400) return @@ -69,15 +64,14 @@ func (h *authHandler) OauthCallback(w http.ResponseWriter, r *http.Request) { } // valid and same as state code := r.FormValue("code") - token, err := h.oclient.Exchange(r.Context(), code) + token, err := h.svc.Goog().Config().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) + client := h.svc.Goog().UserClient(r.Context(), token) + resp, err := client.Get(goog.ProfileURL) if err != nil { http.Error(w, err.Error(), 400) return @@ -88,9 +82,9 @@ func (h *authHandler) OauthCallback(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), 400) return } - gp := &GoogleAuthProfile{} + gp := &goog.GoogleProfile{} err = json.Unmarshal(data, &gp) - profileID, newProfile, err := h.service.LoginOrRegisterSessionID(token, gp) + profileID, newProfile, err := h.svc.LoginOrRegisterSessionID(token, gp) if err != nil { http.Error(w, err.Error(), 400) return @@ -98,8 +92,9 @@ func (h *authHandler) OauthCallback(w http.ResponseWriter, r *http.Request) { h.ses.Put(r.Context(), "profid", profileID) // send to registration if newProfile == true { - http.Redirect(w, r, "/account/register", 302) + http.Redirect(w, r, "/u/register", 302) return } http.Redirect(w, r, "/", 302) + return } diff --git a/internal/auth/middleware.go b/internal/auth/middleware.go new file mode 100644 index 0000000..0be033c --- /dev/null +++ b/internal/auth/middleware.go @@ -0,0 +1,45 @@ +package auth + +import ( + "net/http" + + "github.com/alexedwards/scs/v2" + "github.com/apex/log" +) + +const ( + loginURL = "/login" +) + +func AuthOnly(s Servicer, ses *scs.SessionManager) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + userID := ses.GetString(r.Context(), "profid") + if userID == "" { + userID = "anon" + } + resource := r.URL.Path + // set the action to something that will never match + action := "forbidden" + switch r.Method { + case "POST", "PUT", "PATCH": + action = "write" + case "HEAD", "GET": + action = "read" + } + // TODO determine action + enforced, err := s.Enf().EnforceSafe(userID, resource, action) + if err != nil { + log.Errorf("%s", err) + return + } + if !enforced { + // TODO probably need to do something about suggesting to login + http.Error(w, "not found, are you signed in?", http.StatusNotFound) + return + } + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } +} diff --git a/internal/auth/model.go b/internal/auth/model.go index c51ff05..240b11b 100644 --- a/internal/auth/model.go +++ b/internal/auth/model.go @@ -6,20 +6,10 @@ import ( "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"` @@ -37,10 +27,9 @@ func (ap *Profile) Token() (*oauth2.Token, error) { } // NewAuthProfile merge token and profile -func NewAuthProfile(t *oauth2.Token, u *GoogleAuthProfile) *Profile { +func NewAuthProfile(t *oauth2.Token, id string) *Profile { return &Profile{ - u.ProfileID, - u.Email, + id, t.AccessToken, t.TokenType, t.RefreshToken, diff --git a/internal/auth/repo.go b/internal/auth/repo.go index f404c94..9a0420e 100644 --- a/internal/auth/repo.go +++ b/internal/auth/repo.go @@ -1,8 +1,15 @@ package auth +import ( + "git.ofmax.li/iserv/internal/goog" + "golang.org/x/oauth2" +) + // Repo storage interface type Repo interface { - IsAuthorized(gp *GoogleAuthProfile) (bool, error) - LookUpAuthProfileID(gp *GoogleAuthProfile) (string, error) - SaveAuthProfile(ap *Profile) error + IsAuthorized(gp *goog.GoogleProfile) (bool, error) + LookUpAuthProfileID(gp *goog.GoogleProfile) (string, error) + SaveAuthProfile(email string, ap *Profile) error + CheckProfileID(id string) (bool, error) + GetProfileToken(id string) (*oauth2.Token, error) } diff --git a/internal/auth/service.go b/internal/auth/service.go index 9997264..e85c705 100644 --- a/internal/auth/service.go +++ b/internal/auth/service.go @@ -2,11 +2,14 @@ package auth import ( "errors" + "fmt" "log" "time" + "git.ofmax.li/iserv/internal/goog" "golang.org/x/oauth2" + "github.com/casbin/casbin" "github.com/gbrlsnchs/jwt/v3" ) @@ -22,23 +25,40 @@ var ( // Servicer access to auth functionality type Servicer interface { - LoginOrRegisterSessionID(t *oauth2.Token, gp *GoogleAuthProfile) (string, bool, error) + Goog() goog.Servicer + LoginOrRegisterSessionID(t *oauth2.Token, gp *goog.GoogleProfile) (string, bool, error) GenerateStateToken() (string, error) ValidateStateToken(token string, sessionToken string) (bool, error) + CheckProfileID(id string) (bool, error) + Enf() *casbin.Enforcer } // Service a container for auth deps type Service struct { repo Repo + goog goog.Servicer + enf *casbin.Enforcer } // NewService create auth service -func NewService(repo Repo) *Service { +func NewService(repo Repo, goog goog.Servicer, enf *casbin.Enforcer) *Service { return &Service{ repo, + goog, + enf, } } +// Goog get google interface +func (a *Service) Goog() goog.Servicer { + return a.goog +} + +// Enf enforcer instance +func (a *Service) Enf() *casbin.Enforcer { + return a.enf +} + // GenerateStateToken create a random token for oauth exchange func (a *Service) GenerateStateToken() (string, error) { now := time.Now() @@ -67,8 +87,13 @@ func (a *Service) ValidateStateToken(token string, sessionToken string) (bool, e return false, ErrInvalidToken } +// CheckProfileID check if a profileid exists +func (a *Service) CheckProfileID(id string) (bool, error) { + return a.repo.CheckProfileID(id) +} + // LoginOrRegisterSessionID create a login -func (a *Service) LoginOrRegisterSessionID(t *oauth2.Token, gp *GoogleAuthProfile) (string, bool, error) { +func (a *Service) LoginOrRegisterSessionID(t *oauth2.Token, gp *goog.GoogleProfile) (string, bool, error) { isAuthorized, err := a.repo.IsAuthorized(gp) newRegistration := false if err != nil { @@ -84,10 +109,9 @@ func (a *Service) LoginOrRegisterSessionID(t *oauth2.Token, gp *GoogleAuthProfil if profileID == "" { // create profile log.Printf("creating new profile") - profile := NewAuthProfile(t, gp) + profile := NewAuthProfile(t, fmt.Sprintf("goog:%s", gp.ProfileID)) profileID = profile.ID - log.Printf("new profile %+v", profile) - err = a.repo.SaveAuthProfile(profile) + err = a.repo.SaveAuthProfile(gp.Email, profile) if err != nil { return "", newRegistration, err } diff --git a/internal/auth/service_test.go b/internal/auth/service_test.go index 72ff709..b992696 100644 --- a/internal/auth/service_test.go +++ b/internal/auth/service_test.go @@ -11,6 +11,7 @@ import ( "github.com/golang/mock/gomock" "git.ofmax.li/iserv/internal/auth" + "git.ofmax.li/iserv/internal/goog" "git.ofmax.li/iserv/internal/mock/mock_auth" ) @@ -77,7 +78,7 @@ func (s *serviceSuite) testValidateStateToken() func(t *testing.T) { func (s *serviceSuite) testLoginOrRegsiterSessionId() func(t *testing.T) { return func(t *testing.T) { // is authorized err - gp := &auth.GoogleAuthProfile{} + gp := &goog.GoogleProfile{} token := &oauth2.Token{} gofakeit.Struct(token) gofakeit.Struct(gp) |