зеркало из https://github.com/mozilla/protodash.git
Create a PKCE auth strategy based on goth
This commit is contained in:
Родитель
4a94e3d917
Коммит
1874ac5180
4
go.mod
4
go.mod
|
@ -5,9 +5,13 @@ go 1.14
|
|||
require (
|
||||
cloud.google.com/go/storage v1.12.0
|
||||
github.com/gobuffalo/flect v0.2.2
|
||||
github.com/jarcoal/httpmock v1.0.6
|
||||
github.com/justinas/alice v1.2.0
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/markbates/goth v1.65.0
|
||||
github.com/rs/zerolog v1.20.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
|
||||
google.golang.org/api v0.34.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
)
|
||||
|
|
29
go.sum
29
go.sum
|
@ -13,8 +13,9 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
|
|||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.66.0 h1:DZeAkuQGQqnm9Xv36SbMJEU8aFBz4wL04UpMWPWwjzg=
|
||||
cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko=
|
||||
cloud.google.com/go v0.67.0 h1:YIkzmqUfVGiGPpT98L8sVvUIkDno6UlrDxw4NR6z5ak=
|
||||
cloud.google.com/go v0.67.0/go.mod h1:YNan/mUhNZFrYUor0vqrsQ0Ffl7Xtm/ACOy/vsTS858=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
|
@ -45,6 +46,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
|||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
|
@ -104,9 +107,16 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
|||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
|
||||
github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
|
@ -118,7 +128,12 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
|
||||
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
|
||||
github.com/markbates/goth v1.65.0/go.mod h1:65frybxoeSCfORin51KOKqAKbIh7wREIDvdCkdWj//4=
|
||||
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
|
@ -128,6 +143,7 @@ github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
|
|||
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -199,8 +215,10 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
|
|||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo=
|
||||
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -297,8 +315,9 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc
|
|||
golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20200918232735-d647fc253266 h1:k7tVuG0g1JwmD3Jh8oAl1vQ1C3jb4Hi/dUl1wWDBJpQ=
|
||||
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f h1:18s2P7JILnVhIF2+ZtGJQ9czV5bvTsb13/UGtNPDbjA=
|
||||
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -362,8 +381,9 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D
|
|||
google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5 h1:B9nroC8SSX5GtbVvxPF9tYIVkaCpjhVLOrlAY8ONzm8=
|
||||
google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe h1:6SgESkjJknFUnsfQ2yxQbmTAi37BxhwS/riq+VdLo9c=
|
||||
google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
@ -394,6 +414,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
package pkce
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/markbates/goth"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
authPath = "/authorize"
|
||||
tokenPath = "/oauth/token"
|
||||
profilePath = "/userinfo"
|
||||
protocol = "https://"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
*oauth2.Config
|
||||
ProfileURL string
|
||||
name string
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
NickName string `json:"nickname"`
|
||||
UserID string `json:"sub"`
|
||||
}
|
||||
|
||||
func New(clientID, redirectURI, domain string, scopes ...string) *Provider {
|
||||
p := &Provider{
|
||||
Config: &oauth2.Config{
|
||||
ClientID: clientID,
|
||||
RedirectURL: redirectURI,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: protocol + domain + authPath,
|
||||
TokenURL: protocol + domain + tokenPath,
|
||||
},
|
||||
},
|
||||
ProfileURL: protocol + domain + profilePath,
|
||||
name: "pkce",
|
||||
}
|
||||
if len(scopes) > 0 {
|
||||
p.Config.Scopes = make([]string, len(scopes))
|
||||
for i, scope := range scopes {
|
||||
p.Config.Scopes[i] = scope
|
||||
}
|
||||
} else {
|
||||
p.Config.Scopes = []string{"openid", "profile", "email"}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Provider) Name() string {
|
||||
return p.name
|
||||
}
|
||||
|
||||
func (p *Provider) SetName(name string) {
|
||||
p.name = name
|
||||
}
|
||||
|
||||
func (p *Provider) BeginAuth(state string) (goth.Session, error) {
|
||||
cv, err := codeVerifier()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc := codeChallenge(cv)
|
||||
|
||||
s := &Session{
|
||||
AuthURL: p.Config.AuthCodeURL(
|
||||
state,
|
||||
oauth2.SetAuthURLParam("code_challenge", cc),
|
||||
oauth2.SetAuthURLParam("code_challenge_method", "S256"),
|
||||
),
|
||||
CodeVerifier: cv,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
|
||||
s := session.(*Session)
|
||||
user := goth.User{
|
||||
AccessToken: s.AccessToken,
|
||||
Provider: p.Name(),
|
||||
RefreshToken: s.RefreshToken,
|
||||
ExpiresAt: s.ExpiresAt,
|
||||
}
|
||||
|
||||
if user.AccessToken == "" {
|
||||
return user, fmt.Errorf("%s cannot get user information without accessToken", p.Name())
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, p.ProfileURL, nil)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+s.AccessToken)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
if resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return user, fmt.Errorf("%s responded with a %d while trying to fetch user information", p.Name(), resp.StatusCode)
|
||||
}
|
||||
|
||||
var rawData map[string]interface{}
|
||||
userInfo := &UserInfo{}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &rawData)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, userInfo)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
user.Email = userInfo.Email
|
||||
user.Name = userInfo.Name
|
||||
user.NickName = userInfo.NickName
|
||||
user.RawData = rawData
|
||||
user.UserID = userInfo.UserID
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (p *Provider) Debug(_ bool) {}
|
||||
|
||||
func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
|
||||
token := &oauth2.Token{
|
||||
RefreshToken: refreshToken,
|
||||
}
|
||||
tokenSource := p.Config.TokenSource(context.Background(), token)
|
||||
newToken, err := tokenSource.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newToken, nil
|
||||
}
|
||||
|
||||
func (p *Provider) RefreshTokenAvailable() bool {
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package pkce_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/mozilla/protodash/pkce"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
pkceDomain = "pkce.example.com"
|
||||
pkceClientID = "some-client-id"
|
||||
pkceRedirectURI = "/callback"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
p := provider()
|
||||
expectedProfileURL := fmt.Sprintf("https://%s/userinfo", pkceDomain)
|
||||
|
||||
assert.Equal(t, pkceClientID, p.ClientID)
|
||||
assert.Equal(t, expectedProfileURL, p.ProfileURL)
|
||||
assert.Equal(t, pkceRedirectURI, p.RedirectURL)
|
||||
}
|
||||
|
||||
func TestImplementsProvider(t *testing.T) {
|
||||
p := provider()
|
||||
assert.Implements(t, (*goth.Provider)(nil), p)
|
||||
}
|
||||
|
||||
func TestBeginAuth(t *testing.T) {
|
||||
p := provider()
|
||||
expectedAuthURL := fmt.Sprintf("https://%s/authorize", pkceDomain)
|
||||
|
||||
session, err := p.BeginAuth("test_state")
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := session.(*pkce.Session)
|
||||
assert.Contains(t, s.AuthURL, expectedAuthURL)
|
||||
}
|
||||
|
||||
func TestUnmarshalSession(t *testing.T) {
|
||||
p := provider()
|
||||
expectedAuthURL := "https://" + pkceDomain + "/oauth/authorize"
|
||||
|
||||
sessionResp := fmt.Sprintf(`{"AuthURL":"%s","AccessToken":"1234567890"}`, expectedAuthURL)
|
||||
session, err := p.UnmarshalSession(sessionResp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
s := session.(*pkce.Session)
|
||||
assert.Equal(t, expectedAuthURL, s.AuthURL)
|
||||
assert.Equal(t, "1234567890", s.AccessToken)
|
||||
}
|
||||
|
||||
func TestFetchUser(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
sampleResp := `{
|
||||
"email_verified": false,
|
||||
"email": "test.account@userinfo.com",
|
||||
"clientID": "q2hnj2iu...",
|
||||
"updated_at": "2016-12-05T15:15:40.545Z",
|
||||
"name": "test.account@userinfo.com",
|
||||
"picture": "https://s.gravatar.com/avatar/dummy.png",
|
||||
"user_id": "auth0|58454...",
|
||||
"nickname": "test.account",
|
||||
"identities": [
|
||||
{
|
||||
"user_id": "58454...",
|
||||
"provider": "auth0",
|
||||
"connection": "Username-Password-Authentication",
|
||||
"isSocial": false
|
||||
}],
|
||||
"created_at": "2016-12-05T11:16:59.640Z",
|
||||
"sub": "auth0|58454..."
|
||||
}`
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
fmt.Sprintf("https://%s/userinfo", pkceDomain),
|
||||
httpmock.NewStringResponder(200, sampleResp),
|
||||
)
|
||||
|
||||
p := provider()
|
||||
|
||||
session, _ := p.BeginAuth("test_state")
|
||||
s := session.(*pkce.Session)
|
||||
s.AccessToken = "token"
|
||||
|
||||
u, err := p.FetchUser(s)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test.account@userinfo.com", u.Email)
|
||||
assert.Equal(t, "auth0|58454...", u.UserID)
|
||||
assert.Equal(t, "test.account", u.NickName)
|
||||
assert.Equal(t, "test.account@userinfo.com", u.Name)
|
||||
assert.Equal(t, "token", u.AccessToken)
|
||||
}
|
||||
|
||||
func provider() *pkce.Provider {
|
||||
return pkce.New(
|
||||
pkceClientID,
|
||||
pkceRedirectURI,
|
||||
pkceDomain,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package pkce
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/markbates/goth"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
AuthURL string
|
||||
AccessToken string
|
||||
RefreshToken string
|
||||
ExpiresAt time.Time
|
||||
CodeVerifier string
|
||||
}
|
||||
|
||||
// GetAuthURL returns the URL for the authentication end-point for the provider.
|
||||
func (s *Session) GetAuthURL() (string, error) {
|
||||
if s.AuthURL == "" {
|
||||
return "", errors.New(goth.NoAuthUrlErrorMessage)
|
||||
}
|
||||
return s.AuthURL, nil
|
||||
}
|
||||
|
||||
// Marshal generates a string representation of the Session for storing between requests.
|
||||
func (s *Session) Marshal() string {
|
||||
buf, _ := json.Marshal(s)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// Authorize should validate the data from the provider and return an access token
|
||||
// that can be stored for later access to the provider.
|
||||
func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) {
|
||||
p := provider.(*Provider)
|
||||
|
||||
token, err := p.Config.Exchange(
|
||||
context.Background(),
|
||||
params.Get("code"),
|
||||
oauth2.SetAuthURLParam("code_verifier", s.CodeVerifier),
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !token.Valid() {
|
||||
return "", errors.New("invalid token received from provider")
|
||||
}
|
||||
|
||||
s.AccessToken = token.AccessToken
|
||||
s.RefreshToken = token.RefreshToken
|
||||
s.ExpiresAt = token.Expiry
|
||||
|
||||
return token.AccessToken, nil
|
||||
}
|
||||
|
||||
func (p *Provider) UnmarshalSession(data string) (goth.Session, error) {
|
||||
s := &Session{}
|
||||
if err := json.Unmarshal([]byte(data), s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package pkce_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/markbates/goth"
|
||||
"github.com/mozilla/protodash/pkce"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestImplementsSession(t *testing.T) {
|
||||
s := &pkce.Session{}
|
||||
assert.Implements(t, (*goth.Session)(nil), s)
|
||||
}
|
||||
|
||||
func TestGetAuthURL(t *testing.T) {
|
||||
s := &pkce.Session{}
|
||||
|
||||
_, err := s.GetAuthURL()
|
||||
assert.Error(t, err)
|
||||
|
||||
s.AuthURL = "/foo"
|
||||
url, _ := s.GetAuthURL()
|
||||
assert.Equal(t, "/foo", url)
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
s := &pkce.Session{}
|
||||
data := s.Marshal()
|
||||
assert.Equal(t, `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresAt":"0001-01-01T00:00:00Z","CodeVerifier":""}`, data)
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package pkce
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
func codeVerifier() (string, error) {
|
||||
bs := make([]byte, 32)
|
||||
_, err := rand.Read(bs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str := base64.RawURLEncoding.EncodeToString(bs)
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func codeChallenge(verifier string) string {
|
||||
bs := sha256.Sum256([]byte(verifier))
|
||||
str := base64.RawURLEncoding.EncodeToString(bs[:])
|
||||
return str
|
||||
}
|
Загрузка…
Ссылка в новой задаче