diff --git a/Dockerfile b/Dockerfile index d233fcf..93bd079 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,7 +35,7 @@ RUN apk add --no-cache tzdata COPY --from=go-build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=go-build /app/service . -COPY --from=go-build /app/*.yml ./ +COPY --from=go-build /app/config/*.yml ./ RUN chmod +x /app/service USER nobody diff --git a/dev.yml b/config/dev.yml similarity index 60% rename from dev.yml rename to config/dev.yml index 7cc2f40..2b99a2a 100644 --- a/dev.yml +++ b/config/dev.yml @@ -2,7 +2,7 @@ title: Super Graph Development host_port: 0.0.0.0:8080 web_ui: true debug_level: 1 -enable_tracing: false +enable_tracing: true # Throw a 401 on auth failure for queries that need auth # valid values: always, per_query, never @@ -25,21 +25,32 @@ auth_fail_block: never # sheep: sheep auth: - type: header + # Can be 'rails' or 'jwt' + type: rails cookie: _app_session + + # Comment this out if you want to disable setting + # the user_id via a header. Good for testing header: X-User-ID - # rails_cookie: - # secret_key_base: caf335bfcfdb04e50db5bb0a4d67ab9... + rails: + # Rails version this is used for reading the + # various cookies formats. + version: 5.2 - # rails_memcache: - # host: 127.0.0.1 + # Found in 'Rails.application.config.secret_key_base' + secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566 + + # Remote cookie store. (memcache or redis) + # url: redis://127.0.0.1:6379 + # password: test + # max_idle: 80, + # max_active: 12000, - # rails_redis: - # url: redis://127.0.0.1:6379 - # password: "" - # max_idle: 80, - # max_active: 12000, + # In most cases you don't need these + # salt: "encrypted cookie" + # sign_salt: "signed encrypted cookie" + # auth_salt: "authenticated encrypted cookie" # jwt: # provider: auth0 @@ -47,6 +58,7 @@ auth: # public_key_file: /secrets/public_key.pem # public_key_type: ecdsa #rsa + database: type: postgres host: db @@ -77,18 +89,24 @@ database: fields: - name: users + # This filter will overwrite defaults.filter filter: ["{ id: { eq: $user_id } }"] - name: products + # Multiple filters are AND'd together filter: [ "{ price: { gt: 0 } }", "{ price: { lt: 8 } }" ] - name: customers + # No filter is used for this field not + # even defaults.filter filter: none - - name: me + - # You can create new fields that have a + # real db table backing them + name: me table: users filter: ["{ id: { eq: $user_id } }"] diff --git a/config/prod.yml b/config/prod.yml new file mode 100644 index 0000000..2ef6c59 --- /dev/null +++ b/config/prod.yml @@ -0,0 +1,113 @@ +title: Super Graph Production +host_port: 0.0.0.0:8080 +web_ui: false +debug_level: 0 +enable_tracing: false + +# Throw a 401 on auth failure for queries that need auth +# valid values: always, per_query, never +auth_fail_block: always + +# Postgres related environment Variables +# SG_DATABASE_HOST +# SG_DATABASE_PORT +# SG_DATABASE_USER +# SG_DATABASE_PASSWORD + +# Auth related environment Variables +# SG_AUTH_RAILS_COOKIE_SECRET_KEY_BASE +# SG_AUTH_RAILS_REDIS_URL +# SG_AUTH_RAILS_REDIS_PASSWORD +# SG_AUTH_JWT_PUBLIC_KEY_FILE + +# inflections: +# person: people +# sheep: sheep + +auth: + # Can be 'rails' or 'jwt' + type: rails + cookie: _app_session + + # Comment this out if you want to disable setting + # the user_id via a header. Good for testing + header: X-User-ID + + rails: + # Rails version this is used for reading the + # various cookies formats. + version: 5.2 + + # Found in 'Rails.application.config.secret_key_base' + secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566 + + # Remote cookie store. (memcache or redis) + # url: redis://127.0.0.1:6379 + # password: test + # max_idle: 80, + # max_active: 12000, + + # In most cases you don't need these + # salt: "encrypted cookie" + # sign_salt: "signed encrypted cookie" + # auth_salt: "authenticated encrypted cookie" + + # jwt: + # provider: auth0 + # secret: abc335bfcfdb04e50db5bb0a4d67ab9 + # public_key_file: /secrets/public_key.pem + # public_key_type: ecdsa #rsa + +database: + type: postgres + host: db + port: 5432 + dbname: app_development + user: postgres + password: '' + #pool_size: 10 + #max_retries: 0 + #log_level: "debug" + + # Define variables here that you want to use in filters + variables: + account_id: "select account_id from users where id = $user_id" + + # Define defaults to for the field key and values below + defaults: + filter: ["{ user_id: { eq: $user_id } }"] + + # Fields and table names that you wish to block + blacklist: + - ar_internal_metadata + - schema_migrations + - secret + - password + - encrypted + - token + + fields: + - name: users + # This filter will overwrite defaults.filter + filter: ["{ id: { eq: $user_id } }"] + + - name: products + # Multiple filters are AND'd together + filter: [ + "{ price: { gt: 0 } }", + "{ price: { lt: 8 } }" + ] + + - name: customers + # No filter is used for this field not + # even defaults.filter + filter: none + + - # You can create new fields that have a + # real db table backing them + name: me + table: users + filter: ["{ id: { eq: $user_id } }"] + + # - name: posts + # filter: ["{ account_id: { _eq: $account_id } }"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 369fc9c..11030a6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,13 @@ services: db: image: postgres + # nginx: + # image: nginx:latest + # volumes: + # - ./example/nginx.conf:/etc/nginx/nginx.conf + # ports: + # - 3001:3001 + super_graph: build: context: . @@ -27,4 +34,5 @@ services: - "3000:3000" depends_on: - db - - super_graph \ No newline at end of file + - super_graph + # - nginx \ No newline at end of file diff --git a/docs/guide.md b/docs/guide.md index 8a3ba9f..7f8ff9c 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -423,21 +423,32 @@ auth_fail_block: never # sheep: sheep auth: - type: header + # Can be 'rails' or 'jwt' + type: rails cookie: _app_session + + # Comment this out if you want to disable setting + # the user_id via a header. Good for testing header: X-User-ID - # rails_cookie: - # secret_key_base: caf335bfcfdb04e50db5bb0a4d67ab9... + rails: + # Rails version this is used for reading the + # various cookies formats. + version: 5.2 - # rails_memcache: - # host: 127.0.0.1 + # Found in 'Rails.application.config.secret_key_base' + secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566 + + # Remote cookie store. (memcache or redis) + # url: redis://127.0.0.1:6379 + # password: test + # max_idle: 80, + # max_active: 12000, - # rails_redis: - # url: redis://127.0.0.1:6379 - # password: "" - # max_idle: 80, - # max_active: 12000, + # In most cases you don't need these + # salt: "encrypted cookie" + # sign_salt: "signed encrypted cookie" + # auth_salt: "authenticated encrypted cookie" # jwt: # provider: auth0 @@ -452,9 +463,9 @@ database: dbname: app_development user: postgres password: '' - #pool_size: 10 - #max_retries: 0 - #log_level: "debug" + # pool_size: 10 + # max_retries: 0 + # log_level: "debug" # Define variables here that you want to use in filters variables: diff --git a/example/config/initializers/devise.rb b/example/config/initializers/devise.rb index 21c58fa..ec9f0cb 100644 --- a/example/config/initializers/devise.rb +++ b/example/config/initializers/devise.rb @@ -8,7 +8,7 @@ Devise.setup do |config| # confirmation, reset password and unlock tokens in the database. # Devise will use the `secret_key_base` as its `secret_key` # by default. You can change it below and use your own secret key. - # config.secret_key = 'da45f01cc03f113df2ac8788aab0833ff0ce6d3f2e298e964e0b35c7f63877588054a503106f0102d412987a45ed49e9a49697a6cf66da22f04ca6dafd24f76d' + # config.secret_key = '' # ==> Controller configuration # Configure the parent class to the devise controllers. diff --git a/example/config/initializers/secret_tokens.rb b/example/config/initializers/secret_tokens.rb new file mode 100644 index 0000000..d118b7e --- /dev/null +++ b/example/config/initializers/secret_tokens.rb @@ -0,0 +1 @@ +Rails.application.config.secret_key_base= '0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566' \ No newline at end of file diff --git a/example/nginx.conf b/example/nginx.conf new file mode 100644 index 0000000..1107f6e --- /dev/null +++ b/example/nginx.conf @@ -0,0 +1,25 @@ +events { } + +http { + server { + server_name localhost; + listen 3001; + + location /api/v1/graphql { + proxy_pass http://super_graph:8080; + proxy_redirect off; + #rewrite ^/api/v1/graphql$ $1 break; + } + + location /super-graph { + proxy_pass http://super_graph:8080; + proxy_redirect off; + rewrite ^/super-graph/(.*)$ /$1 break; + } + + location / { + proxy_pass http://web:3000; + proxy_redirect off; + } + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index 2618383..2637978 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/dosco/super-graph require ( + github.com/Masterminds/semver v1.4.2 github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 github.com/dgrijalva/jwt-go v3.2.0+incompatible @@ -13,6 +14,7 @@ require ( github.com/sirupsen/logrus v1.4.0 github.com/spf13/viper v1.3.1 github.com/valyala/fasttemplate v1.0.1 + golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a mellium.im/sasl v0.2.1 // indirect ) diff --git a/go.sum b/go.sum index 4ac72d2..e83b729 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3 h1:+qz9Ga6l6lKw6fgvk5RMV5HQznSLvI8Zxajwdj4FhFg= github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3/go.mod h1:FlkD11RtgMTYjVuBnb7cxoHmQGqvPpCsr2atC88nl/M= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= diff --git a/prod.yml b/prod.yml deleted file mode 100644 index 17d24ed..0000000 --- a/prod.yml +++ /dev/null @@ -1,79 +0,0 @@ -title: Super Graph Production -host_port: 0.0.0.0:8080 -web_ui: false -debug_level: 0 -enable_tracing: false - -# Throw a 401 on auth failure for queries that need auth -# valid values: always, per_query, never -auth_fail_block: always - -# Postgres related environment Variables -# SG_DATABASE_HOST -# SG_DATABASE_PORT -# SG_DATABASE_USER -# SG_DATABASE_PASSWORD - -# Auth related environment Variables -# SG_AUTH_RAILS_COOKIE_SECRET_KEY_BASE -# SG_AUTH_RAILS_REDIS_URL -# SG_AUTH_RAILS_REDIS_PASSWORD -# SG_AUTH_JWT_PUBLIC_KEY_FILE - -# inflections: -# person: people -# sheep: sheep - -auth: - type: cookie - cookie: _app_session - - rails_cookie: - secret_key_base: caf335bfcfdb04e50db5bb0a4d67ab9... - - # rails_memcache: - # host: 127.0.0.1 - - # rails_redis: - # url: redis://127.0.0.1:6379 - # password: "" - # max_idle: 80, - # max_active: 12000, - - # jwt: - # provider: auth0 - # secret: abc335bfcfdb04e50db5bb0a4d67ab9 - # public_key_file: /secrets/public_key.pem - # public_key_type: ecdsa #rsa - -database: - type: postgres - host: db - port: 5432 - dbname: app_development - user: postgres - password: '' - #pool_size: 10 - #max_retries: 0 - #log_level: "debug" - - # Define variables here that you want to use in filters - variables: - account_id: "select account_id from users where id = $user_id" - - # Define defaults to for the field key and values below - defaults: - filter: ["{ id: { _eq: $user_id } }"] - - # Fields and table names that you wish to block - blacklist: - - ar_internal_metadata - - schema_migrations - - secret - - password - - encrypted - - token - - fields: - - name: users - filter: ["{ id: { _eq: $user_id } }"] \ No newline at end of file diff --git a/rails/auth.go b/rails/auth.go new file mode 100644 index 0000000..98e0de7 --- /dev/null +++ b/rails/auth.go @@ -0,0 +1,171 @@ +package rails + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/Masterminds/semver" + "github.com/adjust/gorails/marshal" +) + +const ( + salt = "encrypted cookie" + signSalt = "signed encrypted cookie" + authSalt = "authenticated encrypted cookie" + railsCipher = "aes-256-cbc" + railsCipher52 = "aes-256-gcm" +) + +var ( + errSessionData = errors.New("error decoding session data") +) + +type Auth struct { + Cipher string + Secret string + Salt string + SignSalt string + AuthSalt string +} + +func NewAuth(version, secret string) (*Auth, error) { + ra := &Auth{ + Secret: secret, + Salt: salt, + SignSalt: signSalt, + AuthSalt: authSalt, + } + + ver, err := semver.NewVersion(version) + if err != nil { + return nil, fmt.Errorf("rails auth: %s", err) + } + + gt52, err := semver.NewConstraint(">= 5.2") + if err != nil { + return nil, fmt.Errorf("rails auth: %s", err) + } + + if gt52.Check(ver) { + ra.Cipher = railsCipher52 + } else { + ra.Cipher = railsCipher + } + + return ra, nil +} + +func (ra *Auth) ParseCookie(cookie string) (userID string, err error) { + var dcookie []byte + + switch ra.Cipher { + case railsCipher: + dcookie, err = parseCookie(cookie, ra.Secret, ra.Salt, ra.SignSalt) + + case railsCipher52: + dcookie, err = parseCookie52(cookie, ra.Secret, ra.AuthSalt) + + default: + err = fmt.Errorf("unknown rails cookie cipher '%s'", ra.Cipher) + } + + if err != nil { + return + } + + if dcookie[0] != '{' { + userID, err = getUserId4(dcookie) + } else { + userID, err = getUserId(dcookie) + } + + return +} + +func ParseCookie(cookie string) (string, error) { + if cookie[0] != '{' { + return getUserId4([]byte(cookie)) + } + + return getUserId([]byte(cookie)) +} + +func getUserId(data []byte) (userID string, err error) { + var sessionData map[string]interface{} + + err = json.Unmarshal(data, &sessionData) + if err != nil { + return + } + + userKey, ok := sessionData["warden.user.user.key"] + if !ok { + err = errors.New("key 'warden.user.user.key' not found in session data") + } + + items, ok := userKey.([]interface{}) + if !ok { + err = errSessionData + return + } + + if len(items) != 2 { + err = errSessionData + return + } + + uids, ok := items[0].([]interface{}) + if !ok { + err = errSessionData + return + } + + uid, ok := uids[0].(float64) + if !ok { + err = errSessionData + return + } + userID = fmt.Sprintf("%d", int64(uid)) + + return +} + +func getUserId4(data []byte) (userID string, err error) { + sessionData, err := marshal.CreateMarshalledObject(data).GetAsMap() + if err != nil { + return + } + + wardenData, ok := sessionData["warden.user.user.key"] + if !ok { + err = errSessionData + return + } + + wardenUserKey, err := wardenData.GetAsArray() + if err != nil { + return + } + if len(wardenUserKey) < 1 { + err = errSessionData + return + } + + userData, err := wardenUserKey[0].GetAsArray() + if err != nil { + return + } + if len(userData) < 1 { + err = errSessionData + return + } + + uid, err := userData[0].GetAsInteger() + if err != nil { + return + } + userID = fmt.Sprintf("%d", uid) + + return +} diff --git a/serv/auth_test.go b/rails/auth_test.go similarity index 53% rename from serv/auth_test.go rename to rails/auth_test.go index 996690f..66a19f8 100644 --- a/serv/auth_test.go +++ b/rails/auth_test.go @@ -1,15 +1,22 @@ -package serv +package rails import ( "testing" ) -func TestRailsEncryptedSession(t *testing.T) { +func TestRailsEncryptedSession1(t *testing.T) { cookie := "dDdjMW5jYUNYaFpBT1BSdFgwQkk4ZWNlT214L1FnM0pyZzZ1d21nSnVTTm9zS0ljN000S1JmT3cxcTNtRld2Ny0tQUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09--75d8323b0f0e41cf4d5aabee1b229b1be76b83b6" secret := "development_secret" - userID, err := railsAuth(cookie, secret) + ra := Auth{ + Cipher: railsCipher, + Secret: secret, + Salt: salt, + SignSalt: signSalt, + } + + userID, err := ra.ParseCookie(cookie) if err != nil { t.Error(err) return @@ -20,6 +27,29 @@ func TestRailsEncryptedSession(t *testing.T) { } } +func TestRailsEncryptedSession52(t *testing.T) { + cookie := + "fZy1lt%2FIuXh2cpQgy3wWjbvabh1AqJX%2Bt6qO4D95DOZIpDhMyK2HqPFeNoaBtrXCUa9%2BDQuvbs1GX6tuccEAp14QPLNhm0PPJS5U1pRHqPLWaqT%2BBPYP%2BY9bo677komm9CPuOCOqBKf7rv3%2F4ptLmVO7iefB%2FP2ZlkV1848Johv5q%2B5PGyMxII2BEQnBdS3Petw6lRu741Bquc8z9VofC3t4%2F%2BLxVz%2BvBbTg--VL0MorYITXB8Dj3W--0yr0sr6pRU%2FwlYMQ%2BpEifA%3D%3D" + + secret := "0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566" + + ra := Auth{ + Cipher: railsCipher52, + Secret: secret, + AuthSalt: authSalt, + } + + userID, err := ra.ParseCookie(cookie) + if err != nil { + t.Error(err) + return + } + + if userID != "2" { + t.Errorf("Expecting userID 2 got %s", userID) + } +} + func TestRailsJsonSession(t *testing.T) { sessionData := `{"warden.user.user.key":[[1],"secret"]}` diff --git a/rails/cookie.go b/rails/cookie.go new file mode 100644 index 0000000..c0a0b4b --- /dev/null +++ b/rails/cookie.go @@ -0,0 +1,62 @@ +package rails + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha1" + "encoding/base64" + "net/url" + "strings" + + "github.com/adjust/gorails/session" + "golang.org/x/crypto/pbkdf2" +) + +func parseCookie(cookie, secretKeyBase, salt, signSalt string) ([]byte, error) { + return session.DecryptSignedCookie( + cookie, + secretKeyBase, + salt, + signSalt) +} + +// {"session_id":"a71d6ffcd4ed5572ea2097f569eb95ef","warden.user.user.key":[[2],"$2a$11$q9Br7m4wJxQvF11hAHvTZO"],"_csrf_token":"HsYgrD2YBaWAabOYceN0hluNRnGuz49XiplmMPt43aY="} + +func parseCookie52(cookie, secretKeyBase, authSalt string) ([]byte, error) { + ecookie, err := url.QueryUnescape(cookie) + if err != nil { + return nil, err + } + + vectors := strings.Split(ecookie, "--") + + body, err := base64.RawStdEncoding.DecodeString(vectors[0]) + if err != nil { + return nil, err + } + + iv, err := base64.RawStdEncoding.DecodeString(vectors[1]) + if err != nil { + return nil, err + } + + tag, err := base64.StdEncoding.DecodeString(vectors[2]) + if err != nil { + return nil, err + } + + key := pbkdf2.Key([]byte(secretKeyBase), []byte(authSalt), + 1000, 32, sha1.New) + + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(c) + if err != nil { + return nil, err + } + + return gcm.Open(nil, iv, append(body, tag...), nil) +} diff --git a/serv/auth.go b/serv/auth.go index 09df1a3..e597a9b 100644 --- a/serv/auth.go +++ b/serv/auth.go @@ -2,62 +2,47 @@ package serv import ( "context" - "errors" "net/http" -) - -const ( - salt = "encrypted cookie" - signSalt = "signed encrypted cookie" - emptySecret = "" - authHeader = "Authorization" + "strings" ) var ( userIDProviderKey = struct{}{} userIDKey = struct{}{} - errSessionData = errors.New("error decoding session data") ) -func headerHandler(next http.HandlerFunc) http.HandlerFunc { - fn := conf.Auth.Header - if len(fn) == 0 { - panic(errors.New("no auth.header defined")) +func headerAuth(r *http.Request, c *config) *http.Request { + if len(c.Auth.Header) == 0 { + return nil } - return func(w http.ResponseWriter, r *http.Request) { - userID := r.Header.Get(fn) - if len(userID) == 0 { - next.ServeHTTP(w, r) - return - } - + userID := r.Header.Get(c.Auth.Header) + if len(userID) != 0 { ctx := context.WithValue(r.Context(), userIDKey, userID) - next.ServeHTTP(w, r.WithContext(ctx)) + return r.WithContext(ctx) } + + return nil } func withAuth(next http.HandlerFunc) http.HandlerFunc { at := conf.Auth.Type + ru := conf.Auth.Rails.URL switch at { - case "header": - return headerHandler(next) + case "rails": + if strings.HasPrefix(ru, "memcache:") { + return railsMemcacheHandler(next) + } + + if strings.HasPrefix(ru, "redis:") { + return railsRedisHandler(next) + } - case "rails_cookie": return railsCookieHandler(next) - case "rails_memcache": - return railsMemcacheHandler(next) - - case "rails_redis": - return railsRedisHandler(next) - case "jwt": return jwtHandler(next) - - default: - return next } return next diff --git a/serv/auth_jwt.go b/serv/auth_jwt.go index 14d0752..9ba4528 100644 --- a/serv/auth_jwt.go +++ b/serv/auth_jwt.go @@ -10,7 +10,8 @@ import ( ) const ( - jwtBase int = iota + authHeader = "Authorization" + jwtBase int = iota jwtAuth0 ) @@ -57,6 +58,11 @@ func jwtHandler(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var tok string + if rn := headerAuth(r, conf); rn != nil { + next.ServeHTTP(w, rn) + return + } + if len(cookie) != 0 { ck, err := r.Cookie(cookie) if err != nil { diff --git a/serv/auth_rails.go b/serv/auth_rails.go index 3b71107..99b5b19 100644 --- a/serv/auth_rails.go +++ b/serv/auth_rails.go @@ -2,14 +2,14 @@ package serv import ( "context" - "encoding/json" "errors" "fmt" + "log" "net/http" + "net/url" - "github.com/adjust/gorails/marshal" - "github.com/adjust/gorails/session" "github.com/bradfitz/gomemcache/memcache" + "github.com/dosco/super-graph/rails" "github.com/garyburd/redigo/redis" ) @@ -19,21 +19,20 @@ func railsRedisHandler(next http.HandlerFunc) http.HandlerFunc { panic(errors.New("no auth.cookie defined")) } - authURL := conf.Auth.RailsRedis.URL - if len(authURL) == 0 { - panic(errors.New("no auth.rails_redis.url defined")) + if len(conf.Auth.Rails.URL) == 0 { + log.Fatal(errors.New("no auth.rails.url defined")) } rp := &redis.Pool{ - MaxIdle: conf.Auth.RailsRedis.MaxIdle, - MaxActive: conf.Auth.RailsRedis.MaxActive, + MaxIdle: conf.Auth.Rails.MaxIdle, + MaxActive: conf.Auth.Rails.MaxActive, Dial: func() (redis.Conn, error) { - c, err := redis.DialURL(authURL) + c, err := redis.DialURL(conf.Auth.Rails.URL) if err != nil { panic(err) } - pwd := conf.Auth.RailsRedis.Password + pwd := conf.Auth.Rails.Password if len(pwd) != 0 { if _, err := c.Do("AUTH", pwd); err != nil { panic(err) @@ -44,6 +43,11 @@ func railsRedisHandler(next http.HandlerFunc) http.HandlerFunc { } return func(w http.ResponseWriter, r *http.Request) { + if rn := headerAuth(r, conf); rn != nil { + next.ServeHTTP(w, rn) + return + } + ck, err := r.Cookie(cookie) if err != nil { next.ServeHTTP(w, r) @@ -57,7 +61,7 @@ func railsRedisHandler(next http.HandlerFunc) http.HandlerFunc { return } - userID, err := railsAuth(string(sessionData), emptySecret) + userID, err := rails.ParseCookie(string(sessionData)) if err != nil { next.ServeHTTP(w, r) return @@ -74,14 +78,23 @@ func railsMemcacheHandler(next http.HandlerFunc) http.HandlerFunc { panic(errors.New("no auth.cookie defined")) } - host := conf.Auth.RailsMemcache.Host - if len(host) == 0 { - panic(errors.New("no auth.rails_memcache.host defined")) + if len(conf.Auth.Rails.URL) == 0 { + log.Fatal(errors.New("no auth.rails.url defined")) } - mc := memcache.New(host) + rURL, err := url.Parse(conf.Auth.Rails.URL) + if err != nil { + log.Fatal(err) + } + + mc := memcache.New(rURL.Host) return func(w http.ResponseWriter, r *http.Request) { + if rn := headerAuth(r, conf); rn != nil { + next.ServeHTTP(w, rn) + return + } + ck, err := r.Cookie(cookie) if err != nil { next.ServeHTTP(w, r) @@ -95,7 +108,7 @@ func railsMemcacheHandler(next http.HandlerFunc) http.HandlerFunc { return } - userID, err := railsAuth(string(item.Value), emptySecret) + userID, err := rails.ParseCookie(string(item.Value)) if err != nil { next.ServeHTTP(w, r) return @@ -112,11 +125,17 @@ func railsCookieHandler(next http.HandlerFunc) http.HandlerFunc { panic(errors.New("no auth.cookie defined")) } - secret := conf.Auth.RailsCookie.SecretKeyBase - if len(secret) == 0 { - panic(errors.New("no auth.rails_cookie.secret_key_base defined")) + ra, err := railsAuth(conf) + if err != nil { + log.Fatal(err) } + return func(w http.ResponseWriter, r *http.Request) { + if rn := headerAuth(r, conf); rn != nil { + next.ServeHTTP(w, rn) + return + } + ck, err := r.Cookie(cookie) if err != nil { logger.Error(err) @@ -124,7 +143,7 @@ func railsCookieHandler(next http.HandlerFunc) http.HandlerFunc { return } - userID, err := railsAuth(ck.Value, secret) + userID, err := ra.ParseCookie(ck.Value) if err != nil { logger.Error(err) next.ServeHTTP(w, r) @@ -136,98 +155,33 @@ func railsCookieHandler(next http.HandlerFunc) http.HandlerFunc { } } -func railsAuth(cookie, secret string) (userID string, err error) { - var dcookie []byte +func railsAuth(c *config) (*rails.Auth, error) { + secret := c.Auth.Rails.SecretKeyBase + if len(secret) == 0 { + return nil, errors.New("no auth.rails.secret_key_base defined") + } - dcookie, err = session.DecryptSignedCookie(cookie, secret, salt, signSalt) + version := c.Auth.Rails.Version + if len(version) == 0 { + return nil, errors.New("no auth.rails.version defined") + } + + ra, err := rails.NewAuth(version, secret) if err != nil { - return + return nil, err } - if dcookie[0] != '{' { - userID, err = getUserId4(dcookie) - } else { - userID, err = getUserId(dcookie) + if len(c.Auth.Rails.Salt) != 0 { + ra.Salt = c.Auth.Rails.Salt } - return -} - -func getUserId(data []byte) (userID string, err error) { - var sessionData map[string]interface{} - - err = json.Unmarshal(data, &sessionData) - if err != nil { - return - } - - userKey, ok := sessionData["warden.user.user.key"] - if !ok { - err = errors.New("key 'warden.user.user.key' not found in session data") - } - - items, ok := userKey.([]interface{}) - if !ok { - err = errSessionData - return - } - - if len(items) != 2 { - err = errSessionData - return - } - - uids, ok := items[0].([]interface{}) - if !ok { - err = errSessionData - return - } - - uid, ok := uids[0].(float64) - if !ok { - err = errSessionData - return - } - userID = fmt.Sprintf("%d", int64(uid)) - - return -} - -func getUserId4(data []byte) (userID string, err error) { - sessionData, err := marshal.CreateMarshalledObject(data).GetAsMap() - if err != nil { - return - } - - wardenData, ok := sessionData["warden.user.user.key"] - if !ok { - err = errSessionData - return - } - - wardenUserKey, err := wardenData.GetAsArray() - if err != nil { - return - } - if len(wardenUserKey) < 1 { - err = errSessionData - return - } - - userData, err := wardenUserKey[0].GetAsArray() - if err != nil { - return - } - if len(userData) < 1 { - err = errSessionData - return - } - - uid, err := userData[0].GetAsInteger() - if err != nil { - return - } - userID = fmt.Sprintf("%d", uid) - - return + if len(conf.Auth.Rails.SignSalt) != 0 { + ra.SignSalt = c.Auth.Rails.SignSalt + } + + if len(conf.Auth.Rails.AuthSalt) != 0 { + ra.AuthSalt = c.Auth.Rails.AuthSalt + } + + return ra, nil } diff --git a/serv/serv.go b/serv/serv.go index 4dbb35d..a6f5c34 100644 --- a/serv/serv.go +++ b/serv/serv.go @@ -48,20 +48,17 @@ type config struct { Cookie string Header string - RailsCookie struct { + Rails struct { + Version string SecretKeyBase string `mapstructure:"secret_key_base"` - } `mapstructure:"rails_cookie"` - - RailsMemcache struct { - Host string - } `mapstructure:"rails_memcache"` - - RailsRedis struct { - URL string - Password string - MaxIdle int `mapstructure:"max_idle"` - MaxActive int `mapstructure:"max_active"` - } `mapstructure:"rails_redis"` + URL string + Password string + MaxIdle int `mapstructure:"max_idle"` + MaxActive int `mapstructure:"max_active"` + Salt string + SignSalt string `mapstructure:"sign_salt"` + AuthSalt string `mapstructure:"auth_salt"` + } JWT struct { Provider string