From 33958f938e53192c42e2f03b8dcef5cc56ba91c9 Mon Sep 17 00:00:00 2001 From: lucagez Date: Mon, 17 Apr 2023 17:39:09 +0200 Subject: [PATCH 1/8] implementing password grant flow --- README.md | 1 + examples/main.go | 68 +++++++++++++++++++-- gothic/gothic.go | 10 +++ providers/direct/direct.go | 101 +++++++++++++++++++++++++++++++ providers/direct/direct_test.go | 92 ++++++++++++++++++++++++++++ providers/direct/session.go | 47 ++++++++++++++ providers/direct/session_test.go | 53 ++++++++++++++++ 7 files changed, 368 insertions(+), 4 deletions(-) create mode 100644 providers/direct/direct.go create mode 100644 providers/direct/direct_test.go create mode 100644 providers/direct/session.go create mode 100644 providers/direct/session_test.go diff --git a/README.md b/README.md index b48a3469e..08ac96247 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ $ go get github.com/markbates/goth * OpenID Connect (auto discovery) * Oura * Patreon +* Password Grant * Paypal * SalesForce * Shopify diff --git a/examples/main.go b/examples/main.go index 6f421d81a..9504b9972 100644 --- a/examples/main.go +++ b/examples/main.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "html/template" "log" @@ -21,6 +22,7 @@ import ( "github.com/markbates/goth/providers/dailymotion" "github.com/markbates/goth/providers/deezer" "github.com/markbates/goth/providers/digitalocean" + "github.com/markbates/goth/providers/direct" "github.com/markbates/goth/providers/discord" "github.com/markbates/goth/providers/dropbox" "github.com/markbates/goth/providers/eveonline" @@ -90,7 +92,7 @@ func main() { fitbit.New(os.Getenv("FITBIT_KEY"), os.Getenv("FITBIT_SECRET"), "http://localhost:3000/auth/fitbit/callback"), google.New(os.Getenv("GOOGLE_KEY"), os.Getenv("GOOGLE_SECRET"), "http://localhost:3000/auth/google/callback"), gplus.New(os.Getenv("GPLUS_KEY"), os.Getenv("GPLUS_SECRET"), "http://localhost:3000/auth/gplus/callback"), - github.New(os.Getenv("GITHUB_KEY"), os.Getenv("GITHUB_SECRET"), "http://localhost:3000/auth/github/callback"), + github.New("226a00177637d271c02b", "47abea8888fd34cc86fe2269341ebe44410ef700", "http://localhost:3000/auth/github/callback"), spotify.New(os.Getenv("SPOTIFY_KEY"), os.Getenv("SPOTIFY_SECRET"), "http://localhost:3000/auth/spotify/callback"), linkedin.New(os.Getenv("LINKEDIN_KEY"), os.Getenv("LINKEDIN_SECRET"), "http://localhost:3000/auth/linkedin/callback"), line.New(os.Getenv("LINE_KEY"), os.Getenv("LINE_SECRET"), "http://localhost:3000/auth/line/callback", "profile", "openid", "email"), @@ -159,6 +161,28 @@ func main() { goth.UseProviders(openidConnect) } + directProvider := direct.New("/login") + directProvider.FetchUserByToken = func(token string) (goth.User, error) { + log.Println("fetching user by token:", token) + return goth.User{ + Email: "john@doe.com", + FirstName: "John", + LastName: "Doe", + NickName: "JD", + UserID: "123456789", + Provider: "direct", + AccessToken: "123456789", + }, nil + } + directProvider.CredChecker = func(email, password string) error { + log.Println("checking credentials:", email, password) + if email == "john@doe.com" && password == "password" { + return nil + } + return errors.New("invalid username or password") + } + goth.UseProviders(directProvider) + m := map[string]string{ "amazon": "Amazon", "apple": "Apple", @@ -170,6 +194,7 @@ func main() { "dailymotion": "Dailymotion", "deezer": "Deezer", "digitalocean": "Digital Ocean", + "direct": "Password Grant Flow", "discord": "Discord", "dropbox": "Dropbox", "eveonline": "Eve Online", @@ -231,12 +256,14 @@ func main() { p := pat.New() p.Get("/auth/{provider}/callback", func(res http.ResponseWriter, req *http.Request) { - user, err := gothic.CompleteUserAuth(res, req) if err != nil { fmt.Fprintln(res, err) return } + + // Here you can persist the user in your session store of choice + t, _ := template.New("foo").Parse(userTemplate) t.Execute(res, user) }) @@ -250,6 +277,9 @@ func main() { p.Get("/auth/{provider}", func(res http.ResponseWriter, req *http.Request) { // try to get the user without re-authenticating if gothUser, err := gothic.CompleteUserAuth(res, req); err == nil { + + // Here you can persist the user in your session store of choice + t, _ := template.New("foo").Parse(userTemplate) t.Execute(res, gothUser) } else { @@ -257,6 +287,22 @@ func main() { } }) + p.Post("/auth/direct", func(res http.ResponseWriter, req *http.Request) { + if gothUser, err := gothic.PasswordGrantAuth(res, req); err == nil { + t, _ := template.New("foo").Parse(userTemplate) + t.Execute(res, gothUser) + } else { + log.Println("error:", err) + res.Header().Set("Location", "/") + res.WriteHeader(http.StatusFound) + } + }) + + p.Get("/login", func(res http.ResponseWriter, req *http.Request) { + t, _ := template.New("foo").Parse(loginTemplate) + t.Execute(res, providerIndex) + }) + p.Get("/", func(res http.ResponseWriter, req *http.Request) { t, _ := template.New("foo").Parse(indexTemplate) t.Execute(res, providerIndex) @@ -271,9 +317,23 @@ type ProviderIndex struct { ProvidersMap map[string]string } -var indexTemplate = `{{range $key,$value:=.Providers}} +var loginTemplate = ` + + +
+ + + +
+ + +` + +var indexTemplate = ` +{{range $key,$value:=.Providers}}

Log in with {{index $.ProvidersMap $value}}

-{{end}}` +{{end}} +` var userTemplate = `

logout

diff --git a/gothic/gothic.go b/gothic/gothic.go index 3a814d9a6..7d478eeee 100644 --- a/gothic/gothic.go +++ b/gothic/gothic.go @@ -216,6 +216,16 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us return gu, err } +// PasswordGrantAuth is a helper function that make sure +// authetication is done with the "direct" provider +func PasswordGrantAuth(res http.ResponseWriter, req *http.Request) (goth.User, error) { + ctx := req.Context() + ctx = context.WithValue(ctx, ProviderParamKey, "direct") + req = req.WithContext(ctx) + + return CompleteUserAuth(res, req) +} + // validateState ensures that the state token param from the original // AuthURL matches the one included in the current (callback) request. func validateState(req *http.Request, sess goth.Session) error { diff --git a/providers/direct/direct.go b/providers/direct/direct.go new file mode 100644 index 000000000..be0f70fc8 --- /dev/null +++ b/providers/direct/direct.go @@ -0,0 +1,101 @@ +package direct + +import ( + "crypto/rand" + "encoding/json" + "errors" + "fmt" + "strings" + + "github.com/markbates/goth" + "golang.org/x/oauth2" +) + +type AccessTokenGenerator func() string + +type FetchUserByToken func(token string) (goth.User, error) + +type CredChecker func(email, password string) error + +type DirectProvider struct { + name string + debug bool + AuthURL string + FetchUserByToken + CredChecker + AccessTokenGenerator +} + +func DefaultTokenGenerator() string { + b := make([]byte, 32) + _, _ = rand.Read(b) + return fmt.Sprintf("%x", b) +} + +func New(authUrl string) *DirectProvider { + return &DirectProvider{ + name: "direct", + AccessTokenGenerator: DefaultTokenGenerator, + AuthURL: authUrl, + } +} + +func (p *DirectProvider) Name() string { + return p.name +} + +func (p *DirectProvider) SetName(name string) { + p.name = name +} + +func (p *DirectProvider) BeginAuth(state string) (goth.Session, error) { + return &DirectSession{ + AuthURL: p.AuthURL, + }, nil +} + +func (p *DirectProvider) UnmarshalSession(data string) (goth.Session, error) { + sess := &DirectSession{} + err := json.NewDecoder(strings.NewReader(data)).Decode(sess) + return sess, err +} + +func (p *DirectProvider) FetchUser(session goth.Session) (goth.User, error) { + directSession := session.(*DirectSession) + + if directSession.AccessToken == "" { + // data is not yet retrieved since accessToken is still empty + return goth.User{}, fmt.Errorf("%s cannot get user information without accessToken", p.name) + } + + user, err := p.FetchUserByToken(directSession.AccessToken) + if err != nil { + return goth.User{}, err + } + + return user, nil +} + +func (p *DirectProvider) Debug(debug bool) { + p.debug = debug +} + +func (p *DirectProvider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, errors.New("refreshToken not supported for the password grant") +} + +func (p *DirectProvider) RefreshTokenAvailable() bool { + return false +} + +func (p *DirectProvider) IssueSession(email, password string) (goth.Session, error) { + if p.CredChecker(email, password) != nil { + return nil, errors.New("invalid username or password") + } + + accessToken := p.AccessTokenGenerator() + return &DirectSession{ + AccessToken: accessToken, + Email: email, + }, nil +} diff --git a/providers/direct/direct_test.go b/providers/direct/direct_test.go new file mode 100644 index 000000000..4e73bdb95 --- /dev/null +++ b/providers/direct/direct_test.go @@ -0,0 +1,92 @@ +package direct_test + +import ( + "errors" + "testing" + + "github.com/markbates/goth" + "github.com/markbates/goth/providers/direct" +) + +func TestDirectProvider(t *testing.T) { + p := direct.New("/login") + + users := map[string]goth.User{ + "123": { + Email: "test@example.com", + }, + } + + p.AccessTokenGenerator = func() string { + return "123" + } + p.FetchUserByToken = func(token string) (goth.User, error) { + if user, ok := users[token]; ok { + return user, nil + } + return goth.User{}, errors.New("user not found") + } + p.CredChecker = func(email, password string) error { + if email == "test@example.com" && password == "password" { + return nil + } + return errors.New("invalid email or password") + } + + t.Run("Name", func(t *testing.T) { + if p.Name() != "direct" { + t.Errorf("expected provider name to be 'direct', got %s", p.Name()) + } + }) + + t.Run("SetName", func(t *testing.T) { + p.SetName("direct_custom") + if p.Name() != "direct_custom" { + t.Errorf("expected provider name to be 'direct_custom', got %s", p.Name()) + } + }) + + t.Run("IssueSession", func(t *testing.T) { + _, err := p.IssueSession("test@example.com", "password") + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + _, err = p.IssueSession("test@example.com", "wrong_password") + if err == nil { + t.Error("expected error for invalid password, got nil") + } + + _, err = p.IssueSession("nonexistent@example.com", "password") + if err == nil { + t.Error("expected error for non-existent user, got nil") + } + }) + + t.Run("FetchUser", func(t *testing.T) { + session, _ := p.IssueSession("test@example.com", "password") + + user, err := p.FetchUser(session) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + if user.Email != "test@example.com" { + t.Errorf("expected email to be 'test@example.com', got %s", user.Email) + } + }) + + t.Run("UnmarshalSession", func(t *testing.T) { + session, _ := p.IssueSession("test@example.com", "password") + data := session.Marshal() + + unmarshalledSession, err := p.UnmarshalSession(data) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + + // Check if the unmarshalled session is the same as the original session + if session.Marshal() != unmarshalledSession.Marshal() { + t.Error("unmarshalled session data does not match the original session data") + } + }) +} diff --git a/providers/direct/session.go b/providers/direct/session.go new file mode 100644 index 000000000..a3d43c311 --- /dev/null +++ b/providers/direct/session.go @@ -0,0 +1,47 @@ +package direct + +import ( + "encoding/json" + "errors" + + "github.com/markbates/goth" +) + +type DirectSession struct { + AuthURL string + AccessToken string + Email string +} + +func (s *DirectSession) GetAuthURL() (string, error) { + return s.AuthURL, nil +} + +func (s *DirectSession) Marshal() string { + b, _ := json.Marshal(s) + return string(b) +} + +func (s *DirectSession) Authorize(provider goth.Provider, params goth.Params) (string, error) { + email := params.Get("email") + password := params.Get("password") + + directProvider, ok := provider.(*DirectProvider) + if !ok { + return "", errors.New("invalid provider type") + } + + session, err := directProvider.IssueSession(email, password) + if err != nil { + return "", err + } + + sess, ok := session.(*DirectSession) + if !ok { + return "", errors.New("invalid session type") + } + + s.AccessToken = sess.AccessToken + s.Email = sess.Email + return sess.AccessToken, nil +} diff --git a/providers/direct/session_test.go b/providers/direct/session_test.go new file mode 100644 index 000000000..3b63e25b9 --- /dev/null +++ b/providers/direct/session_test.go @@ -0,0 +1,53 @@ +package direct_test + +import ( + "encoding/json" + "testing" + + "github.com/markbates/goth/providers/direct" +) + +func TestDirectSession(t *testing.T) { + t.Run("Marshal", func(t *testing.T) { + session := &direct.DirectSession{ + AccessToken: "1234567890", + Email: "test@mail.com", + AuthURL: "/login", + } + marshaled := session.Marshal() + + var unmarshaled direct.DirectSession + err := json.Unmarshal([]byte(marshaled), &unmarshaled) + + if err != nil { + t.Errorf("unexpected error when unmarshaling session data: %v", err) + } + + if unmarshaled.AccessToken != session.AccessToken { + t.Errorf("expected access token to be '%s', got '%s'", session.AccessToken, unmarshaled.AccessToken) + } + + if unmarshaled.Email != session.Email { + t.Errorf("expected email to be '%s', got '%s'", session.Email, unmarshaled.Email) + } + + if unmarshaled.AuthURL != session.AuthURL { + t.Errorf("expected auth url to be '%s', got '%s'", session.AuthURL, unmarshaled.AuthURL) + } + }) + + t.Run("GetAuthURL", func(t *testing.T) { + session := &direct.DirectSession{ + AuthURL: "/", + } + + url, err := session.GetAuthURL() + if err != nil { + t.Error("unexpected error when calling GetAuthURL") + } + + if url != "/" { + t.Errorf("expected auth url to be '/', got '%s'", url) + } + }) +} From 70f68f07af535390c6d262d03d0bb1e78de610a3 Mon Sep 17 00:00:00 2001 From: lucagez Date: Mon, 17 Apr 2023 17:44:38 +0200 Subject: [PATCH 2/8] rename session and provider --- examples/main.go | 2 -- providers/direct/direct.go | 33 ++++++++++++++++---------------- providers/direct/session.go | 12 ++++++------ providers/direct/session_test.go | 6 +++--- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/examples/main.go b/examples/main.go index 9504b9972..a4c845aa0 100644 --- a/examples/main.go +++ b/examples/main.go @@ -163,7 +163,6 @@ func main() { directProvider := direct.New("/login") directProvider.FetchUserByToken = func(token string) (goth.User, error) { - log.Println("fetching user by token:", token) return goth.User{ Email: "john@doe.com", FirstName: "John", @@ -175,7 +174,6 @@ func main() { }, nil } directProvider.CredChecker = func(email, password string) error { - log.Println("checking credentials:", email, password) if email == "john@doe.com" && password == "password" { return nil } diff --git a/providers/direct/direct.go b/providers/direct/direct.go index be0f70fc8..0b5165880 100644 --- a/providers/direct/direct.go +++ b/providers/direct/direct.go @@ -13,11 +13,12 @@ import ( type AccessTokenGenerator func() string +// Non puo funzionare con token type FetchUserByToken func(token string) (goth.User, error) type CredChecker func(email, password string) error -type DirectProvider struct { +type Provider struct { name string debug bool AuthURL string @@ -32,36 +33,36 @@ func DefaultTokenGenerator() string { return fmt.Sprintf("%x", b) } -func New(authUrl string) *DirectProvider { - return &DirectProvider{ +func New(authUrl string) *Provider { + return &Provider{ name: "direct", AccessTokenGenerator: DefaultTokenGenerator, AuthURL: authUrl, } } -func (p *DirectProvider) Name() string { +func (p *Provider) Name() string { return p.name } -func (p *DirectProvider) SetName(name string) { +func (p *Provider) SetName(name string) { p.name = name } -func (p *DirectProvider) BeginAuth(state string) (goth.Session, error) { - return &DirectSession{ +func (p *Provider) BeginAuth(state string) (goth.Session, error) { + return &Session{ AuthURL: p.AuthURL, }, nil } -func (p *DirectProvider) UnmarshalSession(data string) (goth.Session, error) { - sess := &DirectSession{} +func (p *Provider) UnmarshalSession(data string) (goth.Session, error) { + sess := &Session{} err := json.NewDecoder(strings.NewReader(data)).Decode(sess) return sess, err } -func (p *DirectProvider) FetchUser(session goth.Session) (goth.User, error) { - directSession := session.(*DirectSession) +func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { + directSession := session.(*Session) if directSession.AccessToken == "" { // data is not yet retrieved since accessToken is still empty @@ -76,25 +77,25 @@ func (p *DirectProvider) FetchUser(session goth.Session) (goth.User, error) { return user, nil } -func (p *DirectProvider) Debug(debug bool) { +func (p *Provider) Debug(debug bool) { p.debug = debug } -func (p *DirectProvider) RefreshToken(refreshToken string) (*oauth2.Token, error) { +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { return nil, errors.New("refreshToken not supported for the password grant") } -func (p *DirectProvider) RefreshTokenAvailable() bool { +func (p *Provider) RefreshTokenAvailable() bool { return false } -func (p *DirectProvider) IssueSession(email, password string) (goth.Session, error) { +func (p *Provider) IssueSession(email, password string) (goth.Session, error) { if p.CredChecker(email, password) != nil { return nil, errors.New("invalid username or password") } accessToken := p.AccessTokenGenerator() - return &DirectSession{ + return &Session{ AccessToken: accessToken, Email: email, }, nil diff --git a/providers/direct/session.go b/providers/direct/session.go index a3d43c311..c12eaadf7 100644 --- a/providers/direct/session.go +++ b/providers/direct/session.go @@ -7,26 +7,26 @@ import ( "github.com/markbates/goth" ) -type DirectSession struct { +type Session struct { AuthURL string AccessToken string Email string } -func (s *DirectSession) GetAuthURL() (string, error) { +func (s *Session) GetAuthURL() (string, error) { return s.AuthURL, nil } -func (s *DirectSession) Marshal() string { +func (s *Session) Marshal() string { b, _ := json.Marshal(s) return string(b) } -func (s *DirectSession) Authorize(provider goth.Provider, params goth.Params) (string, error) { +func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) { email := params.Get("email") password := params.Get("password") - directProvider, ok := provider.(*DirectProvider) + directProvider, ok := provider.(*Provider) if !ok { return "", errors.New("invalid provider type") } @@ -36,7 +36,7 @@ func (s *DirectSession) Authorize(provider goth.Provider, params goth.Params) (s return "", err } - sess, ok := session.(*DirectSession) + sess, ok := session.(*Session) if !ok { return "", errors.New("invalid session type") } diff --git a/providers/direct/session_test.go b/providers/direct/session_test.go index 3b63e25b9..b85e2b0f7 100644 --- a/providers/direct/session_test.go +++ b/providers/direct/session_test.go @@ -9,14 +9,14 @@ import ( func TestDirectSession(t *testing.T) { t.Run("Marshal", func(t *testing.T) { - session := &direct.DirectSession{ + session := &direct.Session{ AccessToken: "1234567890", Email: "test@mail.com", AuthURL: "/login", } marshaled := session.Marshal() - var unmarshaled direct.DirectSession + var unmarshaled direct.Session err := json.Unmarshal([]byte(marshaled), &unmarshaled) if err != nil { @@ -37,7 +37,7 @@ func TestDirectSession(t *testing.T) { }) t.Run("GetAuthURL", func(t *testing.T) { - session := &direct.DirectSession{ + session := &direct.Session{ AuthURL: "/", } From ed75c4ebf0fa8e3eb9c8f734d3c62140fecdcdf8 Mon Sep 17 00:00:00 2001 From: lucagez Date: Mon, 17 Apr 2023 17:59:44 +0200 Subject: [PATCH 3/8] update user fetcher --- examples/main.go | 11 ++++++++--- providers/direct/direct.go | 6 +++--- providers/direct/direct_test.go | 10 +++------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/examples/main.go b/examples/main.go index a4c845aa0..7368be23b 100644 --- a/examples/main.go +++ b/examples/main.go @@ -92,7 +92,7 @@ func main() { fitbit.New(os.Getenv("FITBIT_KEY"), os.Getenv("FITBIT_SECRET"), "http://localhost:3000/auth/fitbit/callback"), google.New(os.Getenv("GOOGLE_KEY"), os.Getenv("GOOGLE_SECRET"), "http://localhost:3000/auth/google/callback"), gplus.New(os.Getenv("GPLUS_KEY"), os.Getenv("GPLUS_SECRET"), "http://localhost:3000/auth/gplus/callback"), - github.New("226a00177637d271c02b", "47abea8888fd34cc86fe2269341ebe44410ef700", "http://localhost:3000/auth/github/callback"), + github.New(os.Getenv("GITHUD_ID"), os.Getenv("GITHUB_SECRET"), "http://localhost:3000/auth/github/callback"), spotify.New(os.Getenv("SPOTIFY_KEY"), os.Getenv("SPOTIFY_SECRET"), "http://localhost:3000/auth/spotify/callback"), linkedin.New(os.Getenv("LINKEDIN_KEY"), os.Getenv("LINKEDIN_SECRET"), "http://localhost:3000/auth/linkedin/callback"), line.New(os.Getenv("LINE_KEY"), os.Getenv("LINE_SECRET"), "http://localhost:3000/auth/line/callback", "profile", "openid", "email"), @@ -162,7 +162,12 @@ func main() { } directProvider := direct.New("/login") - directProvider.FetchUserByToken = func(token string) (goth.User, error) { + directProvider.UserFetcher = func(email, token string) (goth.User, error) { + if email != "john@doe.com" { + return goth.User{}, errors.New("user not found") + } + + // Possible to associate the token with the user return goth.User{ Email: "john@doe.com", FirstName: "John", @@ -170,7 +175,7 @@ func main() { NickName: "JD", UserID: "123456789", Provider: "direct", - AccessToken: "123456789", + AccessToken: token, }, nil } directProvider.CredChecker = func(email, password string) error { diff --git a/providers/direct/direct.go b/providers/direct/direct.go index 0b5165880..5833a7e5d 100644 --- a/providers/direct/direct.go +++ b/providers/direct/direct.go @@ -14,7 +14,7 @@ import ( type AccessTokenGenerator func() string // Non puo funzionare con token -type FetchUserByToken func(token string) (goth.User, error) +type UserFetcher func(email, token string) (goth.User, error) type CredChecker func(email, password string) error @@ -22,7 +22,7 @@ type Provider struct { name string debug bool AuthURL string - FetchUserByToken + UserFetcher CredChecker AccessTokenGenerator } @@ -69,7 +69,7 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { return goth.User{}, fmt.Errorf("%s cannot get user information without accessToken", p.name) } - user, err := p.FetchUserByToken(directSession.AccessToken) + user, err := p.UserFetcher(directSession.Email, directSession.AccessToken) if err != nil { return goth.User{}, err } diff --git a/providers/direct/direct_test.go b/providers/direct/direct_test.go index 4e73bdb95..9bbb8a526 100644 --- a/providers/direct/direct_test.go +++ b/providers/direct/direct_test.go @@ -12,16 +12,12 @@ func TestDirectProvider(t *testing.T) { p := direct.New("/login") users := map[string]goth.User{ - "123": { + "test@example.com": { Email: "test@example.com", }, } - - p.AccessTokenGenerator = func() string { - return "123" - } - p.FetchUserByToken = func(token string) (goth.User, error) { - if user, ok := users[token]; ok { + p.UserFetcher = func(email, token string) (goth.User, error) { + if user, ok := users[email]; ok { return user, nil } return goth.User{}, errors.New("user not found") From b4b03184496e266fa18eafb19d06551913d61d25 Mon Sep 17 00:00:00 2001 From: lucagez Date: Mon, 17 Apr 2023 18:11:31 +0200 Subject: [PATCH 4/8] remove access token which is not used by gothic package --- examples/main.go | 17 ++++++++--------- providers/direct/direct.go | 10 ++++------ providers/direct/direct_test.go | 2 +- providers/direct/session.go | 9 ++++----- providers/direct/session_test.go | 9 ++------- 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/examples/main.go b/examples/main.go index 7368be23b..cf23d87f0 100644 --- a/examples/main.go +++ b/examples/main.go @@ -92,7 +92,7 @@ func main() { fitbit.New(os.Getenv("FITBIT_KEY"), os.Getenv("FITBIT_SECRET"), "http://localhost:3000/auth/fitbit/callback"), google.New(os.Getenv("GOOGLE_KEY"), os.Getenv("GOOGLE_SECRET"), "http://localhost:3000/auth/google/callback"), gplus.New(os.Getenv("GPLUS_KEY"), os.Getenv("GPLUS_SECRET"), "http://localhost:3000/auth/gplus/callback"), - github.New(os.Getenv("GITHUD_ID"), os.Getenv("GITHUB_SECRET"), "http://localhost:3000/auth/github/callback"), + github.New(os.Getenv("GITHUB_ID"), os.Getenv("GITHUB_SECRET"), "http://localhost:3000/auth/github/callback"), spotify.New(os.Getenv("SPOTIFY_KEY"), os.Getenv("SPOTIFY_SECRET"), "http://localhost:3000/auth/spotify/callback"), linkedin.New(os.Getenv("LINKEDIN_KEY"), os.Getenv("LINKEDIN_SECRET"), "http://localhost:3000/auth/linkedin/callback"), line.New(os.Getenv("LINE_KEY"), os.Getenv("LINE_SECRET"), "http://localhost:3000/auth/line/callback", "profile", "openid", "email"), @@ -162,20 +162,19 @@ func main() { } directProvider := direct.New("/login") - directProvider.UserFetcher = func(email, token string) (goth.User, error) { + directProvider.UserFetcher = func(email string) (goth.User, error) { if email != "john@doe.com" { return goth.User{}, errors.New("user not found") } // Possible to associate the token with the user return goth.User{ - Email: "john@doe.com", - FirstName: "John", - LastName: "Doe", - NickName: "JD", - UserID: "123456789", - Provider: "direct", - AccessToken: token, + Email: "john@doe.com", + FirstName: "John", + LastName: "Doe", + NickName: "JD", + UserID: "123456789", + Provider: "direct", }, nil } directProvider.CredChecker = func(email, password string) error { diff --git a/providers/direct/direct.go b/providers/direct/direct.go index 5833a7e5d..862649260 100644 --- a/providers/direct/direct.go +++ b/providers/direct/direct.go @@ -14,7 +14,7 @@ import ( type AccessTokenGenerator func() string // Non puo funzionare con token -type UserFetcher func(email, token string) (goth.User, error) +type UserFetcher func(email string) (goth.User, error) type CredChecker func(email, password string) error @@ -64,12 +64,12 @@ func (p *Provider) UnmarshalSession(data string) (goth.Session, error) { func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { directSession := session.(*Session) - if directSession.AccessToken == "" { + if directSession.Email == "" { // data is not yet retrieved since accessToken is still empty return goth.User{}, fmt.Errorf("%s cannot get user information without accessToken", p.name) } - user, err := p.UserFetcher(directSession.Email, directSession.AccessToken) + user, err := p.UserFetcher(directSession.Email) if err != nil { return goth.User{}, err } @@ -94,9 +94,7 @@ func (p *Provider) IssueSession(email, password string) (goth.Session, error) { return nil, errors.New("invalid username or password") } - accessToken := p.AccessTokenGenerator() return &Session{ - AccessToken: accessToken, - Email: email, + Email: email, }, nil } diff --git a/providers/direct/direct_test.go b/providers/direct/direct_test.go index 9bbb8a526..f301c8415 100644 --- a/providers/direct/direct_test.go +++ b/providers/direct/direct_test.go @@ -16,7 +16,7 @@ func TestDirectProvider(t *testing.T) { Email: "test@example.com", }, } - p.UserFetcher = func(email, token string) (goth.User, error) { + p.UserFetcher = func(email string) (goth.User, error) { if user, ok := users[email]; ok { return user, nil } diff --git a/providers/direct/session.go b/providers/direct/session.go index c12eaadf7..c1a421e79 100644 --- a/providers/direct/session.go +++ b/providers/direct/session.go @@ -8,9 +8,8 @@ import ( ) type Session struct { - AuthURL string - AccessToken string - Email string + AuthURL string + Email string } func (s *Session) GetAuthURL() (string, error) { @@ -41,7 +40,7 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, return "", errors.New("invalid session type") } - s.AccessToken = sess.AccessToken s.Email = sess.Email - return sess.AccessToken, nil + // Result of Authorize is not used by gothic + return "", nil } diff --git a/providers/direct/session_test.go b/providers/direct/session_test.go index b85e2b0f7..905684e0f 100644 --- a/providers/direct/session_test.go +++ b/providers/direct/session_test.go @@ -10,9 +10,8 @@ import ( func TestDirectSession(t *testing.T) { t.Run("Marshal", func(t *testing.T) { session := &direct.Session{ - AccessToken: "1234567890", - Email: "test@mail.com", - AuthURL: "/login", + Email: "test@mail.com", + AuthURL: "/login", } marshaled := session.Marshal() @@ -23,10 +22,6 @@ func TestDirectSession(t *testing.T) { t.Errorf("unexpected error when unmarshaling session data: %v", err) } - if unmarshaled.AccessToken != session.AccessToken { - t.Errorf("expected access token to be '%s', got '%s'", session.AccessToken, unmarshaled.AccessToken) - } - if unmarshaled.Email != session.Email { t.Errorf("expected email to be '%s', got '%s'", session.Email, unmarshaled.Email) } From 85ccb47b4698229a310dae1455b52fc9ceac9153 Mon Sep 17 00:00:00 2001 From: lucagez Date: Mon, 17 Apr 2023 18:23:27 +0200 Subject: [PATCH 5/8] upgrade api to prevent accidental nil pointer exceptions --- examples/main.go | 6 +++--- providers/direct/direct.go | 5 +++-- providers/direct/direct_test.go | 8 ++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/main.go b/examples/main.go index cf23d87f0..c0cd7288d 100644 --- a/examples/main.go +++ b/examples/main.go @@ -161,8 +161,7 @@ func main() { goth.UseProviders(openidConnect) } - directProvider := direct.New("/login") - directProvider.UserFetcher = func(email string) (goth.User, error) { + var userFetcher = func(email string) (goth.User, error) { if email != "john@doe.com" { return goth.User{}, errors.New("user not found") } @@ -177,12 +176,13 @@ func main() { Provider: "direct", }, nil } - directProvider.CredChecker = func(email, password string) error { + var credChecker = func(email, password string) error { if email == "john@doe.com" && password == "password" { return nil } return errors.New("invalid username or password") } + directProvider := direct.New("/login", userFetcher, credChecker) goth.UseProviders(directProvider) m := map[string]string{ diff --git a/providers/direct/direct.go b/providers/direct/direct.go index 862649260..0df5fb1da 100644 --- a/providers/direct/direct.go +++ b/providers/direct/direct.go @@ -13,7 +13,6 @@ import ( type AccessTokenGenerator func() string -// Non puo funzionare con token type UserFetcher func(email string) (goth.User, error) type CredChecker func(email, password string) error @@ -33,11 +32,13 @@ func DefaultTokenGenerator() string { return fmt.Sprintf("%x", b) } -func New(authUrl string) *Provider { +func New(authUrl string, userFetcher UserFetcher, credChecker CredChecker) *Provider { return &Provider{ name: "direct", AccessTokenGenerator: DefaultTokenGenerator, AuthURL: authUrl, + UserFetcher: userFetcher, + CredChecker: credChecker, } } diff --git a/providers/direct/direct_test.go b/providers/direct/direct_test.go index f301c8415..5778020f2 100644 --- a/providers/direct/direct_test.go +++ b/providers/direct/direct_test.go @@ -9,25 +9,25 @@ import ( ) func TestDirectProvider(t *testing.T) { - p := direct.New("/login") - users := map[string]goth.User{ "test@example.com": { Email: "test@example.com", }, } - p.UserFetcher = func(email string) (goth.User, error) { + + var userFetcher = func(email string) (goth.User, error) { if user, ok := users[email]; ok { return user, nil } return goth.User{}, errors.New("user not found") } - p.CredChecker = func(email, password string) error { + var credChecker = func(email, password string) error { if email == "test@example.com" && password == "password" { return nil } return errors.New("invalid email or password") } + p := direct.New("/login", userFetcher, credChecker) t.Run("Name", func(t *testing.T) { if p.Name() != "direct" { From c8e0b4cecc57e28d908a429c9f5a6948fe0c9ee9 Mon Sep 17 00:00:00 2001 From: lucagez Date: Mon, 17 Apr 2023 18:24:32 +0200 Subject: [PATCH 6/8] remove wrong modification --- examples/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/main.go b/examples/main.go index c0cd7288d..df8b42a66 100644 --- a/examples/main.go +++ b/examples/main.go @@ -92,7 +92,7 @@ func main() { fitbit.New(os.Getenv("FITBIT_KEY"), os.Getenv("FITBIT_SECRET"), "http://localhost:3000/auth/fitbit/callback"), google.New(os.Getenv("GOOGLE_KEY"), os.Getenv("GOOGLE_SECRET"), "http://localhost:3000/auth/google/callback"), gplus.New(os.Getenv("GPLUS_KEY"), os.Getenv("GPLUS_SECRET"), "http://localhost:3000/auth/gplus/callback"), - github.New(os.Getenv("GITHUB_ID"), os.Getenv("GITHUB_SECRET"), "http://localhost:3000/auth/github/callback"), + github.New(os.Getenv("GITHUB_KEY"), os.Getenv("GITHUB_SECRET"), "http://localhost:3000/auth/github/callback"), spotify.New(os.Getenv("SPOTIFY_KEY"), os.Getenv("SPOTIFY_SECRET"), "http://localhost:3000/auth/spotify/callback"), linkedin.New(os.Getenv("LINKEDIN_KEY"), os.Getenv("LINKEDIN_SECRET"), "http://localhost:3000/auth/linkedin/callback"), line.New(os.Getenv("LINE_KEY"), os.Getenv("LINE_SECRET"), "http://localhost:3000/auth/line/callback", "profile", "openid", "email"), From b8eda40b155ab57812cba62b41a07ab2c2464e6d Mon Sep 17 00:00:00 2001 From: lucagez Date: Mon, 17 Apr 2023 18:28:46 +0200 Subject: [PATCH 7/8] polish --- examples/main.go | 4 ++++ gothic/gothic.go | 2 +- providers/direct/direct.go | 18 +++++------------- providers/direct/direct_test.go | 1 - providers/direct/session.go | 2 +- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/examples/main.go b/examples/main.go index df8b42a66..966bb4f5c 100644 --- a/examples/main.go +++ b/examples/main.go @@ -332,9 +332,13 @@ var loginTemplate = ` ` var indexTemplate = ` + + {{range $key,$value:=.Providers}}

Log in with {{index $.ProvidersMap $value}}

{{end}} + + ` var userTemplate = ` diff --git a/gothic/gothic.go b/gothic/gothic.go index 7d478eeee..8143f83b3 100644 --- a/gothic/gothic.go +++ b/gothic/gothic.go @@ -217,7 +217,7 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us } // PasswordGrantAuth is a helper function that make sure -// authetication is done with the "direct" provider +// authentication happens via the "direct" provider func PasswordGrantAuth(res http.ResponseWriter, req *http.Request) (goth.User, error) { ctx := req.Context() ctx = context.WithValue(ctx, ProviderParamKey, "direct") diff --git a/providers/direct/direct.go b/providers/direct/direct.go index 0df5fb1da..dffb1edf8 100644 --- a/providers/direct/direct.go +++ b/providers/direct/direct.go @@ -1,7 +1,6 @@ package direct import ( - "crypto/rand" "encoding/json" "errors" "fmt" @@ -26,19 +25,12 @@ type Provider struct { AccessTokenGenerator } -func DefaultTokenGenerator() string { - b := make([]byte, 32) - _, _ = rand.Read(b) - return fmt.Sprintf("%x", b) -} - func New(authUrl string, userFetcher UserFetcher, credChecker CredChecker) *Provider { return &Provider{ - name: "direct", - AccessTokenGenerator: DefaultTokenGenerator, - AuthURL: authUrl, - UserFetcher: userFetcher, - CredChecker: credChecker, + name: "direct", + AuthURL: authUrl, + UserFetcher: userFetcher, + CredChecker: credChecker, } } @@ -66,7 +58,7 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { directSession := session.(*Session) if directSession.Email == "" { - // data is not yet retrieved since accessToken is still empty + // data is not yet retrieved since email is still empty return goth.User{}, fmt.Errorf("%s cannot get user information without accessToken", p.name) } diff --git a/providers/direct/direct_test.go b/providers/direct/direct_test.go index 5778020f2..37dae8228 100644 --- a/providers/direct/direct_test.go +++ b/providers/direct/direct_test.go @@ -80,7 +80,6 @@ func TestDirectProvider(t *testing.T) { t.Errorf("expected no error, got %v", err) } - // Check if the unmarshalled session is the same as the original session if session.Marshal() != unmarshalledSession.Marshal() { t.Error("unmarshalled session data does not match the original session data") } diff --git a/providers/direct/session.go b/providers/direct/session.go index c1a421e79..3b51a4752 100644 --- a/providers/direct/session.go +++ b/providers/direct/session.go @@ -41,6 +41,6 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, } s.Email = sess.Email - // Result of Authorize is not used by gothic + // Result of Authorize is not used by gothic package return "", nil } From 1a5d1c53ebb0545a2789d722ef3dd31acef5d6d5 Mon Sep 17 00:00:00 2001 From: lucagez Date: Mon, 17 Apr 2023 23:30:41 +0200 Subject: [PATCH 8/8] remove unused token generator --- providers/direct/direct.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/providers/direct/direct.go b/providers/direct/direct.go index dffb1edf8..e97af255d 100644 --- a/providers/direct/direct.go +++ b/providers/direct/direct.go @@ -10,8 +10,6 @@ import ( "golang.org/x/oauth2" ) -type AccessTokenGenerator func() string - type UserFetcher func(email string) (goth.User, error) type CredChecker func(email, password string) error @@ -22,7 +20,6 @@ type Provider struct { AuthURL string UserFetcher CredChecker - AccessTokenGenerator } func New(authUrl string, userFetcher UserFetcher, credChecker CredChecker) *Provider {