Merge branch 'authorizerdev:main' into main

This commit is contained in:
scaletech-milan 2023-12-04 11:48:25 +05:30 committed by GitHub
commit a203b853f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 722 additions and 147 deletions

31
app/package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-react": "^1.1.13", "@authorizerdev/authorizer-react": "^1.1.18",
"@types/react": "^17.0.15", "@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9", "@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17", "esbuild": "^0.12.17",
@ -27,9 +27,9 @@
} }
}, },
"node_modules/@authorizerdev/authorizer-js": { "node_modules/@authorizerdev/authorizer-js": {
"version": "1.2.6", "version": "1.2.18",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.6.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.18.tgz",
"integrity": "sha512-9+9phHUMF+AeDM0y+XQvIRDoerOXnQ1vfTfYN6KxWN1apdrkAd9nzS1zUsA2uJSnX3fFZOErn83GjbYYCYF1BA==", "integrity": "sha512-9j5U/4lqaaEcG78Zli+TtLJ0migSKhFwnXXunulAGTZOzQSTCJ/CSSPip5wWNa/Mkr6gdEMwk1HYfhIdk2A9Mg==",
"dependencies": { "dependencies": {
"cross-fetch": "^3.1.5" "cross-fetch": "^3.1.5"
}, },
@ -41,11 +41,12 @@
} }
}, },
"node_modules/@authorizerdev/authorizer-react": { "node_modules/@authorizerdev/authorizer-react": {
"version": "1.1.13", "version": "1.1.18",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.13.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.18.tgz",
"integrity": "sha512-LmpzyfR0+nEn+bjUrb/QU9b3kiVoYzMBIvcQ1nV4TNvrvVSqbLPKk+GmoIPkiBEtfy/QSM6XFLkiGNGD9BRP+g==", "integrity": "sha512-5SgFzG1VatmrMpl9XKwPcoVmCayA4Hn+sd2I9CwRlCWkdcna4pGJL8kYesuIGjGagS9394qp4ICRLRZ35wXj8A==",
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-js": "^1.2.6" "@authorizerdev/authorizer-js": "^1.2.18",
"validator": "^13.11.0"
}, },
"engines": { "engines": {
"node": ">=10" "node": ">=10"
@ -607,9 +608,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}, },
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.6.12", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": { "dependencies": {
"whatwg-url": "^5.0.0" "whatwg-url": "^5.0.0"
}, },
@ -847,6 +848,14 @@
"node": ">=4.2.0" "node": ">=4.2.0"
} }
}, },
"node_modules/validator": {
"version": "13.11.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
"integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/value-equal": { "node_modules/value-equal": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",

View File

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

View File

@ -32,29 +32,34 @@ const FooterContent = styled.div`
export default function Login({ urlProps }: { urlProps: Record<string, any> }) { export default function Login({ urlProps }: { urlProps: Record<string, any> }) {
const { config } = useAuthorizer(); const { config } = useAuthorizer();
const [view, setView] = useState<VIEW_TYPES>(VIEW_TYPES.LOGIN); const [view, setView] = useState<VIEW_TYPES>(VIEW_TYPES.LOGIN);
const isBasicAuth = config.is_basic_authentication_enabled;
return ( return (
<Fragment> <Fragment>
{view === VIEW_TYPES.LOGIN && ( {view === VIEW_TYPES.LOGIN && (
<Fragment> <Fragment>
<h1 style={{ textAlign: 'center' }}>Login</h1> <h1 style={{ textAlign: 'center' }}>Login</h1>
<br />
<AuthorizerSocialLogin urlProps={urlProps} /> <AuthorizerSocialLogin urlProps={urlProps} />
{config.is_basic_authentication_enabled && <br />
{(config.is_basic_authentication_enabled ||
config.is_mobile_basic_authentication_enabled) &&
!config.is_magic_link_login_enabled && ( !config.is_magic_link_login_enabled && (
<AuthorizerBasicAuthLogin urlProps={urlProps} /> <AuthorizerBasicAuthLogin urlProps={urlProps} />
)} )}
{config.is_magic_link_login_enabled && ( {config.is_magic_link_login_enabled && (
<AuthorizerMagicLinkLogin urlProps={urlProps} /> <AuthorizerMagicLinkLogin urlProps={urlProps} />
)} )}
<Footer> {(config.is_basic_authentication_enabled ||
<Link config.is_mobile_basic_authentication_enabled) && (
to="#" <Footer>
onClick={() => setView(VIEW_TYPES.FORGOT_PASSWORD)} <Link
style={{ marginBottom: 10 }} to="#"
> onClick={() => setView(VIEW_TYPES.FORGOT_PASSWORD)}
Forgot Password? style={{ marginBottom: 10 }}
</Link> >
</Footer> Forgot Password?
</Link>
</Footer>
)}
</Fragment> </Fragment>
)} )}
{view === VIEW_TYPES.FORGOT_PASSWORD && ( {view === VIEW_TYPES.FORGOT_PASSWORD && (
@ -81,7 +86,7 @@ export default function Login({ urlProps }: { urlProps: Record<string, any> }) {
!config.is_magic_link_login_enabled && !config.is_magic_link_login_enabled &&
config.is_sign_up_enabled && ( config.is_sign_up_enabled && (
<FooterContent> <FooterContent>
Don't have an account? <Link to="/app/signup"> Sign Up</Link> Don't have an account? &nbsp; <Link to="/app/signup"> Sign Up</Link>
</FooterContent> </FooterContent>
)} )}
</Fragment> </Fragment>

View File

@ -2,19 +2,20 @@
# yarn lockfile v1 # yarn lockfile v1
"@authorizerdev/authorizer-js@^1.2.6": "@authorizerdev/authorizer-js@^1.2.18":
version "1.2.6" version "1.2.18"
resolved "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.6.tgz" resolved "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.18.tgz"
integrity sha512-9+9phHUMF+AeDM0y+XQvIRDoerOXnQ1vfTfYN6KxWN1apdrkAd9nzS1zUsA2uJSnX3fFZOErn83GjbYYCYF1BA== integrity sha512-9j5U/4lqaaEcG78Zli+TtLJ0migSKhFwnXXunulAGTZOzQSTCJ/CSSPip5wWNa/Mkr6gdEMwk1HYfhIdk2A9Mg==
dependencies: dependencies:
cross-fetch "^3.1.5" cross-fetch "^3.1.5"
"@authorizerdev/authorizer-react@^1.1.13": "@authorizerdev/authorizer-react@^1.1.18":
version "1.1.13" version "1.1.18"
resolved "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.13.tgz" resolved "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.18.tgz"
integrity sha512-LmpzyfR0+nEn+bjUrb/QU9b3kiVoYzMBIvcQ1nV4TNvrvVSqbLPKk+GmoIPkiBEtfy/QSM6XFLkiGNGD9BRP+g== integrity sha512-5SgFzG1VatmrMpl9XKwPcoVmCayA4Hn+sd2I9CwRlCWkdcna4pGJL8kYesuIGjGagS9394qp4ICRLRZ35wXj8A==
dependencies: dependencies:
"@authorizerdev/authorizer-js" "^1.2.6" "@authorizerdev/authorizer-js" "^1.2.18"
validator "^13.11.0"
"@babel/code-frame@^7.22.13": "@babel/code-frame@^7.22.13":
version "7.22.13" version "7.22.13"
@ -420,9 +421,9 @@ ms@2.1.2:
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
node-fetch@^2.6.12: node-fetch@^2.6.12:
version "2.6.12" version "2.7.0"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz"
integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies: dependencies:
whatwg-url "^5.0.0" whatwg-url "^5.0.0"
@ -594,6 +595,11 @@ typescript@^4.3.5:
resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz" resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz"
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
validator@^13.11.0:
version "13.11.0"
resolved "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz"
integrity sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==
value-equal@^1.0.1: value-equal@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz" resolved "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz"

View File

@ -17,6 +17,7 @@ import {
FaApple, FaApple,
FaTwitter, FaTwitter,
FaMicrosoft, FaMicrosoft,
FaTwitch,
} from 'react-icons/fa'; } from 'react-icons/fa';
import { import {
TextInputType, TextInputType,
@ -397,6 +398,44 @@ const OAuthConfig = ({
/> />
</Center> </Center>
</Flex> </Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<FaTwitch />
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.TWITCH_CLIENT_ID}
placeholder="Twitch 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.TWITCH_CLIENT_SECRET}
placeholder="Twitch Client Secret"
/>
</Center>
</Flex>
</Stack> </Stack>
</Box> </Box>
</div> </div>

View File

@ -12,6 +12,7 @@ export const TextInputType = {
TWITTER_CLIENT_ID: 'TWITTER_CLIENT_ID', TWITTER_CLIENT_ID: 'TWITTER_CLIENT_ID',
MICROSOFT_CLIENT_ID: 'MICROSOFT_CLIENT_ID', MICROSOFT_CLIENT_ID: 'MICROSOFT_CLIENT_ID',
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: 'MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID', MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: 'MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID',
TWITCH_CLIENT_ID: 'TWITCH_CLIENT_ID',
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM', JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
REDIS_URL: 'REDIS_URL', REDIS_URL: 'REDIS_URL',
SMTP_HOST: 'SMTP_HOST', SMTP_HOST: 'SMTP_HOST',
@ -42,6 +43,7 @@ export const HiddenInputType = {
APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET', APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET',
TWITTER_CLIENT_SECRET: 'TWITTER_CLIENT_SECRET', TWITTER_CLIENT_SECRET: 'TWITTER_CLIENT_SECRET',
MICROSOFT_CLIENT_SECRET: 'MICROSOFT_CLIENT_SECRET', MICROSOFT_CLIENT_SECRET: 'MICROSOFT_CLIENT_SECRET',
TWITCH_CLIENT_SECRET: 'TWITCH_CLIENT_SECRET',
JWT_SECRET: 'JWT_SECRET', JWT_SECRET: 'JWT_SECRET',
SMTP_PASSWORD: 'SMTP_PASSWORD', SMTP_PASSWORD: 'SMTP_PASSWORD',
ADMIN_SECRET: 'ADMIN_SECRET', ADMIN_SECRET: 'ADMIN_SECRET',
@ -132,6 +134,8 @@ export interface envVarTypes {
MICROSOFT_CLIENT_ID: string; MICROSOFT_CLIENT_ID: string;
MICROSOFT_CLIENT_SECRET: string; MICROSOFT_CLIENT_SECRET: string;
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: string; MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: string;
TWITCH_CLIENT_ID: string;
TWITCH_CLIENT_SECRET: string;
ROLES: [string] | []; ROLES: [string] | [];
DEFAULT_ROLES: [string] | []; DEFAULT_ROLES: [string] | [];
PROTECTED_ROLES: [string] | []; PROTECTED_ROLES: [string] | [];

View File

@ -35,6 +35,8 @@ export const EnvVariablesQuery = `
MICROSOFT_CLIENT_ID MICROSOFT_CLIENT_ID
MICROSOFT_CLIENT_SECRET MICROSOFT_CLIENT_SECRET
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID
TWITCH_CLIENT_ID
TWITCH_CLIENT_SECRET
DEFAULT_ROLES DEFAULT_ROLES
PROTECTED_ROLES PROTECTED_ROLES
ROLES ROLES

View File

@ -55,6 +55,8 @@ const Environment = () => {
MICROSOFT_CLIENT_ID: '', MICROSOFT_CLIENT_ID: '',
MICROSOFT_CLIENT_SECRET: '', MICROSOFT_CLIENT_SECRET: '',
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: '', MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: '',
TWITCH_CLIENT_ID: '',
TWITCH_CLIENT_SECRET: '',
ROLES: [], ROLES: [],
DEFAULT_ROLES: [], DEFAULT_ROLES: [],
PROTECTED_ROLES: [], PROTECTED_ROLES: [],
@ -107,6 +109,7 @@ const Environment = () => {
LINKEDIN_CLIENT_SECRET: false, LINKEDIN_CLIENT_SECRET: false,
APPLE_CLIENT_SECRET: false, APPLE_CLIENT_SECRET: false,
TWITTER_CLIENT_SECRET: false, TWITTER_CLIENT_SECRET: false,
TWITCH_CLIENT_SECRET: false,
JWT_SECRET: false, JWT_SECRET: false,
SMTP_PASSWORD: false, SMTP_PASSWORD: false,
ADMIN_SECRET: false, ADMIN_SECRET: false,

View File

@ -19,7 +19,7 @@ type Provider interface {
// Generate totp: to generate totp, store secret into db and returns base64 of QR code image // Generate totp: to generate totp, store secret into db and returns base64 of QR code image
Generate(ctx context.Context, id string) (*AuthenticatorConfig, error) Generate(ctx context.Context, id string) (*AuthenticatorConfig, error)
// Validate totp: user passcode with secret stored in our db // Validate totp: user passcode with secret stored in our db
Validate(ctx context.Context, passcode string, id string) (bool, error) Validate(ctx context.Context, passcode string, userID string) (bool, error)
// RecoveryCode totp: gives a recovery code for first time user // ValidateRecoveryCode totp: allows user to validate using recovery code incase if they lost their device
RecoveryCode(ctx context.Context, id string) (*string, error) ValidateRecoveryCode(ctx context.Context, recoveryCode, userID string) (bool, error)
} }

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"image/png" "image/png"
"time" "time"
@ -113,24 +114,38 @@ func (p *provider) Validate(ctx context.Context, passcode string, userID string)
return status, nil return status, nil
} }
// RecoveryCode generates a recovery code for a user's TOTP authentication, if not already verified. // ValidateRecoveryCode validates a Time-Based One-Time Password (TOTP) recovery code against the stored TOTP recovery code for a user.
func (p *provider) RecoveryCode(ctx context.Context, id string) (*string, error) { func (p *provider) ValidateRecoveryCode(ctx context.Context, recoveryCode, userID string) (bool, error) {
// get totp details // get totp details
// totpModel, err := db.Provider.GetAuthenticatorDetailsByUserId(ctx, id, constants.EnvKeyTOTPAuthenticator) totpModel, err := db.Provider.GetAuthenticatorDetailsByUserId(ctx, userID, constants.EnvKeyTOTPAuthenticator)
// if err != nil { if err != nil {
// return nil, fmt.Errorf("error while getting totp details from authenticators") return false, err
// } }
// //TODO *totpModel.RecoveryCode == "null" used to just verify couchbase recoveryCode value to be nil // convert recoveryCodes to map
// // have to find another way round recoveryCodesMap := map[string]bool{}
// if totpModel.RecoveryCode == nil || *totpModel.RecoveryCode == "null" { err = json.Unmarshal([]byte(refs.StringValue(totpModel.RecoveryCodes)), &recoveryCodesMap)
// recoveryCode := utils.GenerateTOTPRecoveryCode() if err != nil {
// totpModel.RecoveryCode = &recoveryCode return false, err
}
// _, err = db.Provider.UpdateAuthenticator(ctx, totpModel) // check if recovery code is valid
// if err != nil { if val, ok := recoveryCodesMap[recoveryCode]; !ok {
// return nil, fmt.Errorf("error while updaing authenticator table for totp") return false, fmt.Errorf("invalid recovery code")
// } } else if val {
// return &recoveryCode, nil return false, fmt.Errorf("recovery code already used")
// } }
return nil, nil // update recovery code map
recoveryCodesMap[recoveryCode] = true
// convert recoveryCodesMap to string
jsonData, err := json.Marshal(recoveryCodesMap)
if err != nil {
return false, err
}
recoveryCodesString := string(jsonData)
totpModel.RecoveryCodes = refs.NewStringRef(recoveryCodesString)
// update recovery code map in db
_, err = db.Provider.UpdateAuthenticator(ctx, totpModel)
if err != nil {
return false, err
}
return true, nil
} }

View File

@ -23,4 +23,6 @@ const (
AuthRecipeMethodTwitter = "twitter" AuthRecipeMethodTwitter = "twitter"
// AuthRecipeMethodMicrosoft is the microsoft auth method // AuthRecipeMethodMicrosoft is the microsoft auth method
AuthRecipeMethodMicrosoft = "microsoft" AuthRecipeMethodMicrosoft = "microsoft"
// AuthRecipeMethodTwitch is the twitch auth method
AuthRecipeMethodTwitch = "twitch"
) )

View File

@ -118,6 +118,10 @@ const (
EnvKeyMicrosoftActiveDirectoryTenantID = "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID" EnvKeyMicrosoftActiveDirectoryTenantID = "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID"
// EnvKeyMicrosoftClientSecret key for env variable MICROSOFT_CLIENT_SECRET // EnvKeyMicrosoftClientSecret key for env variable MICROSOFT_CLIENT_SECRET
EnvKeyMicrosoftClientSecret = "MICROSOFT_CLIENT_SECRET" EnvKeyMicrosoftClientSecret = "MICROSOFT_CLIENT_SECRET"
// EnvKeyTwitchClientID key for env variable TWITCH_CLIENT_ID
EnvKeyTwitchClientID = "TWITCH_CLIENT_ID"
// EnvKeyTwitchClientSecret key for env variable TWITCH_CLIENT_SECRET
EnvKeyTwitchClientSecret = "TWITCH_CLIENT_SECRET"
// EnvKeyOrganizationName key for env variable ORGANIZATION_NAME // EnvKeyOrganizationName key for env variable ORGANIZATION_NAME
EnvKeyOrganizationName = "ORGANIZATION_NAME" EnvKeyOrganizationName = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO // EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO

View File

@ -139,6 +139,8 @@ type ComplexityRoot struct {
SMTPUsername func(childComplexity int) int SMTPUsername func(childComplexity int) int
SenderEmail func(childComplexity int) int SenderEmail func(childComplexity int) int
SenderName func(childComplexity int) int SenderName func(childComplexity int) int
TwitchClientID func(childComplexity int) int
TwitchClientSecret func(childComplexity int) int
TwitterClientID func(childComplexity int) int TwitterClientID func(childComplexity int) int
TwitterClientSecret func(childComplexity int) int TwitterClientSecret func(childComplexity int) int
} }
@ -160,21 +162,24 @@ type ComplexityRoot struct {
} }
Meta struct { Meta struct {
ClientID func(childComplexity int) int ClientID func(childComplexity int) int
IsAppleLoginEnabled func(childComplexity int) int IsAppleLoginEnabled func(childComplexity int) int
IsBasicAuthenticationEnabled func(childComplexity int) int IsBasicAuthenticationEnabled func(childComplexity int) int
IsEmailVerificationEnabled func(childComplexity int) int IsEmailVerificationEnabled func(childComplexity int) int
IsFacebookLoginEnabled func(childComplexity int) int IsFacebookLoginEnabled func(childComplexity int) int
IsGithubLoginEnabled func(childComplexity int) int IsGithubLoginEnabled func(childComplexity int) int
IsGoogleLoginEnabled func(childComplexity int) int IsGoogleLoginEnabled func(childComplexity int) int
IsLinkedinLoginEnabled func(childComplexity int) int IsLinkedinLoginEnabled func(childComplexity int) int
IsMagicLinkLoginEnabled func(childComplexity int) int IsMagicLinkLoginEnabled func(childComplexity int) int
IsMicrosoftLoginEnabled func(childComplexity int) int IsMicrosoftLoginEnabled func(childComplexity int) int
IsMultiFactorAuthEnabled func(childComplexity int) int IsMobileBasicAuthenticationEnabled func(childComplexity int) int
IsSignUpEnabled func(childComplexity int) int IsMultiFactorAuthEnabled func(childComplexity int) int
IsStrongPasswordEnabled func(childComplexity int) int IsPhoneVerificationEnabled func(childComplexity int) int
IsTwitterLoginEnabled func(childComplexity int) int IsSignUpEnabled func(childComplexity int) int
Version func(childComplexity int) int IsStrongPasswordEnabled func(childComplexity int) int
IsTwitchLoginEnabled func(childComplexity int) int
IsTwitterLoginEnabled func(childComplexity int) int
Version func(childComplexity int) int
} }
Mutation struct { Mutation struct {
@ -992,6 +997,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Env.SenderName(childComplexity), true return e.complexity.Env.SenderName(childComplexity), true
case "Env.TWITCH_CLIENT_ID":
if e.complexity.Env.TwitchClientID == nil {
break
}
return e.complexity.Env.TwitchClientID(childComplexity), true
case "Env.TWITCH_CLIENT_SECRET":
if e.complexity.Env.TwitchClientSecret == nil {
break
}
return e.complexity.Env.TwitchClientSecret(childComplexity), true
case "Env.TWITTER_CLIENT_ID": case "Env.TWITTER_CLIENT_ID":
if e.complexity.Env.TwitterClientID == nil { if e.complexity.Env.TwitterClientID == nil {
break break
@ -1125,6 +1144,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Meta.IsMicrosoftLoginEnabled(childComplexity), true return e.complexity.Meta.IsMicrosoftLoginEnabled(childComplexity), true
case "Meta.is_mobile_basic_authentication_enabled":
if e.complexity.Meta.IsMobileBasicAuthenticationEnabled == nil {
break
}
return e.complexity.Meta.IsMobileBasicAuthenticationEnabled(childComplexity), true
case "Meta.is_multi_factor_auth_enabled": case "Meta.is_multi_factor_auth_enabled":
if e.complexity.Meta.IsMultiFactorAuthEnabled == nil { if e.complexity.Meta.IsMultiFactorAuthEnabled == nil {
break break
@ -1132,6 +1158,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Meta.IsMultiFactorAuthEnabled(childComplexity), true return e.complexity.Meta.IsMultiFactorAuthEnabled(childComplexity), true
case "Meta.is_phone_verification_enabled":
if e.complexity.Meta.IsPhoneVerificationEnabled == nil {
break
}
return e.complexity.Meta.IsPhoneVerificationEnabled(childComplexity), true
case "Meta.is_sign_up_enabled": case "Meta.is_sign_up_enabled":
if e.complexity.Meta.IsSignUpEnabled == nil { if e.complexity.Meta.IsSignUpEnabled == nil {
break break
@ -1146,6 +1179,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Meta.IsStrongPasswordEnabled(childComplexity), true return e.complexity.Meta.IsStrongPasswordEnabled(childComplexity), true
case "Meta.is_twitch_login_enabled":
if e.complexity.Meta.IsTwitchLoginEnabled == nil {
break
}
return e.complexity.Meta.IsTwitchLoginEnabled(childComplexity), true
case "Meta.is_twitter_login_enabled": case "Meta.is_twitter_login_enabled":
if e.complexity.Meta.IsTwitterLoginEnabled == nil { if e.complexity.Meta.IsTwitterLoginEnabled == nil {
break break
@ -2324,12 +2364,15 @@ type Meta {
is_apple_login_enabled: Boolean! is_apple_login_enabled: Boolean!
is_twitter_login_enabled: Boolean! is_twitter_login_enabled: Boolean!
is_microsoft_login_enabled: Boolean! is_microsoft_login_enabled: Boolean!
is_twitch_login_enabled: Boolean!
is_email_verification_enabled: Boolean! is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean! is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean! is_magic_link_login_enabled: Boolean!
is_sign_up_enabled: Boolean! is_sign_up_enabled: Boolean!
is_strong_password_enabled: Boolean! is_strong_password_enabled: Boolean!
is_multi_factor_auth_enabled: Boolean! is_multi_factor_auth_enabled: Boolean!
is_mobile_basic_authentication_enabled: Boolean!
is_phone_verification_enabled: Boolean!
} }
type User { type User {
@ -2477,6 +2520,8 @@ type Env {
MICROSOFT_CLIENT_ID: String MICROSOFT_CLIENT_ID: String
MICROSOFT_CLIENT_SECRET: String MICROSOFT_CLIENT_SECRET: String
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: String MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: String
TWITCH_CLIENT_ID: String
TWITCH_CLIENT_SECRET: String
ORGANIZATION_NAME: String ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String ORGANIZATION_LOGO: String
APP_COOKIE_SECURE: Boolean! APP_COOKIE_SECURE: Boolean!
@ -2604,6 +2649,8 @@ input UpdateEnvInput {
MICROSOFT_CLIENT_ID: String MICROSOFT_CLIENT_ID: String
MICROSOFT_CLIENT_SECRET: String MICROSOFT_CLIENT_SECRET: String
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: String MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: String
TWITCH_CLIENT_ID: String
TWITCH_CLIENT_SECRET: String
ORGANIZATION_NAME: String ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String ORGANIZATION_LOGO: String
DEFAULT_AUTHORIZE_RESPONSE_TYPE: String DEFAULT_AUTHORIZE_RESPONSE_TYPE: String
@ -2870,7 +2917,7 @@ input VerifyOTPRequest {
email: String email: String
phone_number: String phone_number: String
otp: String! otp: String!
totp: Boolean is_totp: Boolean
# state is used for authorization code grant flow # state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login # it is used to get code for an on-going auth process during login
# and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token # and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
@ -6833,6 +6880,88 @@ func (ec *executionContext) fieldContext_Env_MICROSOFT_ACTIVE_DIRECTORY_TENANT_I
return fc, nil return fc, nil
} }
func (ec *executionContext) _Env_TWITCH_CLIENT_ID(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Env_TWITCH_CLIENT_ID(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.TwitchClientID, 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) fieldContext_Env_TWITCH_CLIENT_ID(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Env",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Env_TWITCH_CLIENT_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Env_TWITCH_CLIENT_SECRET(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.TwitchClientSecret, 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) fieldContext_Env_TWITCH_CLIENT_SECRET(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Env",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Env_ORGANIZATION_NAME(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { func (ec *executionContext) _Env_ORGANIZATION_NAME(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Env_ORGANIZATION_NAME(ctx, field) fc, err := ec.fieldContext_Env_ORGANIZATION_NAME(ctx, field)
if err != nil { if err != nil {
@ -7954,6 +8083,50 @@ func (ec *executionContext) fieldContext_Meta_is_microsoft_login_enabled(ctx con
return fc, nil return fc, nil
} }
func (ec *executionContext) _Meta_is_twitch_login_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Meta_is_twitch_login_enabled(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.IsTwitchLoginEnabled, 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) fieldContext_Meta_is_twitch_login_enabled(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Meta",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Meta_is_email_verification_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) { func (ec *executionContext) _Meta_is_email_verification_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Meta_is_email_verification_enabled(ctx, field) fc, err := ec.fieldContext_Meta_is_email_verification_enabled(ctx, field)
if err != nil { if err != nil {
@ -8218,6 +8391,94 @@ func (ec *executionContext) fieldContext_Meta_is_multi_factor_auth_enabled(ctx c
return fc, nil return fc, nil
} }
func (ec *executionContext) _Meta_is_mobile_basic_authentication_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Meta_is_mobile_basic_authentication_enabled(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.IsMobileBasicAuthenticationEnabled, 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) fieldContext_Meta_is_mobile_basic_authentication_enabled(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Meta",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Meta_is_phone_verification_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Meta_is_phone_verification_enabled(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.IsPhoneVerificationEnabled, 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) fieldContext_Meta_is_phone_verification_enabled(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Meta",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Mutation_signup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_signup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_signup(ctx, field) fc, err := ec.fieldContext_Mutation_signup(ctx, field)
if err != nil { if err != nil {
@ -10484,6 +10745,8 @@ func (ec *executionContext) fieldContext_Query_meta(ctx context.Context, field g
return ec.fieldContext_Meta_is_twitter_login_enabled(ctx, field) return ec.fieldContext_Meta_is_twitter_login_enabled(ctx, field)
case "is_microsoft_login_enabled": case "is_microsoft_login_enabled":
return ec.fieldContext_Meta_is_microsoft_login_enabled(ctx, field) return ec.fieldContext_Meta_is_microsoft_login_enabled(ctx, field)
case "is_twitch_login_enabled":
return ec.fieldContext_Meta_is_twitch_login_enabled(ctx, field)
case "is_email_verification_enabled": case "is_email_verification_enabled":
return ec.fieldContext_Meta_is_email_verification_enabled(ctx, field) return ec.fieldContext_Meta_is_email_verification_enabled(ctx, field)
case "is_basic_authentication_enabled": case "is_basic_authentication_enabled":
@ -10496,6 +10759,10 @@ func (ec *executionContext) fieldContext_Query_meta(ctx context.Context, field g
return ec.fieldContext_Meta_is_strong_password_enabled(ctx, field) return ec.fieldContext_Meta_is_strong_password_enabled(ctx, field)
case "is_multi_factor_auth_enabled": case "is_multi_factor_auth_enabled":
return ec.fieldContext_Meta_is_multi_factor_auth_enabled(ctx, field) return ec.fieldContext_Meta_is_multi_factor_auth_enabled(ctx, field)
case "is_mobile_basic_authentication_enabled":
return ec.fieldContext_Meta_is_mobile_basic_authentication_enabled(ctx, field)
case "is_phone_verification_enabled":
return ec.fieldContext_Meta_is_phone_verification_enabled(ctx, field)
} }
return nil, fmt.Errorf("no field named %q was found under type Meta", field.Name) return nil, fmt.Errorf("no field named %q was found under type Meta", field.Name)
}, },
@ -11208,6 +11475,10 @@ func (ec *executionContext) fieldContext_Query__env(ctx context.Context, field g
return ec.fieldContext_Env_MICROSOFT_CLIENT_SECRET(ctx, field) return ec.fieldContext_Env_MICROSOFT_CLIENT_SECRET(ctx, field)
case "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID": case "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID":
return ec.fieldContext_Env_MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID(ctx, field) return ec.fieldContext_Env_MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID(ctx, field)
case "TWITCH_CLIENT_ID":
return ec.fieldContext_Env_TWITCH_CLIENT_ID(ctx, field)
case "TWITCH_CLIENT_SECRET":
return ec.fieldContext_Env_TWITCH_CLIENT_SECRET(ctx, field)
case "ORGANIZATION_NAME": case "ORGANIZATION_NAME":
return ec.fieldContext_Env_ORGANIZATION_NAME(ctx, field) return ec.fieldContext_Env_ORGANIZATION_NAME(ctx, field)
case "ORGANIZATION_LOGO": case "ORGANIZATION_LOGO":
@ -17715,7 +17986,7 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"ACCESS_TOKEN_EXPIRY_TIME", "ADMIN_SECRET", "CUSTOM_ACCESS_TOKEN_SCRIPT", "OLD_ADMIN_SECRET", "SMTP_HOST", "SMTP_PORT", "SMTP_USERNAME", "SMTP_PASSWORD", "SMTP_LOCAL_NAME", "SENDER_EMAIL", "SENDER_NAME", "JWT_TYPE", "JWT_SECRET", "JWT_PRIVATE_KEY", "JWT_PUBLIC_KEY", "ALLOWED_ORIGINS", "APP_URL", "RESET_PASSWORD_URL", "APP_COOKIE_SECURE", "ADMIN_COOKIE_SECURE", "DISABLE_EMAIL_VERIFICATION", "DISABLE_BASIC_AUTHENTICATION", "DISABLE_MAGIC_LINK_LOGIN", "DISABLE_LOGIN_PAGE", "DISABLE_SIGN_UP", "DISABLE_REDIS_FOR_ENV", "DISABLE_STRONG_PASSWORD", "DISABLE_MULTI_FACTOR_AUTHENTICATION", "ENFORCE_MULTI_FACTOR_AUTHENTICATION", "ROLES", "PROTECTED_ROLES", "DEFAULT_ROLES", "JWT_ROLE_CLAIM", "GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET", "GITHUB_CLIENT_ID", "GITHUB_CLIENT_SECRET", "FACEBOOK_CLIENT_ID", "FACEBOOK_CLIENT_SECRET", "LINKEDIN_CLIENT_ID", "LINKEDIN_CLIENT_SECRET", "APPLE_CLIENT_ID", "APPLE_CLIENT_SECRET", "TWITTER_CLIENT_ID", "TWITTER_CLIENT_SECRET", "MICROSOFT_CLIENT_ID", "MICROSOFT_CLIENT_SECRET", "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID", "ORGANIZATION_NAME", "ORGANIZATION_LOGO", "DEFAULT_AUTHORIZE_RESPONSE_TYPE", "DEFAULT_AUTHORIZE_RESPONSE_MODE", "DISABLE_PLAYGROUND", "DISABLE_MAIL_OTP_LOGIN", "DISABLE_TOTP_LOGIN"} fieldsInOrder := [...]string{"ACCESS_TOKEN_EXPIRY_TIME", "ADMIN_SECRET", "CUSTOM_ACCESS_TOKEN_SCRIPT", "OLD_ADMIN_SECRET", "SMTP_HOST", "SMTP_PORT", "SMTP_USERNAME", "SMTP_PASSWORD", "SMTP_LOCAL_NAME", "SENDER_EMAIL", "SENDER_NAME", "JWT_TYPE", "JWT_SECRET", "JWT_PRIVATE_KEY", "JWT_PUBLIC_KEY", "ALLOWED_ORIGINS", "APP_URL", "RESET_PASSWORD_URL", "APP_COOKIE_SECURE", "ADMIN_COOKIE_SECURE", "DISABLE_EMAIL_VERIFICATION", "DISABLE_BASIC_AUTHENTICATION", "DISABLE_MAGIC_LINK_LOGIN", "DISABLE_LOGIN_PAGE", "DISABLE_SIGN_UP", "DISABLE_REDIS_FOR_ENV", "DISABLE_STRONG_PASSWORD", "DISABLE_MULTI_FACTOR_AUTHENTICATION", "ENFORCE_MULTI_FACTOR_AUTHENTICATION", "ROLES", "PROTECTED_ROLES", "DEFAULT_ROLES", "JWT_ROLE_CLAIM", "GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET", "GITHUB_CLIENT_ID", "GITHUB_CLIENT_SECRET", "FACEBOOK_CLIENT_ID", "FACEBOOK_CLIENT_SECRET", "LINKEDIN_CLIENT_ID", "LINKEDIN_CLIENT_SECRET", "APPLE_CLIENT_ID", "APPLE_CLIENT_SECRET", "TWITTER_CLIENT_ID", "TWITTER_CLIENT_SECRET", "MICROSOFT_CLIENT_ID", "MICROSOFT_CLIENT_SECRET", "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID", "TWITCH_CLIENT_ID", "TWITCH_CLIENT_SECRET", "ORGANIZATION_NAME", "ORGANIZATION_LOGO", "DEFAULT_AUTHORIZE_RESPONSE_TYPE", "DEFAULT_AUTHORIZE_RESPONSE_MODE", "DISABLE_PLAYGROUND", "DISABLE_MAIL_OTP_LOGIN", "DISABLE_TOTP_LOGIN"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@ -18154,6 +18425,24 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
return it, err return it, err
} }
it.MicrosoftActiveDirectoryTenantID = data it.MicrosoftActiveDirectoryTenantID = data
case "TWITCH_CLIENT_ID":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("TWITCH_CLIENT_ID"))
data, err := ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
it.TwitchClientID = data
case "TWITCH_CLIENT_SECRET":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("TWITCH_CLIENT_SECRET"))
data, err := ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
it.TwitchClientSecret = data
case "ORGANIZATION_NAME": case "ORGANIZATION_NAME":
var err error var err error
@ -18719,7 +19008,7 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context,
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"email", "phone_number", "otp", "totp", "state"} fieldsInOrder := [...]string{"email", "phone_number", "otp", "is_totp", "state"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@ -18753,15 +19042,15 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context,
return it, err return it, err
} }
it.Otp = data it.Otp = data
case "totp": case "is_totp":
var err error var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("totp")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("is_totp"))
data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
it.Totp = data it.IsTotp = data
case "state": case "state":
var err error var err error
@ -19136,6 +19425,10 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj
out.Values[i] = ec._Env_MICROSOFT_CLIENT_SECRET(ctx, field, obj) out.Values[i] = ec._Env_MICROSOFT_CLIENT_SECRET(ctx, field, obj)
case "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID": case "MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID":
out.Values[i] = ec._Env_MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID(ctx, field, obj) out.Values[i] = ec._Env_MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID(ctx, field, obj)
case "TWITCH_CLIENT_ID":
out.Values[i] = ec._Env_TWITCH_CLIENT_ID(ctx, field, obj)
case "TWITCH_CLIENT_SECRET":
out.Values[i] = ec._Env_TWITCH_CLIENT_SECRET(ctx, field, obj)
case "ORGANIZATION_NAME": case "ORGANIZATION_NAME":
out.Values[i] = ec._Env_ORGANIZATION_NAME(ctx, field, obj) out.Values[i] = ec._Env_ORGANIZATION_NAME(ctx, field, obj)
case "ORGANIZATION_LOGO": case "ORGANIZATION_LOGO":
@ -19376,6 +19669,11 @@ func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "is_twitch_login_enabled":
out.Values[i] = ec._Meta_is_twitch_login_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "is_email_verification_enabled": case "is_email_verification_enabled":
out.Values[i] = ec._Meta_is_email_verification_enabled(ctx, field, obj) out.Values[i] = ec._Meta_is_email_verification_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
@ -19406,6 +19704,16 @@ func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "is_mobile_basic_authentication_enabled":
out.Values[i] = ec._Meta_is_mobile_basic_authentication_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "is_phone_verification_enabled":
out.Values[i] = ec._Meta_is_phone_verification_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }

View File

@ -119,6 +119,8 @@ type Env struct {
MicrosoftClientID *string `json:"MICROSOFT_CLIENT_ID,omitempty"` MicrosoftClientID *string `json:"MICROSOFT_CLIENT_ID,omitempty"`
MicrosoftClientSecret *string `json:"MICROSOFT_CLIENT_SECRET,omitempty"` MicrosoftClientSecret *string `json:"MICROSOFT_CLIENT_SECRET,omitempty"`
MicrosoftActiveDirectoryTenantID *string `json:"MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID,omitempty"` MicrosoftActiveDirectoryTenantID *string `json:"MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID,omitempty"`
TwitchClientID *string `json:"TWITCH_CLIENT_ID,omitempty"`
TwitchClientSecret *string `json:"TWITCH_CLIENT_SECRET,omitempty"`
OrganizationName *string `json:"ORGANIZATION_NAME,omitempty"` OrganizationName *string `json:"ORGANIZATION_NAME,omitempty"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO,omitempty"` OrganizationLogo *string `json:"ORGANIZATION_LOGO,omitempty"`
AppCookieSecure bool `json:"APP_COOKIE_SECURE"` AppCookieSecure bool `json:"APP_COOKIE_SECURE"`
@ -189,21 +191,24 @@ type MagicLinkLoginInput struct {
} }
type Meta struct { type Meta struct {
Version string `json:"version"` Version string `json:"version"`
ClientID string `json:"client_id"` ClientID string `json:"client_id"`
IsGoogleLoginEnabled bool `json:"is_google_login_enabled"` IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"` IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
IsGithubLoginEnabled bool `json:"is_github_login_enabled"` IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
IsLinkedinLoginEnabled bool `json:"is_linkedin_login_enabled"` IsLinkedinLoginEnabled bool `json:"is_linkedin_login_enabled"`
IsAppleLoginEnabled bool `json:"is_apple_login_enabled"` IsAppleLoginEnabled bool `json:"is_apple_login_enabled"`
IsTwitterLoginEnabled bool `json:"is_twitter_login_enabled"` IsTwitterLoginEnabled bool `json:"is_twitter_login_enabled"`
IsMicrosoftLoginEnabled bool `json:"is_microsoft_login_enabled"` IsMicrosoftLoginEnabled bool `json:"is_microsoft_login_enabled"`
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"` IsTwitchLoginEnabled bool `json:"is_twitch_login_enabled"`
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"` IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"` IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
IsSignUpEnabled bool `json:"is_sign_up_enabled"` IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
IsStrongPasswordEnabled bool `json:"is_strong_password_enabled"` IsSignUpEnabled bool `json:"is_sign_up_enabled"`
IsMultiFactorAuthEnabled bool `json:"is_multi_factor_auth_enabled"` IsStrongPasswordEnabled bool `json:"is_strong_password_enabled"`
IsMultiFactorAuthEnabled bool `json:"is_multi_factor_auth_enabled"`
IsMobileBasicAuthenticationEnabled bool `json:"is_mobile_basic_authentication_enabled"`
IsPhoneVerificationEnabled bool `json:"is_phone_verification_enabled"`
} }
type MobileLoginInput struct { type MobileLoginInput struct {
@ -383,6 +388,8 @@ type UpdateEnvInput struct {
MicrosoftClientID *string `json:"MICROSOFT_CLIENT_ID,omitempty"` MicrosoftClientID *string `json:"MICROSOFT_CLIENT_ID,omitempty"`
MicrosoftClientSecret *string `json:"MICROSOFT_CLIENT_SECRET,omitempty"` MicrosoftClientSecret *string `json:"MICROSOFT_CLIENT_SECRET,omitempty"`
MicrosoftActiveDirectoryTenantID *string `json:"MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID,omitempty"` MicrosoftActiveDirectoryTenantID *string `json:"MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID,omitempty"`
TwitchClientID *string `json:"TWITCH_CLIENT_ID,omitempty"`
TwitchClientSecret *string `json:"TWITCH_CLIENT_SECRET,omitempty"`
OrganizationName *string `json:"ORGANIZATION_NAME,omitempty"` OrganizationName *string `json:"ORGANIZATION_NAME,omitempty"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO,omitempty"` OrganizationLogo *string `json:"ORGANIZATION_LOGO,omitempty"`
DefaultAuthorizeResponseType *string `json:"DEFAULT_AUTHORIZE_RESPONSE_TYPE,omitempty"` DefaultAuthorizeResponseType *string `json:"DEFAULT_AUTHORIZE_RESPONSE_TYPE,omitempty"`
@ -510,7 +517,7 @@ type VerifyOTPRequest struct {
Email *string `json:"email,omitempty"` Email *string `json:"email,omitempty"`
PhoneNumber *string `json:"phone_number,omitempty"` PhoneNumber *string `json:"phone_number,omitempty"`
Otp string `json:"otp"` Otp string `json:"otp"`
Totp *bool `json:"totp,omitempty"` IsTotp *bool `json:"is_totp,omitempty"`
State *string `json:"state,omitempty"` State *string `json:"state,omitempty"`
} }

View File

@ -22,12 +22,15 @@ type Meta {
is_apple_login_enabled: Boolean! is_apple_login_enabled: Boolean!
is_twitter_login_enabled: Boolean! is_twitter_login_enabled: Boolean!
is_microsoft_login_enabled: Boolean! is_microsoft_login_enabled: Boolean!
is_twitch_login_enabled: Boolean!
is_email_verification_enabled: Boolean! is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean! is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean! is_magic_link_login_enabled: Boolean!
is_sign_up_enabled: Boolean! is_sign_up_enabled: Boolean!
is_strong_password_enabled: Boolean! is_strong_password_enabled: Boolean!
is_multi_factor_auth_enabled: Boolean! is_multi_factor_auth_enabled: Boolean!
is_mobile_basic_authentication_enabled: Boolean!
is_phone_verification_enabled: Boolean!
} }
type User { type User {
@ -175,6 +178,8 @@ type Env {
MICROSOFT_CLIENT_ID: String MICROSOFT_CLIENT_ID: String
MICROSOFT_CLIENT_SECRET: String MICROSOFT_CLIENT_SECRET: String
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: String MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: String
TWITCH_CLIENT_ID: String
TWITCH_CLIENT_SECRET: String
ORGANIZATION_NAME: String ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String ORGANIZATION_LOGO: String
APP_COOKIE_SECURE: Boolean! APP_COOKIE_SECURE: Boolean!
@ -302,6 +307,8 @@ input UpdateEnvInput {
MICROSOFT_CLIENT_ID: String MICROSOFT_CLIENT_ID: String
MICROSOFT_CLIENT_SECRET: String MICROSOFT_CLIENT_SECRET: String
MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: String MICROSOFT_ACTIVE_DIRECTORY_TENANT_ID: String
TWITCH_CLIENT_ID: String
TWITCH_CLIENT_SECRET: String
ORGANIZATION_NAME: String ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String ORGANIZATION_LOGO: String
DEFAULT_AUTHORIZE_RESPONSE_TYPE: String DEFAULT_AUTHORIZE_RESPONSE_TYPE: String
@ -568,7 +575,7 @@ input VerifyOTPRequest {
email: String email: String
phone_number: String phone_number: String
otp: String! otp: String!
totp: Boolean is_totp: Boolean
# state is used for authorization code grant flow # state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login # it is used to get code for an on-going auth process during login
# and use that code for setting `c_hash` in id_token # and use that code for setting `c_hash` in id_token

View File

@ -11,11 +11,13 @@ import (
"strings" "strings"
"time" "time"
"golang.org/x/oauth2"
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
@ -75,6 +77,8 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user, err = processTwitterUserInfo(ctx, oauthCode, sessionState) user, err = processTwitterUserInfo(ctx, oauthCode, sessionState)
case constants.AuthRecipeMethodMicrosoft: case constants.AuthRecipeMethodMicrosoft:
user, err = processMicrosoftUserInfo(ctx, oauthCode) user, err = processMicrosoftUserInfo(ctx, oauthCode)
case constants.AuthRecipeMethodTwitch:
user, err = processTwitchUserInfo(ctx, oauthCode)
default: default:
log.Info("Invalid oauth provider") log.Info("Invalid oauth provider")
err = fmt.Errorf(`invalid oauth provider`) err = fmt.Errorf(`invalid oauth provider`)
@ -703,3 +707,38 @@ func processMicrosoftUserInfo(ctx context.Context, code string) (*models.User, e
return user, nil return user, nil
} }
// process twitch user information
func processTwitchUserInfo(ctx context.Context, code string) (*models.User, error) {
oauth2Token, err := oauth.OAuthProviders.TwitchConfig.Exchange(ctx, code)
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return nil, fmt.Errorf("invalid twitch exchange code: %s", err.Error())
}
// Extract the ID Token from OAuth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
log.Debug("Failed to extract ID Token from OAuth2 token")
return nil, fmt.Errorf("unable to extract id_token")
}
verifier := oauth.OIDCProviders.TwitchOIDC.Verifier(&oidc.Config{
ClientID: oauth.OAuthProviders.TwitchConfig.ClientID,
SkipIssuerCheck: true,
})
// Parse and verify ID Token payload.
idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil {
log.Debug("Failed to verify ID Token: ", err)
return nil, fmt.Errorf("unable to verify id_token: %s", err.Error())
}
user := &models.User{}
if err := idToken.Claims(&user); err != nil {
log.Debug("Failed to parse ID Token claims: ", err)
return nil, fmt.Errorf("unable to extract claims")
}
return user, nil
}

View File

@ -4,10 +4,12 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/oauth" "github.com/authorizerdev/authorizer/server/oauth"
@ -227,6 +229,24 @@ func OAuthLoginHandler() gin.HandlerFunc {
oauth.OAuthProviders.MicrosoftConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodMicrosoft oauth.OAuthProviders.MicrosoftConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodMicrosoft
url := oauth.OAuthProviders.MicrosoftConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.MicrosoftConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case constants.AuthRecipeMethodTwitch:
if oauth.OAuthProviders.TwitchConfig == nil {
log.Debug("Twitch OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodTwitch)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
"error": "internal server error",
})
return
}
// during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProviders.TwitchConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodTwitch
url := oauth.OAuthProviders.TwitchConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
default: default:
log.Debug("Invalid oauth provider: ", provider) log.Debug("Invalid oauth provider: ", provider)
c.JSON(422, gin.H{ c.JSON(422, gin.H{

View File

@ -74,7 +74,13 @@ func VerifyEmailHandler() gin.HandlerFunc {
now := time.Now().Unix() now := time.Now().Unix()
user.EmailVerifiedAt = &now user.EmailVerifiedAt = &now
isSignUp = true isSignUp = true
db.Provider.UpdateUser(c, user) user, err = db.Provider.UpdateUser(c, user)
if err != nil {
log.Debug("Error updating user: ", err)
errorRes["error"] = err.Error()
utils.HandleRedirectORJsonResponse(c, http.StatusBadRequest, errorRes, generateRedirectURL(redirectURL, errorRes))
return
}
} }
// delete from verification table // delete from verification table
db.Provider.DeleteVerificationRequest(c, verificationRequest) db.Provider.DeleteVerificationRequest(c, verificationRequest)

View File

@ -4,13 +4,16 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"google.golang.org/appengine/log"
facebookOAuth2 "golang.org/x/oauth2/facebook" facebookOAuth2 "golang.org/x/oauth2/facebook"
githubOAuth2 "golang.org/x/oauth2/github" githubOAuth2 "golang.org/x/oauth2/github"
linkedInOAuth2 "golang.org/x/oauth2/linkedin" linkedInOAuth2 "golang.org/x/oauth2/linkedin"
microsoftOAuth2 "golang.org/x/oauth2/microsoft" microsoftOAuth2 "golang.org/x/oauth2/microsoft"
"google.golang.org/appengine/log" twitchOAuth2 "golang.org/x/oauth2/twitch"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
@ -29,12 +32,14 @@ type OAuthProvider struct {
AppleConfig *oauth2.Config AppleConfig *oauth2.Config
TwitterConfig *oauth2.Config TwitterConfig *oauth2.Config
MicrosoftConfig *oauth2.Config MicrosoftConfig *oauth2.Config
TwitchConfig *oauth2.Config
} }
// OIDCProviders is a struct that contains reference all the OpenID providers // OIDCProviders is a struct that contains reference all the OpenID providers
type OIDCProvider struct { type OIDCProvider struct {
GoogleOIDC *oidc.Provider GoogleOIDC *oidc.Provider
MicrosoftOIDC *oidc.Provider MicrosoftOIDC *oidc.Provider
TwitchOIDC *oidc.Provider
} }
var ( var (
@ -198,5 +203,31 @@ func InitOAuth() error {
} }
} }
twitchClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwitchClientID)
if err != nil {
twitchClientID = ""
}
twitchClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwitchClientSecret)
if err != nil {
twitchClientSecret = ""
}
if twitchClientID != "" && twitchClientSecret != "" {
p, err := oidc.NewProvider(ctx, "https://id.twitch.tv/oauth2")
if err != nil {
log.Debugf(ctx, "Error while creating OIDC provider for Twitch: %v", err)
return err
}
OIDCProviders.TwitchOIDC = p
OAuthProviders.TwitchConfig = &oauth2.Config{
ClientID: twitchClientID,
ClientSecret: twitchClientSecret,
RedirectURL: "/oauth_callback/twitch",
Endpoint: twitchOAuth2.Endpoint,
Scopes: []string{oidc.ScopeOpenID},
}
}
return nil return nil
} }

View File

@ -164,7 +164,12 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
if val, ok := store[constants.EnvKeyMicrosoftActiveDirectoryTenantID]; ok { if val, ok := store[constants.EnvKeyMicrosoftActiveDirectoryTenantID]; ok {
res.MicrosoftActiveDirectoryTenantID = refs.NewStringRef(val.(string)) res.MicrosoftActiveDirectoryTenantID = refs.NewStringRef(val.(string))
} }
if val, ok := store[constants.EnvKeyTwitchClientID]; ok {
res.TwitchClientID = refs.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyTwitchClientSecret]; ok {
res.TwitchClientSecret = refs.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyOrganizationName]; ok { if val, ok := store[constants.EnvKeyOrganizationName]; ok {
res.OrganizationName = refs.NewStringRef(val.(string)) res.OrganizationName = refs.NewStringRef(val.(string))
} }

View File

@ -78,7 +78,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
} }
if err != nil { if err != nil {
log.Debug("Failed to get user: ", err) log.Debug("Failed to get user: ", err)
return res, fmt.Errorf(`bad user credentials`) return res, fmt.Errorf(`user not found`)
} }
if user.RevokedTimestamp != nil { if user.RevokedTimestamp != nil {
log.Debug("User access is revoked") log.Debug("User access is revoked")

View File

@ -106,6 +106,16 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
log.Debug("Failed to get Disable Basic Authentication from environment variable", err) log.Debug("Failed to get Disable Basic Authentication from environment variable", err)
isBasicAuthDisabled = true isBasicAuthDisabled = true
} }
isMobileBasicAuthDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMobileBasicAuthentication)
if err != nil {
log.Debug("Failed to get Disable Basic Authentication from environment variable", err)
isMobileBasicAuthDisabled = true
}
isMobileVerificationDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisablePhoneVerification)
if err != nil {
log.Debug("Failed to get Disable Basic Authentication from environment variable", err)
isMobileVerificationDisabled = true
}
isEmailVerificationDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) isEmailVerificationDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification)
if err != nil { if err != nil {
@ -138,21 +148,23 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
} }
metaInfo := model.Meta{ metaInfo := model.Meta{
Version: constants.VERSION, Version: constants.VERSION,
ClientID: clientID, ClientID: clientID,
IsGoogleLoginEnabled: googleClientID != "" && googleClientSecret != "", IsGoogleLoginEnabled: googleClientID != "" && googleClientSecret != "",
IsGithubLoginEnabled: githubClientID != "" && githubClientSecret != "", IsGithubLoginEnabled: githubClientID != "" && githubClientSecret != "",
IsFacebookLoginEnabled: facebookClientID != "" && facebookClientSecret != "", IsFacebookLoginEnabled: facebookClientID != "" && facebookClientSecret != "",
IsLinkedinLoginEnabled: linkedClientID != "" && linkedInClientSecret != "", IsLinkedinLoginEnabled: linkedClientID != "" && linkedInClientSecret != "",
IsAppleLoginEnabled: appleClientID != "" && appleClientSecret != "", IsAppleLoginEnabled: appleClientID != "" && appleClientSecret != "",
IsTwitterLoginEnabled: twitterClientID != "" && twitterClientSecret != "", IsTwitterLoginEnabled: twitterClientID != "" && twitterClientSecret != "",
IsMicrosoftLoginEnabled: microsoftClientID != "" && microsoftClientSecret != "", IsMicrosoftLoginEnabled: microsoftClientID != "" && microsoftClientSecret != "",
IsBasicAuthenticationEnabled: !isBasicAuthDisabled, IsBasicAuthenticationEnabled: !isBasicAuthDisabled,
IsEmailVerificationEnabled: !isEmailVerificationDisabled, IsEmailVerificationEnabled: !isEmailVerificationDisabled,
IsMagicLinkLoginEnabled: !isMagicLinkLoginDisabled, IsMagicLinkLoginEnabled: !isMagicLinkLoginDisabled,
IsSignUpEnabled: !isSignUpDisabled, IsSignUpEnabled: !isSignUpDisabled,
IsStrongPasswordEnabled: !isStrongPasswordDisabled, IsStrongPasswordEnabled: !isStrongPasswordDisabled,
IsMultiFactorAuthEnabled: !isMultiFactorAuthenticationEnabled, IsMultiFactorAuthEnabled: !isMultiFactorAuthenticationEnabled,
IsMobileBasicAuthenticationEnabled: !isMobileBasicAuthDisabled,
IsPhoneVerificationEnabled: !isMobileVerificationDisabled,
} }
return &metaInfo, nil return &metaInfo, nil
} }

View File

@ -73,7 +73,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
} }
isEmailSignup := email != "" isEmailSignup := email != ""
isMobileSignup := phoneNumber != "" isMobileSignup := phoneNumber != ""
if isBasicAuthDisabled { if isBasicAuthDisabled && isEmailSignup {
log.Debug("Basic authentication is disabled") log.Debug("Basic authentication is disabled")
return res, fmt.Errorf(`basic authentication is disabled for this instance`) return res, fmt.Errorf(`basic authentication is disabled for this instance`)
} }
@ -222,12 +222,12 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
log.Debug("Error getting email verification disabled: ", err) log.Debug("Error getting email verification disabled: ", err)
isEmailVerificationDisabled = true isEmailVerificationDisabled = true
} }
if isEmailVerificationDisabled { if isEmailVerificationDisabled && isEmailSignup {
now := time.Now().Unix() now := time.Now().Unix()
user.EmailVerifiedAt = &now user.EmailVerifiedAt = &now
} }
disablePhoneVerification, _ := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisablePhoneVerification) disablePhoneVerification, _ := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisablePhoneVerification)
if disablePhoneVerification { if disablePhoneVerification && isMobileSignup {
now := time.Now().Unix() now := time.Now().Unix()
user.PhoneNumberVerifiedAt = &now user.PhoneNumberVerifiedAt = &now
} }

View File

@ -34,6 +34,7 @@ func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
isCurrentLinkedInLoginEnabled := currentData[constants.EnvKeyLinkedInClientID] != nil && currentData[constants.EnvKeyLinkedInClientSecret] != nil && currentData[constants.EnvKeyLinkedInClientID].(string) != "" && currentData[constants.EnvKeyLinkedInClientSecret].(string) != "" isCurrentLinkedInLoginEnabled := currentData[constants.EnvKeyLinkedInClientID] != nil && currentData[constants.EnvKeyLinkedInClientSecret] != nil && currentData[constants.EnvKeyLinkedInClientID].(string) != "" && currentData[constants.EnvKeyLinkedInClientSecret].(string) != ""
isCurrentTwitterLoginEnabled := currentData[constants.EnvKeyTwitterClientID] != nil && currentData[constants.EnvKeyTwitterClientSecret] != nil && currentData[constants.EnvKeyTwitterClientID].(string) != "" && currentData[constants.EnvKeyTwitterClientSecret].(string) != "" isCurrentTwitterLoginEnabled := currentData[constants.EnvKeyTwitterClientID] != nil && currentData[constants.EnvKeyTwitterClientSecret] != nil && currentData[constants.EnvKeyTwitterClientID].(string) != "" && currentData[constants.EnvKeyTwitterClientSecret].(string) != ""
isCurrentMicrosoftLoginEnabled := currentData[constants.EnvKeyMicrosoftClientID] != nil && currentData[constants.EnvKeyMicrosoftClientSecret] != nil && currentData[constants.EnvKeyMicrosoftClientID].(string) != "" && currentData[constants.EnvKeyMicrosoftClientSecret].(string) != "" isCurrentMicrosoftLoginEnabled := currentData[constants.EnvKeyMicrosoftClientID] != nil && currentData[constants.EnvKeyMicrosoftClientSecret] != nil && currentData[constants.EnvKeyMicrosoftClientID].(string) != "" && currentData[constants.EnvKeyMicrosoftClientSecret].(string) != ""
isCurrentTwitchLoginEnabled := currentData[constants.EnvKeyTwitchClientID] != nil && currentData[constants.EnvKeyTwitchClientSecret] != nil && currentData[constants.EnvKeyTwitchClientID].(string) != "" && currentData[constants.EnvKeyTwitchClientSecret].(string) != ""
isUpdatedBasicAuthEnabled := !updatedData[constants.EnvKeyDisableBasicAuthentication].(bool) isUpdatedBasicAuthEnabled := !updatedData[constants.EnvKeyDisableBasicAuthentication].(bool)
isUpdatedMobileBasicAuthEnabled := !updatedData[constants.EnvKeyDisableMobileBasicAuthentication].(bool) isUpdatedMobileBasicAuthEnabled := !updatedData[constants.EnvKeyDisableMobileBasicAuthentication].(bool)
@ -45,6 +46,7 @@ func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
isUpdatedLinkedInLoginEnabled := updatedData[constants.EnvKeyLinkedInClientID] != nil && updatedData[constants.EnvKeyLinkedInClientSecret] != nil && updatedData[constants.EnvKeyLinkedInClientID].(string) != "" && updatedData[constants.EnvKeyLinkedInClientSecret].(string) != "" isUpdatedLinkedInLoginEnabled := updatedData[constants.EnvKeyLinkedInClientID] != nil && updatedData[constants.EnvKeyLinkedInClientSecret] != nil && updatedData[constants.EnvKeyLinkedInClientID].(string) != "" && updatedData[constants.EnvKeyLinkedInClientSecret].(string) != ""
isUpdatedTwitterLoginEnabled := updatedData[constants.EnvKeyTwitterClientID] != nil && updatedData[constants.EnvKeyTwitterClientSecret] != nil && updatedData[constants.EnvKeyTwitterClientID].(string) != "" && updatedData[constants.EnvKeyTwitterClientSecret].(string) != "" isUpdatedTwitterLoginEnabled := updatedData[constants.EnvKeyTwitterClientID] != nil && updatedData[constants.EnvKeyTwitterClientSecret] != nil && updatedData[constants.EnvKeyTwitterClientID].(string) != "" && updatedData[constants.EnvKeyTwitterClientSecret].(string) != ""
isUpdatedMicrosoftLoginEnabled := updatedData[constants.EnvKeyMicrosoftClientID] != nil && updatedData[constants.EnvKeyMicrosoftClientSecret] != nil && updatedData[constants.EnvKeyMicrosoftClientID].(string) != "" && updatedData[constants.EnvKeyMicrosoftClientSecret].(string) != "" isUpdatedMicrosoftLoginEnabled := updatedData[constants.EnvKeyMicrosoftClientID] != nil && updatedData[constants.EnvKeyMicrosoftClientSecret] != nil && updatedData[constants.EnvKeyMicrosoftClientID].(string) != "" && updatedData[constants.EnvKeyMicrosoftClientSecret].(string) != ""
isUpdatedTwitchLoginEnabled := updatedData[constants.EnvKeyTwitchClientID] != nil && updatedData[constants.EnvKeyTwitchClientSecret] != nil && updatedData[constants.EnvKeyTwitchClientID].(string) != "" && updatedData[constants.EnvKeyTwitchClientSecret].(string) != ""
if isCurrentBasicAuthEnabled && !isUpdatedBasicAuthEnabled { if isCurrentBasicAuthEnabled && !isUpdatedBasicAuthEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodBasicAuth) memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodBasicAuth)
@ -85,6 +87,10 @@ func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
if isCurrentMicrosoftLoginEnabled && !isUpdatedMicrosoftLoginEnabled { if isCurrentMicrosoftLoginEnabled && !isUpdatedMicrosoftLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodMicrosoft) memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodMicrosoft)
} }
if isCurrentTwitchLoginEnabled && !isUpdatedTwitchLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodTwitch)
}
} }
// UpdateEnvResolver is a resolver for update config mutation // UpdateEnvResolver is a resolver for update config mutation

View File

@ -36,27 +36,32 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod
return res, fmt.Errorf(`invalid session: %s`, err.Error()) return res, fmt.Errorf(`invalid session: %s`, err.Error())
} }
if refs.StringValue(params.Email) == "" && refs.StringValue(params.PhoneNumber) == "" { email := strings.TrimSpace(refs.StringValue(params.Email))
phoneNumber := strings.TrimSpace(refs.StringValue(params.PhoneNumber))
if email == "" && phoneNumber == "" {
log.Debug("Email or phone number is required") log.Debug("Email or phone number is required")
return res, fmt.Errorf(`email or phone_number is required`) return res, fmt.Errorf(`email or phone number is required`)
}
currentField := models.FieldNameEmail
if refs.StringValue(params.Email) == "" {
currentField = models.FieldNamePhoneNumber
} }
isEmailVerification := email != ""
isMobileVerification := phoneNumber != ""
// Get user by email or phone number // Get user by email or phone number
var user *models.User var user *models.User
if currentField == models.FieldNameEmail { if isEmailVerification {
user, err = db.Provider.GetUserByEmail(ctx, refs.StringValue(params.Email)) user, err = db.Provider.GetUserByEmail(ctx, refs.StringValue(params.Email))
if err != nil {
log.Debug("Failed to get user by email: ", err)
}
} else { } else {
user, err = db.Provider.GetUserByPhoneNumber(ctx, refs.StringValue(params.PhoneNumber)) user, err = db.Provider.GetUserByPhoneNumber(ctx, refs.StringValue(params.PhoneNumber))
if err != nil {
log.Debug("Failed to get user by phone number: ", err)
}
} }
if user == nil || err != nil { if user == nil || err != nil {
log.Debug("Failed to get user by email or phone number: ", err) return res, fmt.Errorf(`user not found`)
return res, err
} }
// Verify OTP based on TOPT or OTP // Verify OTP based on TOPT or OTP
if refs.BoolValue(params.Totp) { if refs.BoolValue(params.IsTotp) {
status, err := authenticators.Provider.Validate(ctx, params.Otp, user.ID) status, err := authenticators.Provider.Validate(ctx, params.Otp, user.ID)
if err != nil { if err != nil {
log.Debug("Failed to validate totp: ", err) log.Debug("Failed to validate totp: ", err)
@ -64,18 +69,33 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod
} }
if !status { if !status {
log.Debug("Failed to verify otp request: Incorrect value") log.Debug("Failed to verify otp request: Incorrect value")
return res, fmt.Errorf(`invalid otp`) log.Info("Checking if otp is recovery code")
// Check if otp is recovery code
isValidRecoveryCode, err := authenticators.Provider.ValidateRecoveryCode(ctx, params.Otp, user.ID)
if err != nil {
log.Debug("Failed to validate recovery code: ", err)
return nil, fmt.Errorf("error while validating recovery code")
}
if !isValidRecoveryCode {
log.Debug("Failed to verify otp request: Incorrect value")
return res, fmt.Errorf(`invalid otp`)
}
} }
} else { } else {
var otp *models.OTP var otp *models.OTP
if currentField == models.FieldNameEmail { if isEmailVerification {
otp, err = db.Provider.GetOTPByEmail(ctx, refs.StringValue(params.Email)) otp, err = db.Provider.GetOTPByEmail(ctx, refs.StringValue(params.Email))
if err != nil {
log.Debug(`Failed to get otp request for email: `, err.Error())
}
} else { } else {
otp, err = db.Provider.GetOTPByPhoneNumber(ctx, refs.StringValue(params.PhoneNumber)) otp, err = db.Provider.GetOTPByPhoneNumber(ctx, refs.StringValue(params.PhoneNumber))
if err != nil {
log.Debug(`Failed to get otp request for phone number: `, err.Error())
}
} }
if otp == nil && err != nil { if otp == nil && err != nil {
log.Debugf("Failed to get otp request for %s: %s", currentField, err.Error()) return res, fmt.Errorf(`OTP not found`)
return res, fmt.Errorf(`invalid %s: %s`, currentField, err.Error())
} }
if params.Otp != otp.Otp { if params.Otp != otp.Otp {
log.Debug("Failed to verify otp request: Incorrect value") log.Debug("Failed to verify otp request: Incorrect value")
@ -94,10 +114,26 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod
return res, fmt.Errorf(`invalid session: %s`, err.Error()) return res, fmt.Errorf(`invalid session: %s`, err.Error())
} }
isSignUp := user.EmailVerifiedAt == nil && user.PhoneNumberVerifiedAt == nil isSignUp := false
// TODO - Add Login method in DB when we introduce OTP for social media login if user.EmailVerifiedAt == nil && isEmailVerification {
isSignUp = true
now := time.Now().Unix()
user.EmailVerifiedAt = &now
}
if user.PhoneNumberVerifiedAt == nil && isMobileVerification {
isSignUp = true
now := time.Now().Unix()
user.PhoneNumberVerifiedAt = &now
}
if isSignUp {
user, err = db.Provider.UpdateUser(ctx, user)
if err != nil {
log.Debug("Failed to update user: ", err)
return res, err
}
}
loginMethod := constants.AuthRecipeMethodBasicAuth loginMethod := constants.AuthRecipeMethodBasicAuth
if currentField == models.FieldNamePhoneNumber { if isMobileVerification {
loginMethod = constants.AuthRecipeMethodMobileOTP loginMethod = constants.AuthRecipeMethodMobileOTP
} }
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")

View File

@ -98,12 +98,17 @@ func mobileSingupTest(t *testing.T, s TestSetup) {
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.NotEmpty(t, otpRes.Message) assert.NotEmpty(t, otpRes.Message)
// Check if phone number is verified
user, err = db.Provider.GetUserByPhoneNumber(ctx, phoneNumber)
assert.NoError(t, err)
assert.NotNil(t, user)
assert.NotNil(t, user.PhoneNumberVerifiedAt)
res, err = resolvers.SignupResolver(ctx, model.SignUpInput{ res, err = resolvers.SignupResolver(ctx, model.SignUpInput{
PhoneNumber: refs.NewStringRef(phoneNumber), PhoneNumber: refs.NewStringRef(phoneNumber),
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.Error(t, err) assert.Error(t, err, "should throw duplicate error")
assert.Nil(t, res) assert.Nil(t, res)
cleanData("1234567890@authorizer.dev") cleanData("1234567890@authorizer.dev")
}) })

View File

@ -99,9 +99,9 @@ func totpLoginTest(t *testing.T, s TestSetup) {
cookie = strings.TrimSuffix(cookie, ";") cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie) req.Header.Set("Cookie", cookie)
valid, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ valid, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{
Email: &email, Email: &email,
Totp: refs.NewBoolRef(true), IsTotp: refs.NewBoolRef(true),
Otp: code, Otp: code,
}) })
accessToken := valid.AccessToken accessToken := valid.AccessToken
assert.NoError(t, err) assert.NoError(t, err)
@ -147,9 +147,9 @@ func totpLoginTest(t *testing.T, s TestSetup) {
cookie = strings.TrimSuffix(cookie, ";") cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie) req.Header.Set("Cookie", cookie)
valid, err = resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ valid, err = resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{
Otp: code, Otp: code,
Email: &email, Email: &email,
Totp: refs.NewBoolRef(true), IsTotp: refs.NewBoolRef(true),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, *valid.AccessToken) assert.NotNil(t, *valid.AccessToken)

View File

@ -35,7 +35,11 @@ func verifyEmailTest(t *testing.T, s TestSetup) {
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.NotEqual(t, verifyRes.AccessToken, "", "access token should not be empty") assert.NotEqual(t, verifyRes.AccessToken, "", "access token should not be empty")
// Check if phone number is verified
user1, err := db.Provider.GetUserByEmail(ctx, email)
assert.NoError(t, err)
assert.NotNil(t, user1)
assert.NotNil(t, user1.EmailVerifiedAt)
cleanData(email) cleanData(email)
}) })
} }