Merge pull request #182 from authorizerdev/feat/add-linkedin-login

feat: add linkedin login
This commit is contained in:
Lakhan Samani 2022-06-06 22:09:08 +05:30 committed by GitHub
commit 6d1d259f71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 400 additions and 19 deletions

30
app/package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.17.0",
"@authorizerdev/authorizer-react": "^0.23.0",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",
@ -26,9 +26,9 @@
}
},
"node_modules/@authorizerdev/authorizer-js": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.10.0.tgz",
"integrity": "sha512-REM8FLD/Ej9gzA2zDGDAke6QFss33ubePlTDmLDmIYUuQmpHFlO5mCCS6nVsKkN7F/Bcwkmp+eUNQjkdGCaKLg==",
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.12.0.tgz",
"integrity": "sha512-XgRxAkpRobbp15DeHygfOebCxlPJAXbVaLDckYyuz/PUDTyeMIG65RV5rQHYcL4oeoPqNc42dewwM3ST8JSiNg==",
"dependencies": {
"node-fetch": "^2.6.1"
},
@ -37,11 +37,11 @@
}
},
"node_modules/@authorizerdev/authorizer-react": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.17.0.tgz",
"integrity": "sha512-7WcNCU7hDFkVfFb8LcJXFwWiLYd8aY78z1AbNPxCa2Cw5G85PaRkzjKybP6h01ITVOHO6M03lLwPj8p6Sr6fEg==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.23.0.tgz",
"integrity": "sha512-vOwwrrAorxhVsqpf3BO2In8PMg8RAbGBFu8uLDOvUzkwG0ny5CPg6jLx9+dCkRRsqgB+agBoQoIuXEUP0ijsTA==",
"dependencies": {
"@authorizerdev/authorizer-js": "^0.10.0",
"@authorizerdev/authorizer-js": "^0.12.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
@ -852,19 +852,19 @@
},
"dependencies": {
"@authorizerdev/authorizer-js": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.10.0.tgz",
"integrity": "sha512-REM8FLD/Ej9gzA2zDGDAke6QFss33ubePlTDmLDmIYUuQmpHFlO5mCCS6nVsKkN7F/Bcwkmp+eUNQjkdGCaKLg==",
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.12.0.tgz",
"integrity": "sha512-XgRxAkpRobbp15DeHygfOebCxlPJAXbVaLDckYyuz/PUDTyeMIG65RV5rQHYcL4oeoPqNc42dewwM3ST8JSiNg==",
"requires": {
"node-fetch": "^2.6.1"
}
},
"@authorizerdev/authorizer-react": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.17.0.tgz",
"integrity": "sha512-7WcNCU7hDFkVfFb8LcJXFwWiLYd8aY78z1AbNPxCa2Cw5G85PaRkzjKybP6h01ITVOHO6M03lLwPj8p6Sr6fEg==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.23.0.tgz",
"integrity": "sha512-vOwwrrAorxhVsqpf3BO2In8PMg8RAbGBFu8uLDOvUzkwG0ny5CPg6jLx9+dCkRRsqgB+agBoQoIuXEUP0ijsTA==",
"requires": {
"@authorizerdev/authorizer-js": "^0.10.0",
"@authorizerdev/authorizer-js": "^0.12.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"

View File

@ -11,7 +11,7 @@
"author": "Lakhan Samani",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.17.0",
"@authorizerdev/authorizer-react": "^0.23.0",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",

View File

@ -9,7 +9,7 @@ import {
Divider,
useMediaQuery,
} from '@chakra-ui/react';
import { FaGoogle, FaGithub, FaFacebookF } from 'react-icons/fa';
import { FaGoogle, FaGithub, FaFacebookF, FaLinkedin } from 'react-icons/fa';
import { TextInputType, HiddenInputType } from '../../constants';
const OAuthConfig = ({
@ -182,6 +182,44 @@ const OAuthConfig = ({
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<FaLinkedin style={{ color: '#3b5998' }} />
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.LINKEDIN_CLIENT_ID}
placeholder="LinkedIn Client ID"
/>
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.LINKEDIN_CLIENT_SECRET}
placeholder="LinkedIn Secret"
/>
</Center>
</Flex>
</Stack>
</Box>
</div>

View File

@ -7,6 +7,7 @@ export const TextInputType = {
GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID',
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
REDIS_URL: 'REDIS_URL',
SMTP_HOST: 'SMTP_HOST',
@ -31,6 +32,7 @@ export const HiddenInputType = {
GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET',
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET',
JWT_SECRET: 'JWT_SECRET',
SMTP_PASSWORD: 'SMTP_PASSWORD',
ADMIN_SECRET: 'ADMIN_SECRET',
@ -99,6 +101,8 @@ export interface envVarTypes {
GITHUB_CLIENT_SECRET: string;
FACEBOOK_CLIENT_ID: string;
FACEBOOK_CLIENT_SECRET: string;
LINKEDIN_CLIENT_ID: string;
LINKEDIN_CLIENT_SECRET: string;
ROLES: [string] | [];
DEFAULT_ROLES: [string] | [];
PROTECTED_ROLES: [string] | [];

View File

@ -26,7 +26,8 @@ export const EnvVariablesQuery = `
GITHUB_CLIENT_SECRET,
FACEBOOK_CLIENT_ID,
FACEBOOK_CLIENT_SECRET,
ROLES,
LINKEDIN_CLIENT_ID,
LINKEDIN_CLIENT_SECRET,
DEFAULT_ROLES,
PROTECTED_ROLES,
JWT_TYPE,

View File

@ -46,6 +46,8 @@ const Environment = () => {
GITHUB_CLIENT_SECRET: '',
FACEBOOK_CLIENT_ID: '',
FACEBOOK_CLIENT_SECRET: '',
LINKEDIN_CLIENT_ID: '',
LINKEDIN_CLIENT_SECRET: '',
ROLES: [],
DEFAULT_ROLES: [],
PROTECTED_ROLES: [],
@ -83,6 +85,7 @@ const Environment = () => {
GOOGLE_CLIENT_SECRET: false,
GITHUB_CLIENT_SECRET: false,
FACEBOOK_CLIENT_SECRET: false,
LINKEDIN_CLIENT_SECRET: false,
JWT_SECRET: false,
SMTP_PASSWORD: false,
ADMIN_SECRET: false,

View File

@ -73,6 +73,10 @@ const (
EnvKeyFacebookClientID = "FACEBOOK_CLIENT_ID"
// EnvKeyFacebookClientSecret key for env variable FACEBOOK_CLIENT_SECRET
EnvKeyFacebookClientSecret = "FACEBOOK_CLIENT_SECRET"
// EnvKeyLinkedinClientID key for env variable LINKEDIN_CLIENT_ID
EnvKeyLinkedInClientID = "LINKEDIN_CLIENT_ID"
// EnvKeyLinkedinClientSecret key for env variable LINKEDIN_CLIENT_SECRET
EnvKeyLinkedInClientSecret = "LINKEDIN_CLIENT_SECRET"
// EnvKeyOrganizationName key for env variable ORGANIZATION_NAME
EnvKeyOrganizationName = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO

View File

@ -8,4 +8,7 @@ const (
FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token="
// Ref: https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#3-your-github-app-accesses-the-api-with-the-users-access-token
GithubUserInfoURL = "https://api.github.com/user"
// Ref: https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api
LinkedInUserInfoURL = "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName,emailAddress,profilePicture(displayImage~:playableStreams))"
LinkedInEmailURL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))"
)

View File

@ -11,4 +11,6 @@ const (
SignupMethodGithub = "github"
// SignupMethodFacebook is the facebook signup method
SignupMethodFacebook = "facebook"
// SignupMethodLinkedin is the linkedin signup method
SignupMethodLinkedIn = "linkedin"
)

16
server/env/env.go vendored
View File

@ -68,6 +68,8 @@ func InitAllEnv() error {
osGithubClientSecret := os.Getenv(constants.EnvKeyGithubClientSecret)
osFacebookClientID := os.Getenv(constants.EnvKeyFacebookClientID)
osFacebookClientSecret := os.Getenv(constants.EnvKeyFacebookClientSecret)
osLinkedInClientID := os.Getenv(constants.EnvKeyLinkedInClientID)
osLinkedInClientSecret := os.Getenv(constants.EnvKeyLinkedInClientSecret)
osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL)
osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName)
osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo)
@ -345,6 +347,20 @@ func InitAllEnv() error {
envData[constants.EnvKeyFacebookClientSecret] = osFacebookClientSecret
}
if val, ok := envData[constants.EnvKeyLinkedInClientID]; !ok || val == "" {
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
}
if osFacebookClientID != "" && envData[constants.EnvKeyLinkedInClientID] != osFacebookClientID {
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
}
if val, ok := envData[constants.EnvKeyLinkedInClientSecret]; !ok || val == "" {
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
}
if osFacebookClientSecret != "" && envData[constants.EnvKeyLinkedInClientSecret] != osFacebookClientSecret {
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
}
if val, ok := envData[constants.EnvKeyResetPasswordURL]; !ok || val == "" {
envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(osResetPasswordURL, "/")
}

View File

@ -85,6 +85,8 @@ type ComplexityRoot struct {
JwtRoleClaim func(childComplexity int) int
JwtSecret func(childComplexity int) int
JwtType func(childComplexity int) int
LinkedinClientID func(childComplexity int) int
LinkedinClientSecret func(childComplexity int) int
OrganizationLogo func(childComplexity int) int
OrganizationName func(childComplexity int) int
ProtectedRoles func(childComplexity int) int
@ -116,6 +118,7 @@ type ComplexityRoot struct {
IsFacebookLoginEnabled func(childComplexity int) int
IsGithubLoginEnabled func(childComplexity int) int
IsGoogleLoginEnabled func(childComplexity int) int
IsLinkedinLoginEnabled func(childComplexity int) int
IsMagicLinkLoginEnabled func(childComplexity int) int
IsSignUpEnabled func(childComplexity int) int
Version func(childComplexity int) int
@ -528,6 +531,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Env.JwtType(childComplexity), true
case "Env.LINKEDIN_CLIENT_ID":
if e.complexity.Env.LinkedinClientID == nil {
break
}
return e.complexity.Env.LinkedinClientID(childComplexity), true
case "Env.LINKEDIN_CLIENT_SECRET":
if e.complexity.Env.LinkedinClientSecret == nil {
break
}
return e.complexity.Env.LinkedinClientSecret(childComplexity), true
case "Env.ORGANIZATION_LOGO":
if e.complexity.Env.OrganizationLogo == nil {
break
@ -682,6 +699,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Meta.IsGoogleLoginEnabled(childComplexity), true
case "Meta.is_linkedin_login_enabled":
if e.complexity.Meta.IsLinkedinLoginEnabled == nil {
break
}
return e.complexity.Meta.IsLinkedinLoginEnabled(childComplexity), true
case "Meta.is_magic_link_login_enabled":
if e.complexity.Meta.IsMagicLinkLoginEnabled == nil {
break
@ -1352,6 +1376,7 @@ type Meta {
is_google_login_enabled: Boolean!
is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean!
is_linkedin_login_enabled: Boolean!
is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean!
@ -1462,6 +1487,8 @@ type Env {
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
}
@ -1509,6 +1536,8 @@ input UpdateEnvInput {
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
}
@ -3602,6 +3631,70 @@ func (ec *executionContext) _Env_FACEBOOK_CLIENT_SECRET(ctx context.Context, fie
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_LINKEDIN_CLIENT_ID(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.LinkedinClientID, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_LINKEDIN_CLIENT_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.LinkedinClientSecret, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_ORGANIZATION_NAME(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -4007,6 +4100,41 @@ func (ec *executionContext) _Meta_is_github_login_enabled(ctx context.Context, f
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Meta_is_linkedin_login_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Meta",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.IsLinkedinLoginEnabled, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Meta_is_email_verification_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -8563,6 +8691,22 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
if err != nil {
return it, err
}
case "LINKEDIN_CLIENT_ID":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("LINKEDIN_CLIENT_ID"))
it.LinkedinClientID, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "LINKEDIN_CLIENT_SECRET":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("LINKEDIN_CLIENT_SECRET"))
it.LinkedinClientSecret, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "ORGANIZATION_NAME":
var err error
@ -9031,6 +9175,10 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj
out.Values[i] = ec._Env_FACEBOOK_CLIENT_ID(ctx, field, obj)
case "FACEBOOK_CLIENT_SECRET":
out.Values[i] = ec._Env_FACEBOOK_CLIENT_SECRET(ctx, field, obj)
case "LINKEDIN_CLIENT_ID":
out.Values[i] = ec._Env_LINKEDIN_CLIENT_ID(ctx, field, obj)
case "LINKEDIN_CLIENT_SECRET":
out.Values[i] = ec._Env_LINKEDIN_CLIENT_SECRET(ctx, field, obj)
case "ORGANIZATION_NAME":
out.Values[i] = ec._Env_ORGANIZATION_NAME(ctx, field, obj)
case "ORGANIZATION_LOGO":
@ -9142,6 +9290,11 @@ func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null {
invalids++
}
case "is_linkedin_login_enabled":
out.Values[i] = ec._Meta_is_linkedin_login_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "is_email_verification_enabled":
out.Values[i] = ec._Meta_is_email_verification_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null {

View File

@ -65,6 +65,8 @@ type Env struct {
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"`
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
}
@ -116,6 +118,7 @@ type Meta struct {
IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
IsLinkedinLoginEnabled bool `json:"is_linkedin_login_enabled"`
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
@ -216,6 +219,8 @@ type UpdateEnvInput struct {
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"`
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
}

View File

@ -18,6 +18,7 @@ type Meta {
is_google_login_enabled: Boolean!
is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean!
is_linkedin_login_enabled: Boolean!
is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean!
@ -128,6 +129,8 @@ type Env {
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
}
@ -175,6 +178,8 @@ input UpdateEnvInput {
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
}

View File

@ -60,6 +60,8 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user, err = processGithubUserInfo(code)
case constants.SignupMethodFacebook:
user, err = processFacebookUserInfo(code)
case constants.SignupMethodLinkedIn:
user, err = processLinkedInUserInfo(code)
default:
log.Info("Invalid oauth provider")
err = fmt.Errorf(`invalid oauth provider`)
@ -283,6 +285,10 @@ func processGithubUserInfo(code string) (models.User, error) {
log.Debug("Failed to read github user info response body: ", err)
return user, fmt.Errorf("failed to read github response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request linkedin user info: ", string(body))
return user, fmt.Errorf("failed to request linkedin user info: %s", string(body))
}
userRawData := make(map[string]string)
json.Unmarshal(body, &userRawData)
@ -335,7 +341,10 @@ func processFacebookUserInfo(code string) (models.User, error) {
log.Debug("Failed to read facebook response: ", err)
return user, fmt.Errorf("failed to read facebook response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request linkedin user info: ", string(body))
return user, fmt.Errorf("failed to request linkedin user info: %s", string(body))
}
userRawData := make(map[string]interface{})
json.Unmarshal(body, &userRawData)
@ -356,3 +365,85 @@ func processFacebookUserInfo(code string) (models.User, error) {
return user, nil
}
func processLinkedInUserInfo(code string) (models.User, error) {
user := models.User{}
token, err := oauth.OAuthProviders.LinkedInConfig.Exchange(oauth2.NoContext, code)
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid linkedin exchange code: %s", err.Error())
}
client := http.Client{}
req, err := http.NewRequest("GET", constants.LinkedInUserInfoURL, nil)
if err != nil {
log.Debug("Failed to create linkedin user info request: ", err)
return user, fmt.Errorf("error creating linkedin user info request: %s", err.Error())
}
req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", token.AccessToken)},
}
response, err := client.Do(req)
if err != nil {
log.Debug("Failed to request linkedin user info: ", err)
return user, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read linkedin user info response body: ", err)
return user, fmt.Errorf("failed to read linkedin response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request linkedin user info: ", string(body))
return user, fmt.Errorf("failed to request linkedin user info: %s", string(body))
}
userRawData := make(map[string]interface{})
json.Unmarshal(body, &userRawData)
req, err = http.NewRequest("GET", constants.LinkedInEmailURL, nil)
if err != nil {
log.Debug("Failed to create linkedin email info request: ", err)
return user, fmt.Errorf("error creating linkedin user info request: %s", err.Error())
}
req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", token.AccessToken)},
}
response, err = client.Do(req)
if err != nil {
log.Debug("Failed to request linkedin email info: ", err)
return user, err
}
defer response.Body.Close()
body, err = ioutil.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read linkedin email info response body: ", err)
return user, fmt.Errorf("failed to read linkedin email response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request linkedin user info: ", string(body))
return user, fmt.Errorf("failed to request linkedin user info: %s", string(body))
}
emailRawData := make(map[string]interface{})
json.Unmarshal(body, &emailRawData)
firstName := userRawData["localizedFirstName"].(string)
lastName := userRawData["localizedLastName"].(string)
profilePicture := userRawData["profilePicture"].(map[string]interface{})["displayImage~"].(map[string]interface{})["elements"].([]interface{})[0].(map[string]interface{})["identifiers"].([]interface{})[0].(map[string]interface{})["identifier"].(string)
emailAddress := emailRawData["elements"].([]interface{})[0].(map[string]interface{})["handle~"].(map[string]interface{})["emailAddress"].(string)
user = models.User{
GivenName: &firstName,
FamilyName: &lastName,
Picture: &profilePicture,
Email: emailAddress,
}
return user, nil
}

View File

@ -151,6 +151,23 @@ func OAuthLoginHandler() gin.HandlerFunc {
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/facebook"
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodLinkedIn:
if oauth.OAuthProviders.LinkedInConfig == nil {
log.Debug("Linkedin OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodLinkedIn)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
"error": "internal server error",
})
return
}
oauth.OAuthProviders.LinkedInConfig.RedirectURL = hostname + "/oauth_callback/linkedin"
url := oauth.OAuthProviders.LinkedInConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
default:
log.Debug("Invalid oauth provider: ", provider)
c.JSON(422, gin.H{

View File

@ -7,6 +7,7 @@ import (
"golang.org/x/oauth2"
facebookOAuth2 "golang.org/x/oauth2/facebook"
githubOAuth2 "golang.org/x/oauth2/github"
linkedInOAuth2 "golang.org/x/oauth2/linkedin"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
@ -17,6 +18,7 @@ type OAuthProvider struct {
GoogleConfig *oauth2.Config
GithubConfig *oauth2.Config
FacebookConfig *oauth2.Config
LinkedInConfig *oauth2.Config
}
// OIDCProviders is a struct that contains reference all the OpenID providers
@ -92,5 +94,23 @@ func InitOAuth() error {
}
}
linkedInClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientID)
if err != nil {
linkedInClientID = ""
}
linkedInClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientSecret)
if err != nil {
linkedInClientSecret = ""
}
if linkedInClientID != "" && linkedInClientSecret != "" {
OAuthProviders.LinkedInConfig = &oauth2.Config{
ClientID: linkedInClientID,
ClientSecret: linkedInClientSecret,
RedirectURL: "/oauth_callback/linkedin",
Endpoint: linkedInOAuth2.Endpoint,
Scopes: []string{"r_liteprofile", "r_emailaddress"},
}
}
return nil
}

View File

@ -130,6 +130,12 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
if val, ok := store[constants.EnvKeyGithubClientSecret]; ok {
res.GithubClientSecret = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyLinkedInClientID]; ok {
res.LinkedinClientID = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyLinkedInClientSecret]; ok {
res.LinkedinClientSecret = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyOrganizationName]; ok {
res.OrganizationName = utils.NewStringRef(val.(string))
}

View File

@ -41,6 +41,18 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
facebookClientSecret = ""
}
linkedClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientID)
if err != nil {
log.Debug("Failed to get Facebook Client ID from environment variable", err)
linkedClientID = ""
}
linkedInClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientSecret)
if err != nil {
log.Debug("Failed to get Facebook Client Secret from environment variable", err)
linkedInClientSecret = ""
}
githubClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID)
if err != nil {
log.Debug("Failed to get Github Client ID from environment variable", err)
@ -83,6 +95,7 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
IsGoogleLoginEnabled: googleClientID != "" && googleClientSecret != "",
IsGithubLoginEnabled: githubClientID != "" && githubClientSecret != "",
IsFacebookLoginEnabled: facebookClientID != "" && facebookClientSecret != "",
IsLinkedinLoginEnabled: linkedClientID != "" && linkedInClientSecret != "",
IsBasicAuthenticationEnabled: !isBasicAuthDisabled,
IsEmailVerificationEnabled: !isEmailVerificationDisabled,
IsMagicLinkLoginEnabled: !isMagicLinkLoginDisabled,