aboutsummaryrefslogtreecommitdiff
path: root/internal/auth
diff options
context:
space:
mode:
authorMax Resnick <max@ofmax.li>2020-11-08 11:45:16 -0800
committerMax Resnick <max@ofmax.li>2021-01-01 10:50:14 -0800
commita397341ad471cc761f7fb930d77e53cf7eb40a2a (patch)
tree76fb8318269569687fdd30467dc61ecba3499d09 /internal/auth
parent689a57ec4a444f8233fe2e5ec7ceb0903218218d (diff)
downloadiserv-a397341ad471cc761f7fb930d77e53cf7eb40a2a.tar.gz
adds casbin and accounts
Diffstat (limited to '')
-rw-r--r--internal/auth/handler.go35
-rw-r--r--internal/auth/middleware.go45
-rw-r--r--internal/auth/model.go15
-rw-r--r--internal/auth/repo.go13
-rw-r--r--internal/auth/service.go36
-rw-r--r--internal/auth/service_test.go3
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)