Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add facebook provider #2007

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions backend/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ third_party:
enabled: false
microsoft:
enabled: false
facebook:
enabled: false
username:
enabled: false
optional: true
Expand Down
5 changes: 5 additions & 0 deletions backend/config/config_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ func DefaultConfig() *Config {
AllowLinking: true,
Name: "google",
},
Facebook: ThirdPartyProvider{
DisplayName: "Facebook",
AllowLinking: true,
Name: "facebook",
},
},
},
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 @@ -375,6 +376,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
7 changes: 4 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
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 @@ -94,6 +94,13 @@ func (s *thirdPartySuite) setUpConfig(enabledProviders []string, allowedRedirect
Secret: "fakeClientSecret",
AllowLinking: false,
},
Facebook: config.ThirdPartyProvider{
Name: "facebook",
Enabled: false,
ClientID: "fakeClientID",
Secret: "fakeClientSecret",
AllowLinking: false,
},
},
ErrorRedirectURL: "https://error.test.example",
RedirectURL: "https://api.test.example/callback",
Expand All @@ -117,6 +124,8 @@ 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 @@ -1406,6 +1406,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
6 changes: 6 additions & 0 deletions backend/test/fixtures/thirdparty/emails.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
verified: false
created_at: 2020-12-31 23:59:59
updated_at: 2020-12-31 23:59:59
- id: 967ce4a0-677d-4dc3-bacf-53d54471369c
user_id:
address: [email protected]
verified: true
created_at: 2020-12-31 23:59:59
updated_at: 2020-12-31 23:59:59
- id: 527afce8-3b7b-41b6-b1ed-33d408c5a7bb
user_id: 43fb7e88-4d5d-4b2b-9335-391e78d7e472
address: [email protected]
Expand Down
7 changes: 7 additions & 0 deletions backend/test/fixtures/thirdparty/identities.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,10 @@
email_id: d781006b-4f55-4327-bad6-55bc34b88585
created_at: 2020-12-31 23:59:59
updated_at: 2020-12-31 23:59:59
- id: b6b1309d-61de-4a82-b8b8-d54db0be679b
provider_id: "facebook_abcde"
provider_name: "facebook"
data: '{"email":"[email protected]","sub":"facebook_abcde"}'
email_id: d781006b-4f55-4327-bad6-55bc34b88585
created_at: 2020-12-31 23:59:59
updated_at: 2020-12-31 23:59:59
5 changes: 5 additions & 0 deletions backend/test/fixtures/thirdparty/primary_emails.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@
user_id: 43fb7e88-4d5d-4b2b-9335-391e78d7e472
created_at: 2020-12-31 23:59:59
updated_at: 2020-12-31 23:59:59
- id: e2beaaa9-1275-4eb5-aa28-9970b36d249e
email_id: 967ce4a0-677d-4dc3-bacf-53d54471369c
user_id: ef0a05a7-98d1-4e5a-a60f-2c5f740cd26d
created_at: 2020-12-31 23:59:59
updated_at: 2020-12-31 23:59:59
4 changes: 4 additions & 0 deletions backend/test/fixtures/thirdparty/users.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
- id: 48df412f-a7b1-4fbc-ad2d-56bd3e103fd7
created_at: 2020-12-31 23:59:59
updated_at: 2020-12-31 23:59:59
# user with email and facebook identity
- id: ef0a05a7-98d1-4e5a-a60f-2c5f740cd26d
created_at: 2020-12-31 23:59:59
updated_at: 2020-12-31 23:59:59
# user with email, no identity
- id: 43fb7e88-4d5d-4b2b-9335-391e78d7e472
created_at: 2020-12-31 23:59:59
Expand Down
2 changes: 2 additions & 0 deletions backend/thirdparty/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ func getThirdPartyProvider(config config.ThirdParty, id string) (OAuthProvider,
return NewMicrosoftProvider(config.Providers.Microsoft, config.RedirectURL)
case "linkedin":
return NewLinkedInProvider(config.Providers.LinkedIn, config.RedirectURL)
case "facebook":
return NewFacebookProvider(config.Providers.Facebook, config.RedirectURL)
default:
return nil, fmt.Errorf("unknown provider: %s", id)
}
Expand Down
Loading
Loading