From 17acbc63224477e51baa5b4bcc96f07798e786db Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 2 Jan 2019 00:16:37 -0600 Subject: [PATCH] vednor auth v0.2.0 --- backend/Gopkg.lock | 6 +- .../vendor/github.com/go-pkgz/auth/README.md | 22 ++++- .../vendor/github.com/go-pkgz/auth/auth.go | 19 ++++- backend/vendor/github.com/go-pkgz/auth/go.sum | 1 + .../go-pkgz/auth/middleware/auth.go | 4 +- .../go-pkgz/auth/provider/direct.go | 81 +++++++++++++++++++ .../go-pkgz/auth/provider/oauth2.go | 2 +- .../github.com/go-pkgz/auth/token/jwt.go | 28 ++++++- .../github.com/go-pkgz/auth/token/user.go | 7 +- 9 files changed, 155 insertions(+), 15 deletions(-) create mode 100644 backend/vendor/github.com/go-pkgz/auth/provider/direct.go diff --git a/backend/Gopkg.lock b/backend/Gopkg.lock index a4e0cb32..a58fd36d 100644 --- a/backend/Gopkg.lock +++ b/backend/Gopkg.lock @@ -112,7 +112,7 @@ version = "v1.0.0" [[projects]] - digest = "1:2e6b942dd80c33bba11b9567f3f8a4338e80d597959e4a5d182d5251953bebe6" + digest = "1:a4ff2b649472abf046975396ac916b04527fde8d897857c2feea76498aeb762f" name = "github.com/go-pkgz/auth" packages = [ ".", @@ -123,8 +123,8 @@ "token", ] pruneopts = "UT" - revision = "a4dab49e2656a32ab7eb1255980c24f8560dc298" - version = "v0.1.1" + revision = "855a238343c3bcea84b352fdeb4393576f9eb217" + version = "v0.2.0" [[projects]] digest = "1:1212e114344a5cdcc834ea69e19d456eef230f9784659080fee67e02ba2cb574" diff --git a/backend/vendor/github.com/go-pkgz/auth/README.md b/backend/vendor/github.com/go-pkgz/auth/README.md index f6f4b8b5..836344b8 100644 --- a/backend/vendor/github.com/go-pkgz/auth/README.md +++ b/backend/vendor/github.com/go-pkgz/auth/README.md @@ -2,12 +2,13 @@ -This library provides "social login" with Github, Google, Facebook and Yandex. +This library provides "social login" with Github, Google, Facebook and Yandex as well as custom auth providers. - Multiple oauth2 providers can be used at the same time - Special `dev` provider allows local testing and development - JWT stored in a secure cookie with XSRF protection. Cookies can be session-only - 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 - Black list with user-defined validator @@ -128,13 +129,28 @@ Direct links to avatars won't survive any real-life usage if they linked from a - `AvatarRoutePath` - route prefix for direct links to proxied avatar. For example `/api/v1/avatars` will make full links like this - `http://example.com/api/v1/avatars/1234567890123.image`. The url will be stored in user's token and retrieved by middleware (see "User Info") - `AvatarResizeLimit` - size (in pixels) used to resize the avatar. Pls note - resize happens once as a part of `Put` call, i.e. on login. 0 size (default) disables resizing. +### Direct authentication + +In addition to oauth2 providers `auth.Service` allows to use direct user-defined authentication. This is done by adding direct provider with `auth.AddDirectProvider`. + +```go + service.AddDirectProvider("local", provider.CredCheckerFunc(func(user, password string) (ok bool, err error) { + ok, err := checkUserSomehow(user, password) + return ok, err + })) +``` + +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]` + ### Customization There are several ways to adjust functionality of the library: 1. `SecretReader` - interface with a single method `Get(aud string) string` to return the secret used for JWT signing and verification -1. `ClaimsUpdater` - interface with `Update(claims Claims) Claims` method. This is the primary way to alter a token at login time and add any attributes, set ip, email, admin status and so on. -2. `Validator` - interface with `Validate(token string, claims Claims) bool` method. This is post-token hook and will be called on **each request** wrapped with `Auth` middleware. This will be the place for special logic to reject some tokens or users. +2. `ClaimsUpdater` - interface with `Update(claims Claims) Claims` method. This is the primary way to alter a token at login time and add any attributes, set ip, email, admin status and so on. +3. `Validator` - interface with `Validate(token string, claims Claims) bool` method. This is post-token hook and will be called on **each request** wrapped with `Auth` middleware. This will be the place for special logic to reject some tokens or users. All of the interfaces above have corresponding Func adapters - `SecretFunc`, `ClaimsUpdFunc` and `ValidatorFunc`. diff --git a/backend/vendor/github.com/go-pkgz/auth/auth.go b/backend/vendor/github.com/go-pkgz/auth/auth.go index a5b04f8a..47954a8b 100644 --- a/backend/vendor/github.com/go-pkgz/auth/auth.go +++ b/backend/vendor/github.com/go-pkgz/auth/auth.go @@ -34,7 +34,9 @@ type Opts struct { SecureCookies bool // makes jwt cookie secure TokenDuration time.Duration // token's TTL, refreshed automatically CookieDuration time.Duration // cookie's TTL. This cookie stores JWT token - DisableXSRF bool // disable XSRF protection, useful for testing/debugging + + DisableXSRF bool // disable XSRF protection, useful for testing/debugging + DisableIAT bool // disable IssuedAt claim // optional (custom) names for cookies and headers JWTCookieName string // default "JWT" @@ -83,6 +85,7 @@ func NewService(opts Opts) (res *Service) { TokenDuration: opts.TokenDuration, CookieDuration: opts.CookieDuration, DisableXSRF: opts.DisableXSRF, + DisableIAT: opts.DisableIAT, JWTCookieName: opts.JWTCookieName, JWTHeaderKey: opts.JWTHeaderKey, XSRFCookieName: opts.XSRFCookieName, @@ -205,6 +208,20 @@ func (s *Service) AddProvider(name string, cid string, csecret string) { s.authMiddleware.Providers = s.providers } +// AddDirectProvider adds provider with direct check against data store +// it doesn't do any handshake and uses provided credChecker to verify user and password from the request +func (s *Service) AddDirectProvider(name string, credChecker provider.CredChecker) { + dh := provider.DirectHandler{ + L: s.logger, + ProviderName: name, + Issuer: s.issuer, + TokenService: s.jwtService, + CredChecker: credChecker, + } + s.providers = append(s.providers, provider.NewService(dh)) + s.authMiddleware.Providers = s.providers +} + // DevAuth makes dev oauth2 server, for testing and development only! func (s *Service) DevAuth() (*provider.DevAuthServer, error) { p, err := s.Provider("dev") // peak dev provider diff --git a/backend/vendor/github.com/go-pkgz/auth/go.sum b/backend/vendor/github.com/go-pkgz/auth/go.sum index 63a7900c..ccb2a3f2 100644 --- a/backend/vendor/github.com/go-pkgz/auth/go.sum +++ b/backend/vendor/github.com/go-pkgz/auth/go.sum @@ -16,6 +16,7 @@ github.com/go-pkgz/rest v1.1.4 h1:/Lrg9kBWBjNah7nmCDHLszRAfVVBIy5ajf0vVgpHPi0= github.com/go-pkgz/rest v1.1.4/go.mod h1:DIxxm3vSt6e+IY+UQUOFsfB2YaHLmGoOfPLWN5pxQSA= github.com/go-pkgz/rest v1.1.5 h1:5br4mnscfLb27yxv5hJFLBVmAt09PrmIBP+meA3CfHc= github.com/go-pkgz/rest v1.1.5/go.mod h1:DIxxm3vSt6e+IY+UQUOFsfB2YaHLmGoOfPLWN5pxQSA= +github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022 h1:Ys0rDzh8s4UMlGaDa1UTA0sfKgvF0hQZzTYX8ktjiDc= github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022/go.mod h1:x4NsS+uc7ecH/Cbm9xKQ6XzmJM57rWTkjywjfB2yQ18= 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 dc7067a5..f09432c1 100644 --- a/backend/vendor/github.com/go-pkgz/auth/middleware/auth.go +++ b/backend/vendor/github.com/go-pkgz/auth/middleware/auth.go @@ -126,9 +126,9 @@ func (a *Authenticator) refreshExpiredToken(w http.ResponseWriter, claims token. } // AdminOnly middleware allows access for admins only +// this handler internally wrapped with auth(true) to avoid situation if AdminOnly defined without prior Auth func (a *Authenticator) AdminOnly(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { - user, err := token.GetUserInfo(r) if err != nil { http.Error(w, "Unauthorized", http.StatusUnauthorized) @@ -141,7 +141,7 @@ func (a *Authenticator) AdminOnly(next http.Handler) http.Handler { } next.ServeHTTP(w, r) } - return http.HandlerFunc(fn) + return a.auth(true)(http.HandlerFunc(fn)) // enforce auth } // basic auth for admin user diff --git a/backend/vendor/github.com/go-pkgz/auth/provider/direct.go b/backend/vendor/github.com/go-pkgz/auth/provider/direct.go new file mode 100644 index 00000000..1bad3879 --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/auth/provider/direct.go @@ -0,0 +1,81 @@ +package provider + +import ( + "errors" + "net/http" + + jwt "github.com/dgrijalva/jwt-go" + "github.com/go-pkgz/rest" + + "github.com/go-pkgz/auth/logger" + "github.com/go-pkgz/auth/token" +) + +// DirectHandler implements non-oauth2 provider authorizing user in traditional way with storage +// with users and hashes +type DirectHandler struct { + logger.L + CredChecker CredChecker + ProviderName string + TokenService TokenService + Issuer string +} + +// CredChecker defines interface to check credentials +type CredChecker interface { + Check(user, password string) (ok bool, err error) +} + +// CredCheckerFunc type is an adapter to allow the use of ordinary functions as CredsChecker. +type CredCheckerFunc func(user, password string) (ok bool, err error) + +// Check calls f(user,passwd) +func (f CredCheckerFunc) Check(user, password string) (ok bool, err error) { + return f(user, password) +} + +// Name of the handler +func (p DirectHandler) Name() string { return p.ProviderName } + +// LoginHandler checks "user" and "passwd" against data store and makes jwt if all passed +// GET /something?user=name&password=xyz&sess=[0|1] +func (p DirectHandler) LoginHandler(w http.ResponseWriter, r *http.Request) { + user, password := r.URL.Query().Get("user"), r.URL.Query().Get("passwd") + aud := r.URL.Query().Get("aud") + sessOnly := r.URL.Query().Get("sess") == "1" + if p.CredChecker == nil { + rest.SendErrorJSON(w, r, http.StatusInternalServerError, errors.New("empty credential store"), "no credential store") + return + } + ok, err := p.CredChecker.Check(user, password) + if err != nil { + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "failed to access creds store") + return + } + if !ok { + rest.SendErrorJSON(w, r, http.StatusForbidden, nil, "incorrect user or password") + return + } + claims := token.Claims{ + User: &token.User{Name: user}, + StandardClaims: jwt.StandardClaims{ + Issuer: p.Issuer, + Audience: aud, + }, + SessionOnly: sessOnly, + } + + if err = p.TokenService.Set(w, claims); err != nil { + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "failed to set token") + return + } + rest.RenderJSON(w, r, claims.User) +} + +// AuthHandler doesn't do anyting for direct login as it has no callbacks +func (p DirectHandler) AuthHandler(w http.ResponseWriter, r *http.Request) {} + +// LogoutHandler - GET /logout +func (p DirectHandler) LogoutHandler(w http.ResponseWriter, r *http.Request) { + p.TokenService.Reset(w) +} diff --git a/backend/vendor/github.com/go-pkgz/auth/provider/oauth2.go b/backend/vendor/github.com/go-pkgz/auth/provider/oauth2.go index 17ec0134..b69dcad5 100644 --- a/backend/vendor/github.com/go-pkgz/auth/provider/oauth2.go +++ b/backend/vendor/github.com/go-pkgz/auth/provider/oauth2.go @@ -77,7 +77,7 @@ func (p Oauth2Handler) Name() string { return p.name } // LoginHandler - GET /login?from=redirect-back-url&site=siteID&session=1 func (p Oauth2Handler) LoginHandler(w http.ResponseWriter, r *http.Request) { - p.Logf("[DEBUG] login with %s", p.Name) + p.Logf("[DEBUG] login with %s", p.Name()) // make state (random) and store in session state, err := randToken() if err != nil { diff --git a/backend/vendor/github.com/go-pkgz/auth/token/jwt.go b/backend/vendor/github.com/go-pkgz/auth/token/jwt.go index 96e918b6..13ae2b4d 100644 --- a/backend/vendor/github.com/go-pkgz/auth/token/jwt.go +++ b/backend/vendor/github.com/go-pkgz/auth/token/jwt.go @@ -1,6 +1,8 @@ package token import ( + "encoding/json" + "fmt" "net/http" "time" @@ -48,7 +50,7 @@ type Opts struct { TokenDuration time.Duration CookieDuration time.Duration DisableXSRF bool - + DisableIAT bool // disable IssuedAt claim // optional (custom) names for cookies and headers JWTCookieName string JWTHeaderKey string @@ -95,6 +97,10 @@ func (j *Service) Token(claims Claims) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + if j.SecretReader == nil { + return "", errors.New("secretreader not defined") + } + secret, err := j.SecretReader.Get(claims.Audience) // get secret via consumer defined SecretReader if err != nil { return "", errors.Wrap(err, "can't get secret") @@ -128,6 +134,10 @@ func (j *Service) Parse(tokenString string) (Claims, error) { return Claims{}, errors.Wrap(err, "failed to get aud from token token") } + if j.SecretReader == nil { + return Claims{}, errors.New("secretreader not defined") + } + secret, err := j.SecretReader.Get(aud) if err != nil { return Claims{}, errors.Wrap(err, "can't get secret") @@ -159,7 +169,13 @@ func (j *Service) Set(w http.ResponseWriter, claims Claims) error { claims.ExpiresAt = time.Now().Add(j.TokenDuration).Unix() } - claims.Issuer = j.Issuer + if claims.Issuer == "" { + claims.Issuer = j.Issuer + } + + if !j.DisableIAT { + claims.IssuedAt = time.Now().Unix() + } tokenString, err := j.Token(claims) if err != nil { @@ -280,3 +296,11 @@ type ValidatorFunc func(token string, claims Claims) bool func (f ValidatorFunc) Validate(token string, claims Claims) bool { return f(token, claims) } + +func (c Claims) String() string { + b, err := json.Marshal(c) + if err != nil { + return fmt.Sprintf("%+v %+v", c.StandardClaims, c.User) + } + return string(b) +} 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 afbeb0d5..d91bb433 100644 --- a/backend/vendor/github.com/go-pkgz/auth/token/user.go +++ b/backend/vendor/github.com/go-pkgz/auth/token/user.go @@ -7,7 +7,6 @@ import ( "hash" "hash/crc64" "io" - "log" "net/http" "regexp" @@ -74,7 +73,7 @@ func (u *User) IsAdmin() bool { return u.BoolAttr(adminAttr) } -// HashID tries to has val with hash.Hash and fallback to crc if needed +// HashID tries to hash val with hash.Hash and fallback to crc if needed func HashID(h hash.Hash, val string) string { if reValidSha.MatchString(val) { @@ -83,7 +82,9 @@ func HashID(h hash.Hash, val string) string { if _, err := io.WriteString(h, val); err != nil { // fail back to crc64 - log.Printf("[WARN] can't hash id %s, %s", val, err) + if val == "" { + val = "!empty string!" + } if reValidCrc64.MatchString(val) { return val // already crced }