From e1f19c22d9eeb311c6b3aa2ffcf324a30731a74f Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 24 May 2018 18:43:24 -0500 Subject: [PATCH] put auth flag to jwt, dbl check in auth --- app/main.go | 2 ++ app/rest/api/rest.go | 11 ++++++----- app/rest/auth/auth.go | 18 ++++++++++------- app/rest/auth/provider.go | 20 ++++++++++--------- app/rest/auth/provider_test.go | 35 +++++++++++++++++++++++----------- app/rest/auth/providers.go | 8 ++++---- 6 files changed, 58 insertions(+), 36 deletions(-) diff --git a/app/main.go b/app/main.go index eb056384..db2d60ab 100644 --- a/app/main.go +++ b/app/main.go @@ -244,6 +244,8 @@ func makeAuthProviders(jwtService *auth.JWT, avatarProxy *proxy.Avatar, opts Opt RemarkURL: opts.RemarkURL, Cid: cid, Csecret: secret, + Admins: opts.Admins, + SecretKey: opts.SecretKey, } } diff --git a/app/rest/api/rest.go b/app/rest/api/rest.go index 3765abde..169c41f6 100644 --- a/app/rest/api/rest.go +++ b/app/rest/api/rest.go @@ -439,13 +439,14 @@ func (s *Rest) configCtrl(w http.ResponseWriter, r *http.Request) { LowScore: s.ScoreThresholds.Low, CriticalScore: s.ScoreThresholds.Critical, } - authNames := []string{} + + cnf.Auth = []string{} for _, ap := range s.Authenticator.Providers { - authNames = append(authNames, ap.Name) + cnf.Auth = append(cnf.Auth, ap.Name) } - cnf.Auth = authNames - if s.Authenticator.Admins == nil { // prevent json serialization to nil - s.Authenticator.Admins = []string{} + + if cnf.Admins == nil { // prevent json serialization to nil + cnf.Admins = []string{} } render.Status(r, http.StatusOK) render.JSON(w, r, cnf) diff --git a/app/rest/auth/auth.go b/app/rest/auth/auth.go index 5da8fee8..42baf3b0 100644 --- a/app/rest/auth/auth.go +++ b/app/rest/auth/auth.go @@ -14,8 +14,8 @@ import ( // Authenticator is top level auth object providing middlewares type Authenticator struct { JWTService *JWT - Admins []string Providers []Provider + Admins []string DevPasswd string } @@ -58,12 +58,7 @@ func (a *Authenticator) Auth(reqAuth bool) func(http.Handler) http.Handler { if claims.User != nil { // if uinfo in token populate it to context user := *claims.User - for _, admin := range a.Admins { - if admin == user.ID { - user.Admin = true - break - } - } + user.Admin = isAdmin(user.ID, a.Admins) // dbl-check for admin to reset admin flag even if token has it // refresh token if it close to expiration if _, err := a.JWTService.Refresh(w, r); err != nil { log.Printf("[WARN] can't refresh jwt, %s", err) @@ -124,3 +119,12 @@ func (a *Authenticator) basicDevUser(w http.ResponseWriter, r *http.Request) boo return true } + +func isAdmin(userID string, admins []string) bool { + for _, admin := range admins { + if admin == userID { + return true + } + } + return false +} diff --git a/app/rest/auth/provider.go b/app/rest/auth/provider.go index 25e4e4ee..390e6b9c 100644 --- a/app/rest/auth/provider.go +++ b/app/rest/auth/provider.go @@ -38,7 +38,8 @@ type Params struct { RemarkURL string AvatarProxy *proxy.Avatar JwtService *JWT - Secret string + SecretKey string + Admins []string Cid string Csecret string } @@ -53,7 +54,7 @@ func (u userData) value(key string) string { } // newProvider makes auth for given provider -func initProvider(p Params, provider Provider) Provider { +func initProvider(p Params, provider *Provider) Provider { log.Printf("[INFO] create %s auth, id=%s, redir: %s", provider.Name, provider.Cid, provider.RedirectURL) provider.Params = p conf := oauth2.Config{ @@ -65,11 +66,11 @@ func initProvider(p Params, provider Provider) Provider { } provider.conf = &conf - return provider + return *provider } // Routes returns auth routes for given provider -func (p Provider) Routes() chi.Router { +func (p *Provider) Routes() chi.Router { router := chi.NewRouter() router.Get("/login", p.loginHandler) router.Get("/callback", p.authHandler) @@ -78,7 +79,7 @@ func (p Provider) Routes() chi.Router { } // loginHandler - GET /login?from=redirect-back-url -func (p Provider) loginHandler(w http.ResponseWriter, r *http.Request) { +func (p *Provider) loginHandler(w http.ResponseWriter, r *http.Request) { // make state (random) and store in session state := p.randToken() @@ -108,7 +109,7 @@ func (p Provider) loginHandler(w http.ResponseWriter, r *http.Request) { // authHandler fills user info and redirects to "from" url. This is callback url redirected locally by browser // GET /callback -func (p Provider) authHandler(w http.ResponseWriter, r *http.Request) { +func (p *Provider) authHandler(w http.ResponseWriter, r *http.Request) { oauthClaims, err := p.JwtService.Get(r) if err != nil { @@ -163,6 +164,7 @@ func (p Provider) authHandler(w http.ResponseWriter, r *http.Request) { log.Printf("[WARN] failed to proxy avatar, %s", e) } } + u.Admin = isAdmin(u.ID, p.Admins) authClaims := &CustomClaims{ User: &u, @@ -184,16 +186,16 @@ func (p Provider) authHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, oauthClaims.From, http.StatusTemporaryRedirect) return } - render.JSON(w, r, jData) + render.JSON(w, r, &u) } // LogoutHandler - GET /logout -func (p Provider) LogoutHandler(w http.ResponseWriter, r *http.Request) { +func (p *Provider) LogoutHandler(w http.ResponseWriter, r *http.Request) { p.JwtService.Reset(w) log.Printf("[DEBUG] logout") } -func (p Provider) randToken() string { +func (p *Provider) randToken() string { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { log.Fatalf("[ERROR] can't get randoms, %s", err) diff --git a/app/rest/auth/provider_test.go b/app/rest/auth/provider_test.go index e982b9a7..3b0df55d 100644 --- a/app/rest/auth/provider_test.go +++ b/app/rest/auth/provider_test.go @@ -20,7 +20,7 @@ import ( func TestLogin(t *testing.T) { - _, ts, ots := mockProvider(t, 8981, 8982) + p, ts, ots := mockProvider(t, 8981, 8982) defer func() { ts.Close() ots.Close() @@ -46,8 +46,20 @@ func TestLogin(t *testing.T) { u := store.User{} err = json.Unmarshal(body, &u) assert.Nil(t, err) - assert.Equal(t, store.User{Name: "blah", ID: "myuser", Picture: "", + assert.Equal(t, store.User{Name: "blah", ID: "mock_myuser", Picture: "http://exmple.com/pic1.png", Admin: false, Blocked: false, IP: ""}, u) + + // check admin user + p.Admins = []string{"mock_myuser"} + resp, err = client.Get("http://localhost:8981/login") + assert.Nil(t, err) + assert.Equal(t, 200, resp.StatusCode) + body, err = ioutil.ReadAll(resp.Body) + assert.Nil(t, err) + err = json.Unmarshal(body, &u) + assert.Nil(t, err) + assert.Equal(t, store.User{Name: "blah", ID: "mock_myuser", Picture: "http://exmple.com/pic1.png", + Admin: true, Blocked: false, IP: ""}, u) } func TestLogout(t *testing.T) { @@ -78,19 +90,19 @@ func TestLogout(t *testing.T) { } func TestInitProvider(t *testing.T) { - params := Params{RemarkURL: "url", Secret: "123456", Cid: "cid", Csecret: "csecret"} + params := Params{RemarkURL: "url", SecretKey: "123456", Cid: "cid", Csecret: "csecret"} provider := Provider{Name: "test", RedirectURL: "redir"} - res := initProvider(params, provider) + res := initProvider(params, &provider) assert.Equal(t, "cid", res.conf.ClientID) assert.Equal(t, "csecret", res.conf.ClientSecret) assert.Equal(t, "redir", res.RedirectURL) - assert.Equal(t, "123456", res.Secret) + assert.Equal(t, "123456", res.SecretKey) assert.Equal(t, "test", res.Name) } -func mockProvider(t *testing.T, loginPort, authPort int) (provider Provider, ts *http.Server, oauth *http.Server) { +func mockProvider(t *testing.T, loginPort, authPort int) (provider *Provider, ts *http.Server, oauth *http.Server) { - provider = Provider{ + provider = &Provider{ Name: "mock", Endpoint: oauth2.Endpoint{ AuthURL: fmt.Sprintf("http://localhost:%d/login/oauth/authorize", authPort), @@ -101,15 +113,16 @@ func mockProvider(t *testing.T, loginPort, authPort int) (provider Provider, ts InfoURL: fmt.Sprintf("http://localhost:%d/user", authPort), MapUser: func(data userData, _ []byte) store.User { userInfo := store.User{ - ID: data.value("id"), + ID: "mock_" + data.value("id"), Name: data.value("name"), Picture: data.value("picture"), } return userInfo }, } - jwtServcie := NewJWT("12345", false, time.Hour) - provider = initProvider(Params{RemarkURL: "url", Secret: "123456", Cid: "cid", Csecret: "csecret", JwtService: jwtServcie}, provider) + params := Params{RemarkURL: "url", SecretKey: "123456", Cid: "cid", Csecret: "csecret", + JwtService: NewJWT("12345", false, time.Hour), Admins: []string{""}} + initProvider(params, provider) ts = &http.Server{Addr: fmt.Sprintf(":%d", loginPort), Handler: provider.Routes()} @@ -138,7 +151,7 @@ func mockProvider(t *testing.T, loginPort, authPort int) (provider Provider, ts res := `{ "id":"myuser", "name":"blah", - "profile": "http://blah.com/p.html" + "picture":"http://exmple.com/pic1.png" }` w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(200) diff --git a/app/rest/auth/providers.go b/app/rest/auth/providers.go index f08542f2..c551bd97 100644 --- a/app/rest/auth/providers.go +++ b/app/rest/auth/providers.go @@ -14,7 +14,7 @@ import ( // NewGoogle makes google oauth2 provider func NewGoogle(p Params) Provider { - return initProvider(p, Provider{ + return initProvider(p, &Provider{ Name: "google", Endpoint: google.Endpoint, RedirectURL: p.RemarkURL + "/auth/google/callback", @@ -37,7 +37,7 @@ func NewGoogle(p Params) Provider { // NewGithub makes github oauth2 provider func NewGithub(p Params) Provider { - return initProvider(p, Provider{ + return initProvider(p, &Provider{ Name: "github", Endpoint: github.Endpoint, RedirectURL: p.RemarkURL + "/auth/github/callback", @@ -75,7 +75,7 @@ func NewFacebook(p Params) Provider { } `json:"picture"` } - return initProvider(p, Provider{ + return initProvider(p, &Provider{ Name: "facebook", Endpoint: facebook.Endpoint, RedirectURL: p.RemarkURL + "/auth/facebook/callback", @@ -101,7 +101,7 @@ func NewFacebook(p Params) Provider { // NewDisqus makes disqus oauth2 provider. TODO: WIP - seems to need client_id param func NewDisqus(p Params) Provider { - return initProvider(p, Provider{ + return initProvider(p, &Provider{ Name: "disqus", Endpoint: oauth2.Endpoint{ AuthURL: "https://disqus.com/api/oauth/2.0/authorize/",