diff --git a/.dockerignore b/.dockerignore index 5c5baa0d..e64b70da 100644 --- a/.dockerignore +++ b/.dockerignore @@ -20,3 +20,4 @@ debug.test *.prof *.test remark42 +/backend/var/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1ab85900..0f74b453 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ debug.test .DS_Store .mongo remark42 -/bin/ \ No newline at end of file +/bin/ +/backend/var/ \ No newline at end of file diff --git a/backend/go.mod b/backend/go.mod index afe3534f..6dc84b44 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -13,7 +13,7 @@ require ( github.com/go-chi/chi v3.3.2+incompatible github.com/go-chi/cors v1.0.0 github.com/go-chi/render v1.0.0 - github.com/go-pkgz/auth v0.4.2 + github.com/go-pkgz/auth v0.5.0 github.com/go-pkgz/lcw v0.2.0 github.com/go-pkgz/lgr v0.4.0 github.com/go-pkgz/mongo v1.1.2 diff --git a/backend/go.sum b/backend/go.sum index 8bf05b82..7a21bfa4 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -28,6 +28,8 @@ github.com/go-chi/render v1.0.0 h1:cLJlkaTB4xfx5rWhtoB0BSXsXVJKWFqv08Y3cR1bZKA= github.com/go-chi/render v1.0.0/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/go-pkgz/auth v0.4.2 h1:WY3XzjUieUGxJSjXDU0rLrKt8RcPEaGdtLVRS7M56wU= github.com/go-pkgz/auth v0.4.2/go.mod h1:CWtB8dHmOv+TfF3MUzKwk/YwTLepC2TaDL05A+pFVBM= +github.com/go-pkgz/auth v0.5.0 h1:+wqppq35x83PchZNZ7SHHYLI/e8WeETFouujDLsklac= +github.com/go-pkgz/auth v0.5.0/go.mod h1:CWtB8dHmOv+TfF3MUzKwk/YwTLepC2TaDL05A+pFVBM= github.com/go-pkgz/lcw v0.2.0 h1:aFoKUG8q0YybId+ThVRQpDMjjuSG4hkLL1EA2xUtruc= github.com/go-pkgz/lcw v0.2.0/go.mod h1:k+PY1CkCMTLXILtFoJOyK65Qqi9rkoTYunFH1vE/C0I= github.com/go-pkgz/lgr v0.2.2/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8= diff --git a/backend/vendor/github.com/go-pkgz/auth/README.md b/backend/vendor/github.com/go-pkgz/auth/README.md index 7d8b8b97..2109af13 100644 --- a/backend/vendor/github.com/go-pkgz/auth/README.md +++ b/backend/vendor/github.com/go-pkgz/auth/README.md @@ -10,7 +10,8 @@ This library provides "social login" with Github, Google, Facebook and Yandex as - Minimal scopes with user name, id and picture (avatar) only - Direct authentication with user's provided credential checker - Integrated avatar proxy with FS, boltdb and gridfs storages -- Support of user-defined storages for avatars +- Support of user-defined storage for avatars +- Identicon for default avatars - Black list with user-defined validator - Multiple aud (audience) supported - Secure key with customizable `SecretReader` @@ -144,6 +145,8 @@ Such provider acts like any other, i.e. will be registered as `/auth/local/login The API for this provider - `GET /auth//login?user=&passwd=&aud=&session=[1|0]` +_note: password parameter doesn't have to be naked/real password and can be any kind of password hash prepared by caller._ + ### Customization There are several ways to adjust functionality of the library: diff --git a/backend/vendor/github.com/go-pkgz/auth/auth.go b/backend/vendor/github.com/go-pkgz/auth/auth.go index 13dc9dc2..3992222e 100644 --- a/backend/vendor/github.com/go-pkgz/auth/auth.go +++ b/backend/vendor/github.com/go-pkgz/auth/auth.go @@ -1,3 +1,4 @@ +// Package auth provides "social login" with Github, Google, Facebook and Yandex as well as custom auth providers. package auth import ( @@ -226,6 +227,7 @@ func (s *Service) AddDirectProvider(name string, credChecker provider.CredChecke Issuer: s.issuer, TokenService: s.jwtService, CredChecker: credChecker, + AvatarSaver: s.avatarProxy, } s.providers = append(s.providers, provider.NewService(dh)) s.authMiddleware.Providers = s.providers diff --git a/backend/vendor/github.com/go-pkgz/auth/avatar/avatar.go b/backend/vendor/github.com/go-pkgz/auth/avatar/avatar.go index 03f7d4a2..8c816a8f 100644 --- a/backend/vendor/github.com/go-pkgz/auth/avatar/avatar.go +++ b/backend/vendor/github.com/go-pkgz/auth/avatar/avatar.go @@ -13,6 +13,7 @@ import ( "time" "github.com/go-pkgz/rest" + "github.com/nullrocks/identicon" "github.com/pkg/errors" "golang.org/x/image/draw" @@ -33,9 +34,19 @@ type Proxy struct { // Put stores retrieved avatar to avatar.Store. Gets image from user info. Returns proxied url func (p *Proxy) Put(u token.User) (avatarURL string, err error) { - // no picture for user, try default avatar + // no picture for user, try to generate identicon avatar if u.Picture == "" { - return "", errors.Errorf("no picture for %s", u.ID) + b, err := GenerateAvatar(u.ID) + if err != nil { + return "", errors.Errorf("no picture for %s", u.ID) + } + avatarID, err := p.Store.Put(u.ID, p.resize(bytes.NewBuffer(b), p.ResizeLimit)) // put returns avatar base name, like 123456.image + if err != nil { + return "", err + } + + p.Logf("[DEBUG] saved identicon avatar to %s, user %q", avatarID, u.Name) + return p.URL + p.RoutePath + "/" + avatarID, nil } // load avatar from remote location @@ -152,6 +163,25 @@ func (p *Proxy) resize(reader io.Reader, limit int) io.Reader { } return &out } + +// GenerateAvatar for give user with identicon +func GenerateAvatar(user string) ([]byte, error) { + + iconGen, err := identicon.New("pkgz/auth", 5, 5) + if err != nil { + return nil, errors.Wrap(err, "can't create identicon service") + } + + ii, err := iconGen.Draw(user) // generate an IdentIcon + if err != nil { + return nil, errors.Wrapf(err, "failed to draw avatar for %s", user) + } + + buf := &bytes.Buffer{} + err = ii.Png(300, buf) + return buf.Bytes(), err +} + func retry(retries int, delay time.Duration, fn func() error) (err error) { for i := 0; i < retries; i++ { if err = fn(); err == nil { diff --git a/backend/vendor/github.com/go-pkgz/auth/logger/interface.go b/backend/vendor/github.com/go-pkgz/auth/logger/interface.go index d2a7f738..308c456e 100644 --- a/backend/vendor/github.com/go-pkgz/auth/logger/interface.go +++ b/backend/vendor/github.com/go-pkgz/auth/logger/interface.go @@ -1,3 +1,5 @@ +// Package logger defines interface for logging. Implementation should be passed by user. +// Also provides NoOp (do-nothing) and Std (redirect to std log) predefined loggers. package logger import "log" diff --git a/backend/vendor/github.com/go-pkgz/auth/middleware/auth.go b/backend/vendor/github.com/go-pkgz/auth/middleware/auth.go index 584d55c4..1416bd9b 100644 --- a/backend/vendor/github.com/go-pkgz/auth/middleware/auth.go +++ b/backend/vendor/github.com/go-pkgz/auth/middleware/auth.go @@ -1,4 +1,7 @@ -// Package middleware provides oauth2 support as well as related middlewares. +// Package middleware provides login middlewares: +// - Auth: adds auth from session and populates user info +// - Trace: populates user info if token presented +// - AdminOnly: restrict access to admin users only package middleware import ( @@ -35,6 +38,7 @@ type TokenService interface { Reset(w http.ResponseWriter) } +// adminUser sets claims for an optional basic auth var adminUser = token.User{ ID: "admin", Name: "admin", @@ -120,6 +124,7 @@ func (a *Authenticator) auth(reqAuth bool) func(http.Handler) http.Handler { // refreshExpiredToken makes a new token with passed claims func (a *Authenticator) refreshExpiredToken(w http.ResponseWriter, claims token.Claims, tkn string) (token.Claims, error) { + // cache refreshed claims for given token in order to eliminate multiple refreshes for concurrent requests if a.RefreshCache != nil { if c, ok := a.RefreshCache.Get(tkn); ok { // already in cache diff --git a/backend/vendor/github.com/go-pkgz/auth/provider/dev_provider.go b/backend/vendor/github.com/go-pkgz/auth/provider/dev_provider.go index 3715a3ce..e0359245 100644 --- a/backend/vendor/github.com/go-pkgz/auth/provider/dev_provider.go +++ b/backend/vendor/github.com/go-pkgz/auth/provider/dev_provider.go @@ -1,7 +1,6 @@ package provider import ( - "bytes" "context" "fmt" "net/http" @@ -10,9 +9,8 @@ import ( "text/template" "time" + "github.com/go-pkgz/auth/avatar" "github.com/go-pkgz/auth/logger" - "github.com/nullrocks/identicon" - "github.com/pkg/errors" "golang.org/x/oauth2" "github.com/go-pkgz/auth/token" @@ -31,7 +29,6 @@ type DevAuthServer struct { Automatic bool username string // unsafe, but fine for dev - iconGen *identicon.Generator httpServer *http.Server lock sync.Mutex } @@ -42,10 +39,6 @@ func (d *DevAuthServer) Run(ctx context.Context) { d.Logf("[INFO] run local oauth2 dev server on %d, redir url=%s", devAuthPort, d.Provider.redirectURL) d.lock.Lock() var err error - d.iconGen, err = identicon.New("github", 5, 3) - if err != nil { - d.Logf("[WARN] can't create identicon, %s", err) - } userFormTmpl, err := template.New("page").Parse(devUserFormTmpl) if err != nil { @@ -113,7 +106,7 @@ func (d *DevAuthServer) Run(ctx context.Context) { case strings.HasPrefix(r.URL.Path, "/avatar"): user := r.URL.Query().Get("user") - b, e := d.genAvatar(user) + b, e := avatar.GenerateAvatar(user) if e != nil { w.WriteHeader(http.StatusNotFound) return @@ -177,21 +170,6 @@ func NewDev(p Params) Oauth2Handler { }) } -func (d *DevAuthServer) genAvatar(user string) ([]byte, error) { - if d.iconGen == nil { - return nil, errors.Errorf("no iconGen, skip avatar generation for %s", user) - } - - ii, err := d.iconGen.Draw(user) // Generate an IdentIcon - if err != nil { - return nil, errors.Wrapf(err, "failed to draw avatar for %s", user) - } - - buf := &bytes.Buffer{} - err = ii.Png(300, buf) - return buf.Bytes(), err -} - var devUserFormTmpl = ` diff --git a/backend/vendor/github.com/go-pkgz/auth/provider/direct.go b/backend/vendor/github.com/go-pkgz/auth/provider/direct.go index d332a0e0..d8fd2a80 100644 --- a/backend/vendor/github.com/go-pkgz/auth/provider/direct.go +++ b/backend/vendor/github.com/go-pkgz/auth/provider/direct.go @@ -20,6 +20,7 @@ type DirectHandler struct { ProviderName string TokenService TokenService Issuer string + AvatarSaver AvatarSaver } // CredChecker defines interface to check credentials @@ -45,23 +46,31 @@ func (p DirectHandler) LoginHandler(w http.ResponseWriter, r *http.Request) { aud := r.URL.Query().Get("aud") sessOnly := r.URL.Query().Get("sess") == "1" if p.CredChecker == nil { - rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, errors.New("empty credential store"), "no credential store") + rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, + errors.New("no credential checker"), "no credential checker") return } ok, err := p.CredChecker.Check(user, password) if err != nil { - rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to access creds store") + rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to check user credentials") return } if !ok { rest.SendErrorJSON(w, r, p.L, http.StatusForbidden, nil, "incorrect user or password") return } + u := token.User{ + Name: user, + ID: p.ProviderName + "_" + token.HashID(sha1.New(), user), + } + u, err = setAvatar(p.AvatarSaver, u) + if err != nil { + rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "failed to save avatar to proxy") + return + } + claims := token.Claims{ - User: &token.User{ - Name: user, - ID: p.ProviderName + "_" + token.HashID(sha1.New(), user), - }, + User: &u, StandardClaims: jwt.StandardClaims{ Issuer: p.Issuer, Audience: aud, diff --git a/backend/vendor/github.com/go-pkgz/auth/token/user.go b/backend/vendor/github.com/go-pkgz/auth/token/user.go index 5a5691a0..12f180b5 100644 --- a/backend/vendor/github.com/go-pkgz/auth/token/user.go +++ b/backend/vendor/github.com/go-pkgz/auth/token/user.go @@ -20,12 +20,14 @@ const adminAttr = "admin" // predefined attribute key for bool isAdmin status // User is the basic part of oauth data provided by service type User struct { + // set by service Name string `json:"name"` ID string `json:"id"` Picture string `json:"picture"` - IP string `json:"ip,omitempty"` - Email string `json:"email,omitempty"` + // set by client + IP string `json:"ip,omitempty"` + Email string `json:"email,omitempty"` Attributes map[string]interface{} `json:"attrs,omitempty"` } @@ -82,7 +84,7 @@ func (u *User) SliceAttr(key string) []string { return r } -// SetSliceAttr sets boolean attribute +// SetSliceAttr sets slice attribute for given key func (u *User) SetSliceAttr(key string, val []string) { if u.Attributes == nil { u.Attributes = map[string]interface{}{} @@ -112,7 +114,7 @@ func HashID(h hash.Hash, val string) string { type contextKey string -// MustGetUserInfo fails if can't extract user data from the request. +// MustGetUserInfo gets user info and panics if can't extract it from the request. // should be called from authenticated controllers only func MustGetUserInfo(r *http.Request) User { user, err := GetUserInfo(r) @@ -122,7 +124,7 @@ func MustGetUserInfo(r *http.Request) User { return user } -// GetUserInfo returns user from request context +// GetUserInfo returns user info from request context func GetUserInfo(r *http.Request) (user User, err error) { ctx := r.Context() diff --git a/backend/vendor/modules.txt b/backend/vendor/modules.txt index 3f1038ff..4863c4d0 100644 --- a/backend/vendor/modules.txt +++ b/backend/vendor/modules.txt @@ -30,7 +30,7 @@ github.com/go-chi/chi/middleware github.com/go-chi/cors # github.com/go-chi/render v1.0.0 github.com/go-chi/render -# github.com/go-pkgz/auth v0.4.2 +# github.com/go-pkgz/auth v0.5.0 github.com/go-pkgz/auth github.com/go-pkgz/auth/avatar github.com/go-pkgz/auth/provider