mirror of
https://github.com/teamhanko/hanko.git
synced 2026-03-13 08:43:15 +08:00
feat: add facebook provider (#2007)
* add sign in with facebook * feat: add facebook provider to factory function * feat: add facebook config defaults * feat: use newest facebook api version * feat: make facebook provider consistent with other providers * feat: add check for email We cannot assume a user always has a valid email. Even though it is not the used "me" endpoint, see: https://developers.facebook.com/docs/graph-api/reference/user/ * docs: elaborate comment * fix: fix third party tests * feat: add facebook icon * feat: add appsecret_proof to requests w. access token * refactor: build userinfo url programmatically * feat: map all available name claims --------- Co-authored-by: Prathamesh <psvagare@gmail.com>
This commit is contained in:
committed by
GitHub
parent
5023a53980
commit
d66b267646
@@ -116,6 +116,8 @@ third_party:
|
||||
enabled: false
|
||||
microsoft:
|
||||
enabled: false
|
||||
facebook:
|
||||
enabled: false
|
||||
username:
|
||||
enabled: false
|
||||
optional: true
|
||||
|
||||
@@ -149,6 +149,11 @@ func DefaultConfig() *Config {
|
||||
AllowLinking: true,
|
||||
Name: "google",
|
||||
},
|
||||
Facebook: ThirdPartyProvider{
|
||||
DisplayName: "Facebook",
|
||||
AllowLinking: true,
|
||||
Name: "facebook",
|
||||
},
|
||||
},
|
||||
},
|
||||
Passkey: Passkey{
|
||||
|
||||
@@ -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 {
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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: "test-facebook-signup@example.com",
|
||||
})
|
||||
|
||||
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("test-facebook-signup@example.com")
|
||||
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: "test-with-facebook-identity@example.com",
|
||||
})
|
||||
|
||||
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("test-with-facebook-identity@example.com")
|
||||
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() {
|
||||
|
||||
@@ -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",
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1408,6 +1408,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,
|
||||
|
||||
6
backend/test/fixtures/thirdparty/emails.yaml
vendored
6
backend/test/fixtures/thirdparty/emails.yaml
vendored
@@ -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: test-with-facebook-identity@example.com
|
||||
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: test-no-identity@example.com
|
||||
|
||||
@@ -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":"test-with-facebook-identity@example.com","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
|
||||
|
||||
@@ -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
backend/test/fixtures/thirdparty/users.yaml
vendored
4
backend/test/fixtures/thirdparty/users.yaml
vendored
@@ -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
|
||||
|
||||
2
backend/thirdparty/provider.go
vendored
2
backend/thirdparty/provider.go
vendored
@@ -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)
|
||||
}
|
||||
|
||||
129
backend/thirdparty/provider_facebook.go
vendored
Normal file
129
backend/thirdparty/provider_facebook.go
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
package thirdparty
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"github.com/teamhanko/hanko/backend/config"
|
||||
"golang.org/x/oauth2"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
FacebookAuthBase = "https://www.facebook.com"
|
||||
FacebookAPIBase = "https://graph.facebook.com"
|
||||
FacebookOauthAuthEndpoint = FacebookAuthBase + "/v21.0/dialog/oauth"
|
||||
FacebookOauthTokenEndpoint = FacebookAPIBase + "/v21.0/oauth/access_token"
|
||||
FacebookUserInfoEndpoint = FacebookAPIBase + "/me"
|
||||
)
|
||||
|
||||
var DefaultFacebookScopes = []string{
|
||||
"email", "public_profile",
|
||||
}
|
||||
|
||||
type facebookProvider struct {
|
||||
config config.ThirdPartyProvider
|
||||
oauthConfig *oauth2.Config
|
||||
}
|
||||
|
||||
type FacebookUser struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Picture struct {
|
||||
Data struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"data"`
|
||||
} `json:"picture"`
|
||||
FirstName string `json:"first_name"`
|
||||
MiddleName string `json:"middle_name"`
|
||||
LastName string `json:"last_name"`
|
||||
}
|
||||
|
||||
// NewFacebookProvider creates a Facebook third-party OAuth provider.
|
||||
func NewFacebookProvider(config config.ThirdPartyProvider, redirectURL string) (OAuthProvider, error) {
|
||||
if !config.Enabled {
|
||||
return nil, errors.New("facebook provider is disabled")
|
||||
}
|
||||
|
||||
return &facebookProvider{
|
||||
config: config,
|
||||
oauthConfig: &oauth2.Config{
|
||||
ClientID: config.ClientID,
|
||||
ClientSecret: config.Secret,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: FacebookOauthAuthEndpoint,
|
||||
TokenURL: FacebookOauthTokenEndpoint,
|
||||
},
|
||||
Scopes: DefaultFacebookScopes,
|
||||
RedirectURL: redirectURL,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f facebookProvider) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
|
||||
return f.oauthConfig.AuthCodeURL(state, opts...)
|
||||
}
|
||||
|
||||
func (f facebookProvider) GetOAuthToken(code string) (*oauth2.Token, error) {
|
||||
return f.oauthConfig.Exchange(context.Background(), code)
|
||||
}
|
||||
|
||||
func (f facebookProvider) GetUserData(token *oauth2.Token) (*UserData, error) {
|
||||
endpointURL, err := url.Parse(FacebookUserInfoEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpointURLQuery := endpointURL.Query()
|
||||
endpointURLQuery.Add("fields", "id,name,email,picture,first_name,middle_name,last_name")
|
||||
|
||||
// Calculate appsecret_proof, see:
|
||||
// https://developers.facebook.com/docs/graph-api/guides/secure-requests/#appsecret_proof
|
||||
hash := hmac.New(sha256.New, []byte(f.config.Secret))
|
||||
hash.Write([]byte(token.AccessToken))
|
||||
appsecretProof := hex.EncodeToString(hash.Sum(nil))
|
||||
|
||||
endpointURLQuery.Add("appsecret_proof", appsecretProof)
|
||||
endpointURL.RawQuery = endpointURLQuery.Encode()
|
||||
|
||||
var fbUser FacebookUser
|
||||
if err = makeRequest(token, f.oauthConfig, endpointURL.String(), &fbUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fbUser.Email == "" {
|
||||
return nil, errors.New("unable to find email with Facebook provider")
|
||||
}
|
||||
|
||||
data := &UserData{
|
||||
Emails: []Email{
|
||||
{
|
||||
Email: fbUser.Email,
|
||||
// Consider the email as verified because a User node only returns an email if a valid
|
||||
// email address is available. See: https://developers.facebook.com/docs/graph-api/reference/user/
|
||||
Verified: true,
|
||||
Primary: true,
|
||||
},
|
||||
},
|
||||
Metadata: &Claims{
|
||||
Issuer: FacebookAuthBase,
|
||||
Subject: fbUser.ID,
|
||||
Name: fbUser.Name,
|
||||
Picture: fbUser.Picture.Data.URL,
|
||||
Email: fbUser.Email,
|
||||
EmailVerified: true,
|
||||
GivenName: fbUser.FirstName,
|
||||
MiddleName: fbUser.MiddleName,
|
||||
FamilyName: fbUser.LastName,
|
||||
},
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (f facebookProvider) Name() string {
|
||||
return f.config.Name
|
||||
}
|
||||
@@ -59,6 +59,16 @@ spec:
|
||||
secretKeyRef:
|
||||
key: client_secret
|
||||
name: apple
|
||||
- name: THIRD_PARTY_PROVIDERS_FACEBOOK_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: client_id
|
||||
name: facebook
|
||||
- name: THIRD_PARTY_PROVIDERS_FACEBOOK_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: client_secret
|
||||
name: facebook
|
||||
initContainers:
|
||||
- name: hanko-migrate
|
||||
env:
|
||||
|
||||
@@ -23,3 +23,6 @@ secretGenerator:
|
||||
- name: apple
|
||||
envs:
|
||||
- apple.env
|
||||
- name: facebook
|
||||
envs:
|
||||
- facebook.env
|
||||
|
||||
51
frontend/elements/src/components/icons/Facebook.tsx
Normal file
51
frontend/elements/src/components/icons/Facebook.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { IconProps } from "./Icon";
|
||||
import cx from "classnames";
|
||||
import styles from "./styles.sass";
|
||||
|
||||
const Facebook = ({ size, secondary, disabled }: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 666.66668 666.66717"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs id="defs13">
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath25">
|
||||
<path d="M 0,700 H 700 V 0 H 0 Z" id="path23" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g
|
||||
id="g17"
|
||||
transform="matrix(1.3333333,0,0,-1.3333333,-133.33333,799.99999)"
|
||||
>
|
||||
<g id="g19">
|
||||
<g id="g21" clipPath="url(#clipPath25)">
|
||||
<g id="g27" transform="translate(600,350)">
|
||||
<path
|
||||
className={cx(
|
||||
styles.facebookIcon,
|
||||
disabled ? styles.disabledOutline : styles.outline,
|
||||
)}
|
||||
d="m 0,0 c 0,138.071 -111.929,250 -250,250 -138.071,0 -250,-111.929 -250,-250 0,-117.245 80.715,-215.622 189.606,-242.638 v 166.242 h -51.552 V 0 h 51.552 v 32.919 c 0,85.092 38.508,124.532 122.048,124.532 15.838,0 43.167,-3.105 54.347,-6.211 V 81.986 c -5.901,0.621 -16.149,0.932 -28.882,0.932 -40.993,0 -56.832,-15.528 -56.832,-55.9 V 0 h 81.659 l -14.028,-76.396 h -67.631 V -248.169 C -95.927,-233.218 0,-127.818 0,0"
|
||||
id="path29"
|
||||
/>
|
||||
</g>
|
||||
<g id="g31" transform="translate(447.9175,273.6036)">
|
||||
<path
|
||||
className={cx(
|
||||
styles.facebookIcon,
|
||||
disabled ? styles.disabledLetter : styles.letter,
|
||||
)}
|
||||
d="M 0,0 14.029,76.396 H -67.63 v 27.019 c 0,40.372 15.838,55.899 56.831,55.899 12.733,0 22.981,-0.31 28.882,-0.931 v 69.253 c -11.18,3.106 -38.509,6.212 -54.347,6.212 -83.539,0 -122.048,-39.441 -122.048,-124.533 V 76.396 h -51.552 V 0 h 51.552 v -166.242 c 19.343,-4.798 39.568,-7.362 60.394,-7.362 10.254,0 20.358,0.632 30.288,1.831 L -67.63,0 Z"
|
||||
id="path33"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default Facebook;
|
||||
@@ -4,6 +4,7 @@ import { default as copy } from "./Copy";
|
||||
import { default as customProvider } from "./CustomProvider";
|
||||
import { default as discord } from "./Discord";
|
||||
import { default as exclamation } from "./ExclamationMark";
|
||||
import { default as facebook } from "./Facebook";
|
||||
import { default as github } from "./GitHub";
|
||||
import { default as google } from "./Google";
|
||||
import { default as linkedin } from "./LinkedIn";
|
||||
@@ -22,6 +23,7 @@ export {
|
||||
customProvider,
|
||||
discord,
|
||||
exclamation,
|
||||
facebook,
|
||||
github,
|
||||
google,
|
||||
linkedin,
|
||||
|
||||
@@ -101,3 +101,12 @@
|
||||
&.red
|
||||
fill: #F25022
|
||||
|
||||
.facebookIcon
|
||||
&.outline
|
||||
fill: #0866FF
|
||||
&.disabledOutline
|
||||
fill: variables.$color-shade-1
|
||||
&.letter
|
||||
fill: #FFFFFF
|
||||
&.disabledLetter
|
||||
fill: variables.$color-shade-2
|
||||
|
||||
Reference in New Issue
Block a user