Skip to content

Commit

Permalink
added sign in with facebook
Browse files Browse the repository at this point in the history
  • Loading branch information
prathamesh0987 committed Oct 23, 2024
1 parent 42b1c94 commit b509555
Show file tree
Hide file tree
Showing 12 changed files with 319 additions and 5 deletions.
2 changes: 2 additions & 0 deletions backend/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ third_party:
enabled: false
microsoft:
enabled: false
facebook:
enabled: false
username:
enabled: false
optional: true
Expand Down
4 changes: 4 additions & 0 deletions backend/config/config_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ func DefaultConfig() *Config {
DisplayName: "Discord",
AllowLinking: true,
},
Facebook: ThirdPartyProvider{
DisplayName: "Facebook",
AllowLinking: true,
},
},
},
Passkey: Passkey{
Expand Down
5 changes: 4 additions & 1 deletion backend/config/config_third_party.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package config
import (
"errors"
"fmt"
"strings"

"github.com/fatih/structs"
"github.com/gobwas/glob"
"github.com/invopop/jsonschema"
orderedmap "github.com/wk8/go-ordered-map/v2"
"strings"
)

type ThirdParty struct {
Expand Down Expand Up @@ -113,6 +114,8 @@ type ThirdPartyProviders struct {
LinkedIn ThirdPartyProvider `yaml:"linkedin" json:"linkedin,omitempty" koanf:"linkedin"`
// `microsoft` contains the provider configuration for Microsoft.
Microsoft ThirdPartyProvider `yaml:"microsoft" json:"microsoft,omitempty" koanf:"microsoft"`
//`facebook` contains the provider configuration for Facebook.
Facebook ThirdPartyProvider `yaml:"facebook" json:"facebook,omitempty" koanf:"facebook"`
}

func (p *ThirdPartyProviders) Validate() error {
Expand Down
6 changes: 5 additions & 1 deletion backend/flow_api/flow/shared/hook_generate_oauth_links.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package shared

import (
"fmt"
"net/url"

"github.com/labstack/echo/v4"
"github.com/teamhanko/hanko/backend/flowpilot"
"net/url"
)

type GenerateOAuthLinks struct {
Expand Down Expand Up @@ -38,6 +39,9 @@ func (h GenerateOAuthLinks) Execute(c flowpilot.HookExecutionContext) error {
if deps.Cfg.ThirdParty.Providers.Apple.Enabled {
c.AddLink(OAuthLink("apple", h.generateHref(deps.HttpContext, "apple", returnToUrl)))
}
if deps.Cfg.ThirdParty.Providers.Facebook.Enabled {
c.AddLink(OAuthLink("facebook", h.generateHref(deps.HttpContext, "facebook", returnToUrl)))
}

return nil
}
Expand Down
9 changes: 9 additions & 0 deletions backend/handler/thirdparty_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Auth() {
requestedRedirectTo: "https://app.test.example",
expectedBaseURL: thirdparty.MicrosoftOAuthAuthEndpoint,
},
{
name: "successful redirect to facebook",
referer: "https://login.test.example",
enabledProviders: []string{"facebook"},
allowedRedirectURLs: []string{"https://*.test.example"},
requestedProvider: "facebook",
requestedRedirectTo: "https://app.test.example",
expectedBaseURL: thirdparty.FacebookOauthAuthEndpoint,
},
{
name: "error redirect on missing provider",
referer: "https://login.test.example",
Expand Down
58 changes: 55 additions & 3 deletions backend/handler/thirdparty_callback_error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package handler

import (
"fmt"
"github.com/h2non/gock"
"github.com/teamhanko/hanko/backend/thirdparty"
"github.com/teamhanko/hanko/backend/utils"
"net/http"
"net/http/httptest"
"testing"

"github.com/h2non/gock"
"github.com/teamhanko/hanko/backend/thirdparty"
"github.com/teamhanko/hanko/backend/utils"
)

func (s *thirdPartySuite) TestThirdPartyHandler_Callback_Error_LinkingNotAllowedForProvider() {
Expand Down Expand Up @@ -438,3 +439,54 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_Error_MicrosoftUnverifi
s.Len(logs, 1)
}
}

func (s *thirdPartySuite) TestThirdPartyHandler_Callback_Error_FacebookUnverifiedEmail() {
if testing.Short() {
s.T().Skip("skipping test in short mode.")
}

err := s.LoadFixtures("../test/fixtures/thirdparty")
s.NoError(err)

gock.New(thirdparty.FacebookOauthTokenEndpoint).
Post("/").
Reply(200).
JSON(map[string]string{"access_token": "fakeAccessToken"})

gock.New(thirdparty.FacebookUserInfoEndpoint).
Get("/me").
Reply(200).
JSON(map[string]interface{}{
"id": "facebook_abcde",
"email": "[email protected]",
"verified": false,
})

cfg := s.setUpConfig([]string{"facebook"}, []string{"https://example.com"})
cfg.Email.RequireVerification = true

state, err := thirdparty.GenerateState(cfg, "facebook", "https://example.com")
s.NoError(err)

req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/thirdparty/callback?code=abcde&state=%s", state), nil)
req.AddCookie(&http.Cookie{
Name: utils.HankoThirdpartyStateCookie,
Value: string(state),
})

c, rec := s.setUpContext(req)
handler := s.setUpHandler(cfg)

if s.NoError(handler.Callback(c)) {
s.Equal(http.StatusTemporaryRedirect, rec.Code)
location, err := rec.Result().Location()
s.NoError(err)

s.Equal(thirdparty.ErrorCodeUnverifiedProviderEmail, location.Query().Get("error"))
s.Equal("third party provider email must be verified", location.Query().Get("error_description"))

logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signin_signup_failed"}, "", "", "", "")
s.NoError(lerr)
s.Len(logs, 1)
}
}
117 changes: 117 additions & 0 deletions backend/handler/thirdparty_callback_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,123 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignIn_Microsoft() {
}
}

func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignUp_Facebook() {
defer gock.Off()
if testing.Short() {
s.T().Skip("skipping test in short mode.")
}

gock.New(thirdparty.FacebookOauthTokenEndpoint).
Post("/").
Reply(200).
JSON(map[string]string{"access_token": "fakeAccessToken"})

gock.New(thirdparty.FacebookUserInfoEndpoint).
Get("/me").
Reply(200).
JSON(&thirdparty.FacebookUser{
ID: "facebook_abcde",
Email: "[email protected]",
})

cfg := s.setUpConfig([]string{"facebook"}, []string{"https://example.com"})

state, err := thirdparty.GenerateState(cfg, "facebook", "https://example.com")
s.NoError(err)

req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/thirdparty/callback?code=abcde&state=%s", state), nil)
req.AddCookie(&http.Cookie{
Name: utils.HankoThirdpartyStateCookie,
Value: string(state),
})

c, rec := s.setUpContext(req)
handler := s.setUpHandler(cfg)

if s.NoError(handler.Callback(c)) {
s.Equal(http.StatusTemporaryRedirect, rec.Code)

s.assertLocationHeaderHasToken(rec)
s.assertStateCookieRemoved(rec)

email, err := s.Storage.GetEmailPersister().FindByAddress("[email protected]")
s.NoError(err)
s.NotNil(email)
s.True(email.IsPrimary())

user, err := s.Storage.GetUserPersister().Get(*email.UserID)
s.NoError(err)
s.NotNil(user)

identity := email.Identities.GetIdentity("facebook", "facebook_abcde")
s.NotNil(identity)

logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signup_succeeded"}, user.ID.String(), email.Address, "", "")
s.NoError(lerr)
s.Len(logs, 1)
}
}

func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignIn_Facebook() {
defer gock.Off()
if testing.Short() {
s.T().Skip("skipping test in short mode.")
}

err := s.LoadFixtures("../test/fixtures/thirdparty")
s.NoError(err)

gock.New(thirdparty.FacebookOauthTokenEndpoint).
Post("/").
Reply(200).
JSON(map[string]string{"access_token": "fakeAccessToken"})

gock.New(thirdparty.FacebookUserInfoEndpoint).
Get("/me").
Reply(200).
JSON(&thirdparty.FacebookUser{
ID: "facebook_abcde",
Email: "[email protected]",
})

cfg := s.setUpConfig([]string{"facebook"}, []string{"https://example.com"})

state, err := thirdparty.GenerateState(cfg, "facebook", "https://example.com")
s.NoError(err)

req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/thirdparty/callback?code=abcde&state=%s", state), nil)
req.AddCookie(&http.Cookie{
Name: utils.HankoThirdpartyStateCookie,
Value: string(state),
})

c, rec := s.setUpContext(req)
handler := s.setUpHandler(cfg)

if s.NoError(handler.Callback(c)) {
s.Equal(http.StatusTemporaryRedirect, rec.Code)

s.assertLocationHeaderHasToken(rec)
s.assertStateCookieRemoved(rec)

email, err := s.Storage.GetEmailPersister().FindByAddress("[email protected]")
s.NoError(err)
s.NotNil(email)
s.True(email.IsPrimary())

user, err := s.Storage.GetUserPersister().Get(*email.UserID)
s.NoError(err)
s.NotNil(user)

identity := email.Identities.GetIdentity("facebook", "facebook_abcde")
s.NotNil(identity)

logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signin_succeeded"}, user.ID.String(), "", "", "")
s.NoError(lerr)
s.Len(logs, 1)
}
}

func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignUp_WithUnclaimedEmail() {
defer gock.Off()
if testing.Short() {
Expand Down
9 changes: 9 additions & 0 deletions backend/handler/thirdparty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ func (s *thirdPartySuite) setUpConfig(enabledProviders []string, allowedRedirect
Secret: "fakeClientSecret",
AllowLinking: false,
},
Facebook: config.ThirdPartyProvider{
Enabled: false,
ClientID: "fakeClientID",
Secret: "fakeClientSecret",
AllowLinking: false,
},
},
ErrorRedirectURL: "https://error.test.example",
RedirectURL: "https://api.test.example/callback",
Expand All @@ -112,6 +118,9 @@ func (s *thirdPartySuite) setUpConfig(enabledProviders []string, allowedRedirect
cfg.ThirdParty.Providers.Discord.Enabled = true
case "microsoft":
cfg.ThirdParty.Providers.Microsoft.Enabled = true
case "facebook":
cfg.ThirdParty.Providers.Facebook.Enabled = true
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions backend/json_schema/hanko.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,10 @@
"microsoft": {
"$ref": "#/$defs/ThirdPartyProvider",
"description": "`microsoft` contains the provider configuration for Microsoft."
},
"facebook": {
"$ref": "#/$defs/ThirdPartyProvider",
"description": "`facebook` contains the provider configuration for Facebook."
}
},
"additionalProperties": false,
Expand Down
Loading

0 comments on commit b509555

Please sign in to comment.