Merge f6981ef3b2
into ad41bcf792
This commit is contained in:
commit
0860c33920
|
@ -4,6 +4,7 @@ import InputField from '../InputField';
|
|||
import { SwitchInputType } from '../../constants';
|
||||
|
||||
const Features = ({ variables, setVariables }: any) => {
|
||||
// window.alert(variables)
|
||||
return (
|
||||
<div>
|
||||
{' '}
|
||||
|
@ -24,6 +25,8 @@ const Features = ({ variables, setVariables }: any) => {
|
|||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
|
||||
<Flex>
|
||||
<Flex w="100%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Email Verification:</Text>
|
||||
|
@ -97,6 +100,7 @@ const Features = ({ variables, setVariables }: any) => {
|
|||
also ignore the user MFA setting.
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex justifyContent="start" mb={3}>
|
||||
<InputField
|
||||
variables={variables}
|
||||
|
@ -106,6 +110,46 @@ const Features = ({ variables, setVariables }: any) => {
|
|||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{
|
||||
!variables.DISABLE_MULTI_FACTOR_AUTHENTICATION &&
|
||||
<Flex alignItems="center">
|
||||
<Flex w="100%" alignItems="baseline" flexDir="column">
|
||||
<Text fontSize="sm">TOTP:</Text>
|
||||
<Text fontSize="x-small">
|
||||
Note: to enable totp mfa
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex justifyContent="start" mb={3}>
|
||||
<InputField
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={SwitchInputType.DISABLE_TOTP_LOGIN}
|
||||
hasReversedValue
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
}
|
||||
{!variables.DISABLE_MULTI_FACTOR_AUTHENTICATION &&
|
||||
<Flex alignItems="center">
|
||||
<Flex w="100%" alignItems="baseline" flexDir="column">
|
||||
<Text fontSize="sm">EMAIL OTP:</Text>
|
||||
<Text fontSize="x-small">
|
||||
Note: to enable email otp mfa
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex justifyContent="start" mb={3}>
|
||||
<InputField
|
||||
variables={variables}
|
||||
setVariables={setVariables}
|
||||
inputType={SwitchInputType.DISABLE_MAIL_OTP_LOGIN}
|
||||
hasReversedValue
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>}
|
||||
|
||||
<Flex alignItems="center">
|
||||
<Flex w="100%" alignItems="baseline" flexDir="column">
|
||||
<Text fontSize="sm">
|
||||
|
|
|
@ -85,6 +85,8 @@ export const SwitchInputType = {
|
|||
DISABLE_MULTI_FACTOR_AUTHENTICATION: 'DISABLE_MULTI_FACTOR_AUTHENTICATION',
|
||||
ENFORCE_MULTI_FACTOR_AUTHENTICATION: 'ENFORCE_MULTI_FACTOR_AUTHENTICATION',
|
||||
DISABLE_PLAYGROUND: 'DISABLE_PLAYGROUND',
|
||||
DISABLE_TOTP_LOGIN: 'DISABLE_TOTP_LOGIN',
|
||||
DISABLE_MAIL_OTP_LOGIN: 'DISABLE_MAIL_OTP_LOGIN',
|
||||
};
|
||||
|
||||
export const DateInputType = {
|
||||
|
@ -169,6 +171,8 @@ export interface envVarTypes {
|
|||
DEFAULT_AUTHORIZE_RESPONSE_TYPE: string;
|
||||
DEFAULT_AUTHORIZE_RESPONSE_MODE: string;
|
||||
DISABLE_PLAYGROUND: boolean;
|
||||
DISABLE_TOTP_LOGIN: boolean;
|
||||
DISABLE_MAIL_OTP_LOGIN: boolean;
|
||||
}
|
||||
|
||||
export const envSubViews = {
|
||||
|
|
|
@ -74,6 +74,8 @@ export const EnvVariablesQuery = `
|
|||
DEFAULT_AUTHORIZE_RESPONSE_TYPE
|
||||
DEFAULT_AUTHORIZE_RESPONSE_MODE
|
||||
DISABLE_PLAYGROUND
|
||||
DISABLE_TOTP_LOGIN
|
||||
DISABLE_MAIL_OTP_LOGIN
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -94,6 +94,8 @@ const Environment = () => {
|
|||
DEFAULT_AUTHORIZE_RESPONSE_TYPE: '',
|
||||
DEFAULT_AUTHORIZE_RESPONSE_MODE: '',
|
||||
DISABLE_PLAYGROUND: false,
|
||||
DISABLE_TOTP_LOGIN: false,
|
||||
DISABLE_MAIL_OTP_LOGIN: true,
|
||||
});
|
||||
|
||||
const [fieldVisibility, setFieldVisibility] = React.useState<
|
||||
|
|
|
@ -160,6 +160,12 @@ const (
|
|||
// EnvKeyDisableMultiFactorAuthentication is key for env variable DISABLE_MULTI_FACTOR_AUTHENTICATION
|
||||
// this variable is used to completely disable multi factor authentication. It will have no effect on profile preference
|
||||
EnvKeyDisableMultiFactorAuthentication = "DISABLE_MULTI_FACTOR_AUTHENTICATION"
|
||||
// EnvKeyDisableTOTPLogin is key for env variable DISABLE_TOTP_LOGIN
|
||||
// this variable is used to completely disable totp verification
|
||||
EnvKeyDisableTOTPLogin = "DISABLE_TOTP_LOGIN"
|
||||
// EnvKeyDisableMailOTPLogin is key for env variable DISABLE_MAIL_OTP_LOGIN
|
||||
// this variable is used to completely disable totp verification
|
||||
EnvKeyDisableMailOTPLogin = "DISABLE_MAIL_OTP_LOGIN"
|
||||
// EnvKeyDisablePhoneVerification is key for env variable DISABLE_PHONE_VERIFICATION
|
||||
// this variable is used to disable phone verification
|
||||
EnvKeyDisablePhoneVerification = "DISABLE_PHONE_VERIFICATION"
|
||||
|
|
|
@ -3,7 +3,9 @@ package crypto
|
|||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
)
|
||||
|
@ -116,3 +118,24 @@ func AsRSAStr(privateKey *rsa.PrivateKey, publickKey *rsa.PublicKey) (string, st
|
|||
|
||||
return privParsedPem, pubParsedPem, nil
|
||||
}
|
||||
|
||||
func EncryptRSA(message string, key rsa.PublicKey) (string, error) {
|
||||
label := []byte("OAEP Encrypted")
|
||||
rng := rand.Reader
|
||||
ciphertext, err := rsa.EncryptOAEP(sha256.New(), rng, &key, []byte(message), label)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
func DecryptRSA(cipherText string, privateKey rsa.PrivateKey) (string, error) {
|
||||
ct, _ := base64.StdEncoding.DecodeString(cipherText)
|
||||
label := []byte("OAEP Encrypted")
|
||||
rng := rand.Reader
|
||||
plaintext, err := rsa.DecryptOAEP(sha256.New(), rng, &privateKey, ct, label)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(plaintext), nil
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ type User struct {
|
|||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
|
||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
|
||||
AppData *string `json:"app_data" bson:"app_data" cql:"app_data" dynamo:"app_data"`
|
||||
TotpSecret *string `json:"totp_secret" bson:"totp_secret" cql:"totp_secret" dynamo:"totp_secret"`
|
||||
TotpVerified bool `json:"totp_verified" bson:"totp_verified" cql:"totp_verified" dynamo:"totp_verified"`
|
||||
}
|
||||
|
||||
func (user *User) AsAPIUser() *model.User {
|
||||
|
|
70
server/db/providers/arangodb/totp.go
Normal file
70
server/db/providers/arangodb/totp.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package arangodb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
)
|
||||
|
||||
func (p *provider) GenerateTotp(ctx context.Context, id string) (*string, error) {
|
||||
var buf bytes.Buffer
|
||||
//get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
|
||||
// generate totp, TOTP hash is valid for 30 seconds
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "authorizer",
|
||||
AccountName: user.Email,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while genrating totp")
|
||||
}
|
||||
|
||||
// get secret for user
|
||||
secret := key.Secret()
|
||||
|
||||
//generating image for key and encoding to base64 for displaying in frontend
|
||||
img, err := key.Image(200, 200)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while creating qr image for totp")
|
||||
}
|
||||
png.Encode(&buf, img)
|
||||
encodedText := crypto.EncryptB64(buf.String())
|
||||
|
||||
// update user totp secret in db
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
user.TotpSecret = &secret
|
||||
_, err = p.UpdateUser(ctx, user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while updating user's totp secret")
|
||||
}
|
||||
|
||||
return &encodedText, nil
|
||||
}
|
||||
|
||||
func (p *provider) ValidatePasscode(ctx context.Context, passcode string, id string) (bool, error) {
|
||||
// get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
status := totp.Validate(passcode, *user.TotpSecret)
|
||||
if !user.TotpVerified {
|
||||
if status {
|
||||
user.TotpVerified = true
|
||||
p.UpdateUser(ctx, user)
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
|
@ -267,6 +267,14 @@ func NewProvider() (*provider, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// add totp_secret and totp_verified on users table
|
||||
totpTableAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (totp_verified boolean, totp_secret text, app_data text)`, KeySpace, models.Collections.User)
|
||||
err = session.Query(totpTableAlterQuery).Exec()
|
||||
if err != nil {
|
||||
log.Debug("Failed to alter table as column exists: ", err)
|
||||
// return nil, err
|
||||
}
|
||||
|
||||
return &provider{
|
||||
db: session,
|
||||
}, err
|
||||
|
|
70
server/db/providers/cassandradb/totp.go
Normal file
70
server/db/providers/cassandradb/totp.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package cassandradb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
)
|
||||
|
||||
func (p *provider) GenerateTotp(ctx context.Context, id string) (*string, error) {
|
||||
var buf bytes.Buffer
|
||||
//get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
|
||||
// generate totp, TOTP hash is valid for 30 seconds
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "authorizer",
|
||||
AccountName: user.Email,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while genrating totp")
|
||||
}
|
||||
|
||||
// get secret for user
|
||||
secret := key.Secret()
|
||||
|
||||
//generating image for key and encoding to base64 for displaying in frontend
|
||||
img, err := key.Image(200, 200)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while creating qr image for totp")
|
||||
}
|
||||
png.Encode(&buf, img)
|
||||
encodedText := crypto.EncryptB64(buf.String())
|
||||
|
||||
// update user totp secret in db
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
user.TotpSecret = &secret
|
||||
_, err = p.UpdateUser(ctx, user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while updating user's totp secret")
|
||||
}
|
||||
|
||||
return &encodedText, nil
|
||||
}
|
||||
|
||||
func (p *provider) ValidatePasscode(ctx context.Context, passcode string, id string) (bool, error) {
|
||||
// get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
status := totp.Validate(passcode, *user.TotpSecret)
|
||||
if !user.TotpVerified {
|
||||
if status {
|
||||
user.TotpVerified = true
|
||||
p.UpdateUser(ctx, user)
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
|
@ -39,6 +39,7 @@ func (p *provider) AddUser(ctx context.Context, user *models.User) (*models.User
|
|||
|
||||
user.CreatedAt = time.Now().Unix()
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
user.TotpVerified = false
|
||||
|
||||
bytes, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
|
@ -177,13 +178,19 @@ func (p *provider) ListUsers(ctx context.Context, pagination *model.Pagination)
|
|||
// there is no offset in cassandra
|
||||
// so we fetch till limit + offset
|
||||
// and return the results from offset to limit
|
||||
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.User, pagination.Limit+pagination.Offset)
|
||||
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, "+
|
||||
"nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled,"+
|
||||
" created_at, updated_at, totp_verified FROM %s LIMIT %d", KeySpace+"."+models.Collections.User,
|
||||
pagination.Limit+pagination.Offset)
|
||||
scanner := p.db.Query(query).Iter().Scanner()
|
||||
counter := int64(0)
|
||||
for scanner.Next() {
|
||||
if counter >= pagination.Offset {
|
||||
var user models.User
|
||||
err := scanner.Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt)
|
||||
err := scanner.Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods,
|
||||
&user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber,
|
||||
&user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled,
|
||||
&user.CreatedAt, &user.UpdatedAt, &user.TotpVerified)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -200,8 +207,8 @@ func (p *provider) ListUsers(ctx context.Context, pagination *model.Pagination)
|
|||
// GetUserByEmail to get user information from database using email address
|
||||
func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
|
||||
var user models.User
|
||||
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, email)
|
||||
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt)
|
||||
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at, totp_secret, totp_verified, app_data FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, email)
|
||||
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt, &user.TotpSecret, &user.TotpVerified, &user.AppData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -211,8 +218,8 @@ func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.Us
|
|||
// GetUserByID to get user information from database using user ID
|
||||
func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) {
|
||||
var user models.User
|
||||
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1", KeySpace+"."+models.Collections.User, id)
|
||||
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt)
|
||||
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at, totp_secret, totp_verified, app_data FROM %s WHERE id = '%s' LIMIT 1", KeySpace+"."+models.Collections.User, id)
|
||||
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt, &user.TotpSecret, &user.TotpVerified, &user.AppData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
70
server/db/providers/couchbase/totp.go
Normal file
70
server/db/providers/couchbase/totp.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package couchbase
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
)
|
||||
|
||||
func (p *provider) GenerateTotp(ctx context.Context, id string) (*string, error) {
|
||||
var buf bytes.Buffer
|
||||
//get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
|
||||
// generate totp, TOTP hash is valid for 30 seconds
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "authorizer",
|
||||
AccountName: user.Email,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while genrating totp")
|
||||
}
|
||||
|
||||
// get secret for user
|
||||
secret := key.Secret()
|
||||
|
||||
//generating image for key and encoding to base64 for displaying in frontend
|
||||
img, err := key.Image(200, 200)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while creating qr image for totp")
|
||||
}
|
||||
png.Encode(&buf, img)
|
||||
encodedText := crypto.EncryptB64(buf.String())
|
||||
|
||||
// update user totp secret in db
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
user.TotpSecret = &secret
|
||||
_, err = p.UpdateUser(ctx, user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while updating user's totp secret")
|
||||
}
|
||||
|
||||
return &encodedText, nil
|
||||
}
|
||||
|
||||
func (p *provider) ValidatePasscode(ctx context.Context, passcode string, id string) (bool, error) {
|
||||
// get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
status := totp.Validate(passcode, *user.TotpSecret)
|
||||
if !user.TotpVerified {
|
||||
if status {
|
||||
user.TotpVerified = true
|
||||
p.UpdateUser(ctx, user)
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
|
@ -103,7 +103,7 @@ func (p *provider) ListUsers(ctx context.Context, pagination *model.Pagination)
|
|||
// GetUserByEmail to get user information from database using email address
|
||||
func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
|
||||
var user *models.User
|
||||
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1", p.scopeName, models.Collections.User)
|
||||
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at, totp_secret, totp_verified FROM %s.%s WHERE email = $1 LIMIT 1", p.scopeName, models.Collections.User)
|
||||
q, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
Context: ctx,
|
||||
|
@ -122,7 +122,7 @@ func (p *provider) GetUserByEmail(ctx context.Context, email string) (*models.Us
|
|||
// GetUserByID to get user information from database using user ID
|
||||
func (p *provider) GetUserByID(ctx context.Context, id string) (*models.User, error) {
|
||||
var user *models.User
|
||||
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE _id = $1 LIMIT 1", p.scopeName, models.Collections.User)
|
||||
query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at, totp_secret, totp_verified FROM %s.%s WHERE _id = $1 LIMIT 1", p.scopeName, models.Collections.User)
|
||||
q, err := p.db.Query(query, &gocb.QueryOptions{
|
||||
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
|
||||
Context: ctx,
|
||||
|
|
70
server/db/providers/dynamodb/totp.go
Normal file
70
server/db/providers/dynamodb/totp.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package dynamodb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
)
|
||||
|
||||
func (p *provider) GenerateTotp(ctx context.Context, id string) (*string, error) {
|
||||
var buf bytes.Buffer
|
||||
//get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
|
||||
// generate totp, TOTP hash is valid for 30 seconds
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "authorizer",
|
||||
AccountName: user.Email,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while genrating totp")
|
||||
}
|
||||
|
||||
// get secret for user
|
||||
secret := key.Secret()
|
||||
|
||||
//generating image for key and encoding to base64 for displaying in frontend
|
||||
img, err := key.Image(200, 200)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while creating qr image for totp")
|
||||
}
|
||||
png.Encode(&buf, img)
|
||||
encodedText := crypto.EncryptB64(buf.String())
|
||||
|
||||
// update user totp secret in db
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
user.TotpSecret = &secret
|
||||
_, err = p.UpdateUser(ctx, user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while updating user's totp secret")
|
||||
}
|
||||
|
||||
return &encodedText, nil
|
||||
}
|
||||
|
||||
func (p *provider) ValidatePasscode(ctx context.Context, passcode string, id string) (bool, error) {
|
||||
// get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
status := totp.Validate(passcode, *user.TotpSecret)
|
||||
if !user.TotpVerified {
|
||||
if status {
|
||||
user.TotpVerified = true
|
||||
p.UpdateUser(ctx, user)
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
70
server/db/providers/mongodb/totp.go
Normal file
70
server/db/providers/mongodb/totp.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package mongodb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
)
|
||||
|
||||
func (p *provider) GenerateTotp(ctx context.Context, id string) (*string, error) {
|
||||
var buf bytes.Buffer
|
||||
//get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
|
||||
// generate totp, TOTP hash is valid for 30 seconds
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "authorizer",
|
||||
AccountName: user.Email,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while genrating totp")
|
||||
}
|
||||
|
||||
// get secret for user
|
||||
secret := key.Secret()
|
||||
|
||||
//generating image for key and encoding to base64 for displaying in frontend
|
||||
img, err := key.Image(200, 200)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while creating qr image for totp")
|
||||
}
|
||||
png.Encode(&buf, img)
|
||||
encodedText := crypto.EncryptB64(buf.String())
|
||||
|
||||
// update user totp secret in db
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
user.TotpSecret = &secret
|
||||
_, err = p.UpdateUser(ctx, user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while updating user's totp secret")
|
||||
}
|
||||
|
||||
return &encodedText, nil
|
||||
}
|
||||
|
||||
func (p *provider) ValidatePasscode(ctx context.Context, passcode string, id string) (bool, error) {
|
||||
// get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
status := totp.Validate(passcode, *user.TotpSecret)
|
||||
if !user.TotpVerified {
|
||||
if status {
|
||||
user.TotpVerified = true
|
||||
p.UpdateUser(ctx, user)
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
70
server/db/providers/provider_template/totp.go
Normal file
70
server/db/providers/provider_template/totp.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package provider_template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
)
|
||||
|
||||
func (p *provider) GenerateTotp(ctx context.Context, id string) (*string, error) {
|
||||
var buf bytes.Buffer
|
||||
//get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
|
||||
// generate totp, TOTP hash is valid for 30 seconds
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "authorizer",
|
||||
AccountName: user.Email,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while genrating totp")
|
||||
}
|
||||
|
||||
// get secret for user
|
||||
secret := key.Secret()
|
||||
|
||||
//generating image for key and encoding to base64 for displaying in frontend
|
||||
img, err := key.Image(200, 200)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while creating qr image for totp")
|
||||
}
|
||||
png.Encode(&buf, img)
|
||||
encodedText := crypto.EncryptB64(buf.String())
|
||||
|
||||
// update user totp secret in db
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
user.TotpSecret = &secret
|
||||
_, err = p.UpdateUser(ctx, user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while updating user's totp secret")
|
||||
}
|
||||
|
||||
return &encodedText, nil
|
||||
}
|
||||
|
||||
func (p *provider) ValidatePasscode(ctx context.Context, passcode string, id string) (bool, error) {
|
||||
// get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
status := totp.Validate(passcode, *user.TotpSecret)
|
||||
if !user.TotpVerified {
|
||||
if status {
|
||||
user.TotpVerified = true
|
||||
p.UpdateUser(ctx, user)
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
|
@ -2,7 +2,6 @@ package providers
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
)
|
||||
|
@ -88,4 +87,9 @@ type Provider interface {
|
|||
GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error)
|
||||
// DeleteOTP to delete otp
|
||||
DeleteOTP(ctx context.Context, otp *models.OTP) error
|
||||
|
||||
// GenerateTotp to generate totp, store secret into db and returns base64 of QR code image
|
||||
GenerateTotp(ctx context.Context, id string) (*string, error)
|
||||
// ValidatePasscode validate user passcode with secret stored in our db
|
||||
ValidatePasscode(ctx context.Context, passcode string, id string) (bool, error)
|
||||
}
|
||||
|
|
71
server/db/providers/sql/totp.go
Normal file
71
server/db/providers/sql/totp.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package sql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"image/png"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
)
|
||||
|
||||
func (p *provider) GenerateTotp(ctx context.Context, id string) (*string, error) {
|
||||
var buf bytes.Buffer
|
||||
//get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
|
||||
// generate totp, TOTP hash is valid for 30 seconds
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: "authorizer",
|
||||
AccountName: user.Email,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while genrating totp")
|
||||
}
|
||||
|
||||
// get secret for user
|
||||
secret := key.Secret()
|
||||
|
||||
//generating image for key and encoding to base64 for displaying in frontend
|
||||
img, err := key.Image(200, 200)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while creating qr image for totp")
|
||||
}
|
||||
png.Encode(&buf, img)
|
||||
encodedText := crypto.EncryptB64(buf.String())
|
||||
|
||||
// update user totp secret in db
|
||||
user.UpdatedAt = time.Now().Unix()
|
||||
user.TotpSecret = &secret
|
||||
_, err = p.UpdateUser(ctx, user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while updating user's totp secret")
|
||||
}
|
||||
log.Info("\n\n\n", &encodedText)
|
||||
return &encodedText, nil
|
||||
}
|
||||
|
||||
func (p *provider) ValidatePasscode(ctx context.Context, passcode string, id string) (bool, error) {
|
||||
// get user details
|
||||
user, err := p.GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error while getting user details")
|
||||
}
|
||||
status := totp.Validate(passcode, *user.TotpSecret)
|
||||
if !user.TotpVerified {
|
||||
if status {
|
||||
user.TotpVerified = true
|
||||
p.UpdateUser(ctx, user)
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
return status, nil
|
||||
}
|
37
server/env/env.go
vendored
37
server/env/env.go
vendored
|
@ -104,6 +104,8 @@ func InitAllEnv() error {
|
|||
osDisableStrongPassword := os.Getenv(constants.EnvKeyDisableStrongPassword)
|
||||
osEnforceMultiFactorAuthentication := os.Getenv(constants.EnvKeyEnforceMultiFactorAuthentication)
|
||||
osDisableMultiFactorAuthentication := os.Getenv(constants.EnvKeyDisableMultiFactorAuthentication)
|
||||
osDisableTOTPLogin := os.Getenv(constants.EnvKeyDisableTOTPLogin)
|
||||
osDisableMailOTPLogin := os.Getenv(constants.EnvKeyDisableMailOTPLogin)
|
||||
// phone verification var
|
||||
osDisablePhoneVerification := os.Getenv(constants.EnvKeyDisablePhoneVerification)
|
||||
osDisablePlayground := os.Getenv(constants.EnvKeyDisablePlayGround)
|
||||
|
@ -689,20 +691,13 @@ func InitAllEnv() error {
|
|||
envData[constants.EnvKeyDisableEmailVerification] = true
|
||||
envData[constants.EnvKeyDisableMagicLinkLogin] = true
|
||||
envData[constants.EnvKeyIsEmailServiceEnabled] = false
|
||||
envData[constants.EnvKeyDisableMailOTPLogin] = true
|
||||
}
|
||||
|
||||
if envData[constants.EnvKeySmtpHost] != "" && envData[constants.EnvKeySmtpUsername] != "" && envData[constants.EnvKeySmtpPassword] != "" && envData[constants.EnvKeySenderEmail] != "" && envData[constants.EnvKeySmtpPort] != "" {
|
||||
envData[constants.EnvKeyIsEmailServiceEnabled] = true
|
||||
}
|
||||
|
||||
if envData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && !envData[constants.EnvKeyIsEmailServiceEnabled].(bool) && !envData[constants.EnvKeyIsSMSServiceEnabled].(bool) {
|
||||
return errors.New("to enable multi factor authentication, please enable email service")
|
||||
}
|
||||
|
||||
if !envData[constants.EnvKeyIsEmailServiceEnabled].(bool) {
|
||||
envData[constants.EnvKeyDisableMultiFactorAuthentication] = true
|
||||
}
|
||||
|
||||
if envData[constants.EnvKeyDisableEmailVerification].(bool) {
|
||||
envData[constants.EnvKeyDisableMagicLinkLogin] = true
|
||||
}
|
||||
|
@ -840,6 +835,32 @@ func InitAllEnv() error {
|
|||
}
|
||||
}
|
||||
|
||||
if _, ok := envData[constants.EnvKeyDisableTOTPLogin]; !ok {
|
||||
envData[constants.EnvKeyDisableTOTPLogin] = osDisableTOTPLogin == "false"
|
||||
}
|
||||
if osDisableTOTPLogin != "" {
|
||||
boolValue, err := strconv.ParseBool(osDisableTOTPLogin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if boolValue != envData[constants.EnvKeyDisableTOTPLogin].(bool) {
|
||||
envData[constants.EnvKeyDisableTOTPLogin] = boolValue
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := envData[constants.EnvKeyDisableMailOTPLogin]; !ok {
|
||||
envData[constants.EnvKeyDisableMailOTPLogin] = osDisableMailOTPLogin == "true"
|
||||
}
|
||||
if osDisableMailOTPLogin != "" {
|
||||
boolValue, err := strconv.ParseBool(osDisableMailOTPLogin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if boolValue != envData[constants.EnvKeyDisableMailOTPLogin].(bool) {
|
||||
envData[constants.EnvKeyDisableMailOTPLogin] = boolValue
|
||||
}
|
||||
}
|
||||
|
||||
err = memorystore.Provider.UpdateEnvStore(envData)
|
||||
if err != nil {
|
||||
log.Debug("Error while updating env store: ", err)
|
||||
|
|
7
server/env/persist_env.go
vendored
7
server/env/persist_env.go
vendored
|
@ -196,7 +196,7 @@ func PersistEnv() error {
|
|||
envValue := strings.TrimSpace(os.Getenv(key))
|
||||
if envValue != "" {
|
||||
switch key {
|
||||
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableMobileBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyIsSMSServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure, constants.EnvKeyDisablePhoneVerification, constants.EnvKeyDisablePlayGround:
|
||||
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableMobileBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyIsSMSServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure, constants.EnvKeyDisablePhoneVerification, constants.EnvKeyDisablePlayGround, constants.EnvKeyDisableTOTPLogin, constants.EnvKeyDisableMailOTPLogin:
|
||||
if envValueBool, err := strconv.ParseBool(envValue); err == nil {
|
||||
if value.(bool) != envValueBool {
|
||||
storeData[key] = envValueBool
|
||||
|
@ -227,6 +227,11 @@ func PersistEnv() error {
|
|||
storeData[constants.EnvKeyDisableMagicLinkLogin] = true
|
||||
hasChanged = true
|
||||
}
|
||||
|
||||
if !storeData[constants.EnvKeyDisableMailOTPLogin].(bool) {
|
||||
storeData[constants.EnvKeyDisableMailOTPLogin] = true
|
||||
hasChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
err = memorystore.Provider.UpdateEnvStore(storeData)
|
||||
|
|
|
@ -13,6 +13,7 @@ require (
|
|||
github.com/go-playground/validator/v10 v10.11.1 // indirect
|
||||
github.com/goccy/go-json v0.9.11 // indirect
|
||||
github.com/gocql/gocql v1.2.0
|
||||
github.com/gokyle/twofactor v1.0.1
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
|
@ -21,10 +22,12 @@ require (
|
|||
github.com/joho/godotenv v1.3.0
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/redis/go-redis/v9 v9.0.3
|
||||
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/tuotoo/qrcode v0.0.0-20220425170535-52ccc2bebf5d
|
||||
github.com/twilio/twilio-go v1.7.2
|
||||
github.com/vektah/gqlparser/v2 v2.5.1
|
||||
go.mongodb.org/mongo-driver v1.8.1
|
||||
|
|
|
@ -56,8 +56,12 @@ github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8
|
|||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
|
||||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
|
||||
github.com/bits-and-blooms/bitset v1.2.1 h1:M+/hrU9xlMp7t4TyTDQW97d3tRPVuKFC6zBEK16QnXY=
|
||||
github.com/bits-and-blooms/bitset v1.2.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
|
@ -129,6 +133,8 @@ github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
|||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gocql/gocql v1.2.0 h1:TZhsCd7fRuye4VyHr3WCvWwIQaZUmjsqnSIXK9FcVCE=
|
||||
github.com/gocql/gocql v1.2.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8=
|
||||
github.com/gokyle/twofactor v1.0.1 h1:uRhvx0S4Hb82RPIDALnf7QxbmPL49LyyaCkJDpWx+Ek=
|
||||
github.com/gokyle/twofactor v1.0.1/go.mod h1:4gxzH1eaE/F3Pct/sCDNOylP0ClofUO5j4XZN9tKtLE=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
|
@ -254,6 +260,8 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
|
|||
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q=
|
||||
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw=
|
||||
github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
|
||||
github.com/maruel/rs v1.1.0 h1:dh4OceAF5yD06EASOrb+DS358LI4g0B90YApSdjCP6U=
|
||||
github.com/maruel/rs v1.1.0/go.mod h1:vzwMjzSJJxLIXmU62qHj6O5QRn5kvCKxFrfaFCxBcUY=
|
||||
github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
|
@ -285,6 +293,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k=
|
||||
github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||
|
@ -319,6 +329,8 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
|
|||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tuotoo/qrcode v0.0.0-20220425170535-52ccc2bebf5d h1:4x1FeGJRB00cvxnKXnRJDT89fvG/Lzm2ecm0vlr/qDs=
|
||||
github.com/tuotoo/qrcode v0.0.0-20220425170535-52ccc2bebf5d/go.mod h1:uSELzeIcTceNCgzbKdJuJa0ouCqqtkyzL+6bnA3rM+M=
|
||||
github.com/twilio/twilio-go v1.7.2 h1:tX38DXbSuDWWIK+tKAE2AJSMR6d8i7lf9ksY8J29VLE=
|
||||
github.com/twilio/twilio-go v1.7.2/go.mod h1:tdnfQ5TjbewoAu4lf9bMsGvfuJ/QU9gYuv9yx3TSIXU=
|
||||
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
|
||||
|
@ -743,5 +755,7 @@ modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.6.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
|
|
@ -52,6 +52,8 @@ type ComplexityRoot struct {
|
|||
RefreshToken func(childComplexity int) int
|
||||
ShouldShowEmailOtpScreen func(childComplexity int) int
|
||||
ShouldShowMobileOtpScreen func(childComplexity int) int
|
||||
TotpBase64URL func(childComplexity int) int
|
||||
TotpToken func(childComplexity int) int
|
||||
User func(childComplexity int) int
|
||||
}
|
||||
|
||||
|
@ -96,11 +98,13 @@ type ComplexityRoot struct {
|
|||
DisableEmailVerification func(childComplexity int) int
|
||||
DisableLoginPage func(childComplexity int) int
|
||||
DisableMagicLinkLogin func(childComplexity int) int
|
||||
DisableMailOtpLogin func(childComplexity int) int
|
||||
DisableMultiFactorAuthentication func(childComplexity int) int
|
||||
DisablePlayground func(childComplexity int) int
|
||||
DisableRedisForEnv func(childComplexity int) int
|
||||
DisableSignUp func(childComplexity int) int
|
||||
DisableStrongPassword func(childComplexity int) int
|
||||
DisableTotpLogin func(childComplexity int) int
|
||||
EnforceMultiFactorAuthentication func(childComplexity int) int
|
||||
FacebookClientID func(childComplexity int) int
|
||||
FacebookClientSecret func(childComplexity int) int
|
||||
|
@ -201,6 +205,7 @@ type ComplexityRoot struct {
|
|||
UpdateWebhook func(childComplexity int, params model.UpdateWebhookRequest) int
|
||||
VerifyEmail func(childComplexity int, params model.VerifyEmailInput) int
|
||||
VerifyOtp func(childComplexity int, params model.VerifyOTPRequest) int
|
||||
VerifyTotp func(childComplexity int, params model.VerifyTOTPRequest) int
|
||||
}
|
||||
|
||||
Pagination struct {
|
||||
|
@ -265,6 +270,7 @@ type ComplexityRoot struct {
|
|||
RevokedTimestamp func(childComplexity int) int
|
||||
Roles func(childComplexity int) int
|
||||
SignupMethods func(childComplexity int) int
|
||||
TotpVerified func(childComplexity int) int
|
||||
UpdatedAt func(childComplexity int) int
|
||||
}
|
||||
|
||||
|
@ -347,6 +353,7 @@ type MutationResolver interface {
|
|||
Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error)
|
||||
VerifyOtp(ctx context.Context, params model.VerifyOTPRequest) (*model.AuthResponse, error)
|
||||
ResendOtp(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error)
|
||||
VerifyTotp(ctx context.Context, params model.VerifyTOTPRequest) (*model.AuthResponse, error)
|
||||
DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error)
|
||||
UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error)
|
||||
AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error)
|
||||
|
@ -446,6 +453,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.AuthResponse.ShouldShowMobileOtpScreen(childComplexity), true
|
||||
|
||||
case "AuthResponse.totp_base64_url":
|
||||
if e.complexity.AuthResponse.TotpBase64URL == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.AuthResponse.TotpBase64URL(childComplexity), true
|
||||
|
||||
case "AuthResponse.totp_token":
|
||||
if e.complexity.AuthResponse.TotpToken == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.AuthResponse.TotpToken(childComplexity), true
|
||||
|
||||
case "AuthResponse.user":
|
||||
if e.complexity.AuthResponse.User == nil {
|
||||
break
|
||||
|
@ -691,6 +712,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.Env.DisableMagicLinkLogin(childComplexity), true
|
||||
|
||||
case "Env.DISABLE_MAIL_OTP_LOGIN":
|
||||
if e.complexity.Env.DisableMailOtpLogin == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Env.DisableMailOtpLogin(childComplexity), true
|
||||
|
||||
case "Env.DISABLE_MULTI_FACTOR_AUTHENTICATION":
|
||||
if e.complexity.Env.DisableMultiFactorAuthentication == nil {
|
||||
break
|
||||
|
@ -726,6 +754,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.Env.DisableStrongPassword(childComplexity), true
|
||||
|
||||
case "Env.DISABLE_TOTP_LOGIN":
|
||||
if e.complexity.Env.DisableTotpLogin == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Env.DisableTotpLogin(childComplexity), true
|
||||
|
||||
case "Env.ENFORCE_MULTI_FACTOR_AUTHENTICATION":
|
||||
if e.complexity.Env.EnforceMultiFactorAuthentication == nil {
|
||||
break
|
||||
|
@ -1466,6 +1501,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.Mutation.VerifyOtp(childComplexity, args["params"].(model.VerifyOTPRequest)), true
|
||||
|
||||
case "Mutation.verify_totp":
|
||||
if e.complexity.Mutation.VerifyTotp == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_verify_totp_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.VerifyTotp(childComplexity, args["params"].(model.VerifyTOTPRequest)), true
|
||||
|
||||
case "Pagination.limit":
|
||||
if e.complexity.Pagination.Limit == nil {
|
||||
break
|
||||
|
@ -1838,6 +1885,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.User.SignupMethods(childComplexity), true
|
||||
|
||||
case "User.totp_verified":
|
||||
if e.complexity.User.TotpVerified == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.User.TotpVerified(childComplexity), true
|
||||
|
||||
case "User.updated_at":
|
||||
if e.complexity.User.UpdatedAt == nil {
|
||||
break
|
||||
|
@ -2139,6 +2193,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
|
|||
ec.unmarshalInputValidateSessionInput,
|
||||
ec.unmarshalInputVerifyEmailInput,
|
||||
ec.unmarshalInputVerifyOTPRequest,
|
||||
ec.unmarshalInputVerifyTOTPRequest,
|
||||
ec.unmarshalInputWebhookRequest,
|
||||
)
|
||||
first := true
|
||||
|
@ -2254,6 +2309,7 @@ type User {
|
|||
revoked_timestamp: Int64
|
||||
is_multi_factor_auth_enabled: Boolean
|
||||
app_data: Map
|
||||
totp_verified: Boolean
|
||||
}
|
||||
|
||||
type Users {
|
||||
|
@ -2301,6 +2357,8 @@ type AuthResponse {
|
|||
refresh_token: String
|
||||
expires_in: Int64
|
||||
user: User
|
||||
totp_base64_url: String
|
||||
totp_token: String
|
||||
}
|
||||
|
||||
type Response {
|
||||
|
@ -2375,6 +2433,8 @@ type Env {
|
|||
DEFAULT_AUTHORIZE_RESPONSE_TYPE: String
|
||||
DEFAULT_AUTHORIZE_RESPONSE_MODE: String
|
||||
DISABLE_PLAYGROUND: Boolean!
|
||||
DISABLE_MAIL_OTP_LOGIN: Boolean!
|
||||
DISABLE_TOTP_LOGIN: Boolean!
|
||||
}
|
||||
|
||||
type ValidateJWTTokenResponse {
|
||||
|
@ -2498,6 +2558,8 @@ input UpdateEnvInput {
|
|||
DEFAULT_AUTHORIZE_RESPONSE_TYPE: String
|
||||
DEFAULT_AUTHORIZE_RESPONSE_MODE: String
|
||||
DISABLE_PLAYGROUND: Boolean
|
||||
DISABLE_MAIL_OTP_LOGIN: Boolean
|
||||
DISABLE_TOTP_LOGIN: Boolean
|
||||
}
|
||||
|
||||
input AdminLoginInput {
|
||||
|
@ -2760,6 +2822,12 @@ input VerifyOTPRequest {
|
|||
state: String
|
||||
}
|
||||
|
||||
input VerifyTOTPRequest {
|
||||
otp: String!
|
||||
token: String!
|
||||
state: String
|
||||
}
|
||||
|
||||
input ResendOTPRequest {
|
||||
email: String
|
||||
phone_number: String
|
||||
|
@ -2789,6 +2857,7 @@ type Mutation {
|
|||
revoke(params: OAuthRevokeInput!): Response!
|
||||
verify_otp(params: VerifyOTPRequest!): AuthResponse!
|
||||
resend_otp(params: ResendOTPRequest!): Response!
|
||||
verify_totp(params: VerifyTOTPRequest!): AuthResponse!
|
||||
# admin only apis
|
||||
_delete_user(params: DeleteUserInput!): Response!
|
||||
_update_user(params: UpdateUserInput!): User!
|
||||
|
@ -3269,6 +3338,21 @@ func (ec *executionContext) field_Mutation_verify_otp_args(ctx context.Context,
|
|||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_verify_totp_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
var arg0 model.VerifyTOTPRequest
|
||||
if tmp, ok := rawArgs["params"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
|
||||
arg0, err = ec.unmarshalNVerifyTOTPRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerifyTOTPRequest(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["params"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
|
@ -3838,6 +3922,8 @@ func (ec *executionContext) fieldContext_AuthResponse_user(ctx context.Context,
|
|||
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
|
||||
case "app_data":
|
||||
return ec.fieldContext_User_app_data(ctx, field)
|
||||
case "totp_verified":
|
||||
return ec.fieldContext_User_totp_verified(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
|
||||
},
|
||||
|
@ -3845,6 +3931,88 @@ func (ec *executionContext) fieldContext_AuthResponse_user(ctx context.Context,
|
|||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _AuthResponse_totp_base64_url(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_AuthResponse_totp_base64_url(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.TotpBase64URL, 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_AuthResponse_totp_base64_url(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "AuthResponse",
|
||||
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) _AuthResponse_totp_token(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_AuthResponse_totp_token(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.TotpToken, 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_AuthResponse_totp_token(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "AuthResponse",
|
||||
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) _EmailTemplate_id(ctx context.Context, field graphql.CollectedField, obj *model.EmailTemplate) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_EmailTemplate_id(ctx, field)
|
||||
if err != nil {
|
||||
|
@ -6845,6 +7013,94 @@ func (ec *executionContext) fieldContext_Env_DISABLE_PLAYGROUND(ctx context.Cont
|
|||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Env_DISABLE_MAIL_OTP_LOGIN(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Env_DISABLE_MAIL_OTP_LOGIN(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.DisableMailOtpLogin, 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_Env_DISABLE_MAIL_OTP_LOGIN(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 Boolean does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Env_DISABLE_TOTP_LOGIN(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Env_DISABLE_TOTP_LOGIN(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.DisableTotpLogin, 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_Env_DISABLE_TOTP_LOGIN(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 Boolean does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Error_message(ctx context.Context, field graphql.CollectedField, obj *model.Error) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Error_message(ctx, field)
|
||||
if err != nil {
|
||||
|
@ -7179,6 +7435,8 @@ func (ec *executionContext) fieldContext_InviteMembersResponse_Users(ctx context
|
|||
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
|
||||
case "app_data":
|
||||
return ec.fieldContext_User_app_data(ctx, field)
|
||||
case "totp_verified":
|
||||
return ec.fieldContext_User_totp_verified(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
|
||||
},
|
||||
|
@ -7901,6 +8159,10 @@ func (ec *executionContext) fieldContext_Mutation_signup(ctx context.Context, fi
|
|||
return ec.fieldContext_AuthResponse_expires_in(ctx, field)
|
||||
case "user":
|
||||
return ec.fieldContext_AuthResponse_user(ctx, field)
|
||||
case "totp_base64_url":
|
||||
return ec.fieldContext_AuthResponse_totp_base64_url(ctx, field)
|
||||
case "totp_token":
|
||||
return ec.fieldContext_AuthResponse_totp_token(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name)
|
||||
},
|
||||
|
@ -7974,6 +8236,10 @@ func (ec *executionContext) fieldContext_Mutation_mobile_signup(ctx context.Cont
|
|||
return ec.fieldContext_AuthResponse_expires_in(ctx, field)
|
||||
case "user":
|
||||
return ec.fieldContext_AuthResponse_user(ctx, field)
|
||||
case "totp_base64_url":
|
||||
return ec.fieldContext_AuthResponse_totp_base64_url(ctx, field)
|
||||
case "totp_token":
|
||||
return ec.fieldContext_AuthResponse_totp_token(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name)
|
||||
},
|
||||
|
@ -8047,6 +8313,10 @@ func (ec *executionContext) fieldContext_Mutation_login(ctx context.Context, fie
|
|||
return ec.fieldContext_AuthResponse_expires_in(ctx, field)
|
||||
case "user":
|
||||
return ec.fieldContext_AuthResponse_user(ctx, field)
|
||||
case "totp_base64_url":
|
||||
return ec.fieldContext_AuthResponse_totp_base64_url(ctx, field)
|
||||
case "totp_token":
|
||||
return ec.fieldContext_AuthResponse_totp_token(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name)
|
||||
},
|
||||
|
@ -8120,6 +8390,10 @@ func (ec *executionContext) fieldContext_Mutation_mobile_login(ctx context.Conte
|
|||
return ec.fieldContext_AuthResponse_expires_in(ctx, field)
|
||||
case "user":
|
||||
return ec.fieldContext_AuthResponse_user(ctx, field)
|
||||
case "totp_base64_url":
|
||||
return ec.fieldContext_AuthResponse_totp_base64_url(ctx, field)
|
||||
case "totp_token":
|
||||
return ec.fieldContext_AuthResponse_totp_token(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name)
|
||||
},
|
||||
|
@ -8359,6 +8633,10 @@ func (ec *executionContext) fieldContext_Mutation_verify_email(ctx context.Conte
|
|||
return ec.fieldContext_AuthResponse_expires_in(ctx, field)
|
||||
case "user":
|
||||
return ec.fieldContext_AuthResponse_user(ctx, field)
|
||||
case "totp_base64_url":
|
||||
return ec.fieldContext_AuthResponse_totp_base64_url(ctx, field)
|
||||
case "totp_token":
|
||||
return ec.fieldContext_AuthResponse_totp_token(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name)
|
||||
},
|
||||
|
@ -8668,6 +8946,10 @@ func (ec *executionContext) fieldContext_Mutation_verify_otp(ctx context.Context
|
|||
return ec.fieldContext_AuthResponse_expires_in(ctx, field)
|
||||
case "user":
|
||||
return ec.fieldContext_AuthResponse_user(ctx, field)
|
||||
case "totp_base64_url":
|
||||
return ec.fieldContext_AuthResponse_totp_base64_url(ctx, field)
|
||||
case "totp_token":
|
||||
return ec.fieldContext_AuthResponse_totp_token(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name)
|
||||
},
|
||||
|
@ -8745,6 +9027,83 @@ func (ec *executionContext) fieldContext_Mutation_resend_otp(ctx context.Context
|
|||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_verify_totp(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation_verify_totp(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 ec.resolvers.Mutation().VerifyTotp(rctx, fc.Args["params"].(model.VerifyTOTPRequest))
|
||||
})
|
||||
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.(*model.AuthResponse)
|
||||
fc.Result = res
|
||||
return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Mutation_verify_totp(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Mutation",
|
||||
Field: field,
|
||||
IsMethod: true,
|
||||
IsResolver: true,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
switch field.Name {
|
||||
case "message":
|
||||
return ec.fieldContext_AuthResponse_message(ctx, field)
|
||||
case "should_show_email_otp_screen":
|
||||
return ec.fieldContext_AuthResponse_should_show_email_otp_screen(ctx, field)
|
||||
case "should_show_mobile_otp_screen":
|
||||
return ec.fieldContext_AuthResponse_should_show_mobile_otp_screen(ctx, field)
|
||||
case "access_token":
|
||||
return ec.fieldContext_AuthResponse_access_token(ctx, field)
|
||||
case "id_token":
|
||||
return ec.fieldContext_AuthResponse_id_token(ctx, field)
|
||||
case "refresh_token":
|
||||
return ec.fieldContext_AuthResponse_refresh_token(ctx, field)
|
||||
case "expires_in":
|
||||
return ec.fieldContext_AuthResponse_expires_in(ctx, field)
|
||||
case "user":
|
||||
return ec.fieldContext_AuthResponse_user(ctx, field)
|
||||
case "totp_base64_url":
|
||||
return ec.fieldContext_AuthResponse_totp_base64_url(ctx, field)
|
||||
case "totp_token":
|
||||
return ec.fieldContext_AuthResponse_totp_token(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name)
|
||||
},
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = ec.Recover(ctx, r)
|
||||
ec.Error(ctx, err)
|
||||
}
|
||||
}()
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
if fc.Args, err = ec.field_Mutation_verify_totp_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation__delete_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation__delete_user(ctx, field)
|
||||
if err != nil {
|
||||
|
@ -8883,6 +9242,8 @@ func (ec *executionContext) fieldContext_Mutation__update_user(ctx context.Conte
|
|||
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
|
||||
case "app_data":
|
||||
return ec.fieldContext_User_app_data(ctx, field)
|
||||
case "totp_verified":
|
||||
return ec.fieldContext_User_totp_verified(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
|
||||
},
|
||||
|
@ -10090,6 +10451,10 @@ func (ec *executionContext) fieldContext_Query_session(ctx context.Context, fiel
|
|||
return ec.fieldContext_AuthResponse_expires_in(ctx, field)
|
||||
case "user":
|
||||
return ec.fieldContext_AuthResponse_user(ctx, field)
|
||||
case "totp_base64_url":
|
||||
return ec.fieldContext_AuthResponse_totp_base64_url(ctx, field)
|
||||
case "totp_token":
|
||||
return ec.fieldContext_AuthResponse_totp_token(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name)
|
||||
},
|
||||
|
@ -10187,6 +10552,8 @@ func (ec *executionContext) fieldContext_Query_profile(ctx context.Context, fiel
|
|||
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
|
||||
case "app_data":
|
||||
return ec.fieldContext_User_app_data(ctx, field)
|
||||
case "totp_verified":
|
||||
return ec.fieldContext_User_totp_verified(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
|
||||
},
|
||||
|
@ -10456,6 +10823,8 @@ func (ec *executionContext) fieldContext_Query__user(ctx context.Context, field
|
|||
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
|
||||
case "app_data":
|
||||
return ec.fieldContext_User_app_data(ctx, field)
|
||||
case "totp_verified":
|
||||
return ec.fieldContext_User_totp_verified(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
|
||||
},
|
||||
|
@ -10746,6 +11115,10 @@ func (ec *executionContext) fieldContext_Query__env(ctx context.Context, field g
|
|||
return ec.fieldContext_Env_DEFAULT_AUTHORIZE_RESPONSE_MODE(ctx, field)
|
||||
case "DISABLE_PLAYGROUND":
|
||||
return ec.fieldContext_Env_DISABLE_PLAYGROUND(ctx, field)
|
||||
case "DISABLE_MAIL_OTP_LOGIN":
|
||||
return ec.fieldContext_Env_DISABLE_MAIL_OTP_LOGIN(ctx, field)
|
||||
case "DISABLE_TOTP_LOGIN":
|
||||
return ec.fieldContext_Env_DISABLE_TOTP_LOGIN(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type Env", field.Name)
|
||||
},
|
||||
|
@ -12360,6 +12733,47 @@ func (ec *executionContext) fieldContext_User_app_data(ctx context.Context, fiel
|
|||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _User_totp_verified(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_User_totp_verified(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.TotpVerified, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*bool)
|
||||
fc.Result = res
|
||||
return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_User_totp_verified(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "User",
|
||||
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) _Users_pagination(ctx context.Context, field graphql.CollectedField, obj *model.Users) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Users_pagination(ctx, field)
|
||||
if err != nil {
|
||||
|
@ -12493,6 +12907,8 @@ func (ec *executionContext) fieldContext_Users_users(ctx context.Context, field
|
|||
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
|
||||
case "app_data":
|
||||
return ec.fieldContext_User_app_data(ctx, field)
|
||||
case "totp_verified":
|
||||
return ec.fieldContext_User_totp_verified(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
|
||||
},
|
||||
|
@ -12708,6 +13124,8 @@ func (ec *executionContext) fieldContext_ValidateSessionResponse_user(ctx contex
|
|||
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
|
||||
case "app_data":
|
||||
return ec.fieldContext_User_app_data(ctx, field)
|
||||
case "totp_verified":
|
||||
return ec.fieldContext_User_totp_verified(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
|
||||
},
|
||||
|
@ -17132,7 +17550,7 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
|
|||
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"}
|
||||
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"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
|
@ -17563,6 +17981,22 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
|
|||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "DISABLE_MAIL_OTP_LOGIN":
|
||||
var err error
|
||||
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("DISABLE_MAIL_OTP_LOGIN"))
|
||||
it.DisableMailOtpLogin, err = ec.unmarshalOBoolean2ᚖbool(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "DISABLE_TOTP_LOGIN":
|
||||
var err error
|
||||
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("DISABLE_TOTP_LOGIN"))
|
||||
it.DisableTotpLogin, err = ec.unmarshalOBoolean2ᚖbool(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18069,6 +18503,50 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context,
|
|||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputVerifyTOTPRequest(ctx context.Context, obj interface{}) (model.VerifyTOTPRequest, error) {
|
||||
var it model.VerifyTOTPRequest
|
||||
asMap := map[string]interface{}{}
|
||||
for k, v := range obj.(map[string]interface{}) {
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"otp", "token", "state"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case "otp":
|
||||
var err error
|
||||
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("otp"))
|
||||
it.Otp, err = ec.unmarshalNString2string(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "token":
|
||||
var err error
|
||||
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("token"))
|
||||
it.Token, err = ec.unmarshalNString2string(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "state":
|
||||
var err error
|
||||
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
|
||||
it.State, err = ec.unmarshalOString2ᚖstring(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputWebhookRequest(ctx context.Context, obj interface{}) (model.WebhookRequest, error) {
|
||||
var it model.WebhookRequest
|
||||
asMap := map[string]interface{}{}
|
||||
|
@ -18150,6 +18628,14 @@ func (ec *executionContext) _AuthResponse(ctx context.Context, sel ast.Selection
|
|||
|
||||
out.Values[i] = ec._AuthResponse_user(ctx, field, obj)
|
||||
|
||||
case "totp_base64_url":
|
||||
|
||||
out.Values[i] = ec._AuthResponse_totp_base64_url(ctx, field, obj)
|
||||
|
||||
case "totp_token":
|
||||
|
||||
out.Values[i] = ec._AuthResponse_totp_token(ctx, field, obj)
|
||||
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
|
@ -18557,6 +19043,20 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj
|
|||
|
||||
out.Values[i] = ec._Env_DISABLE_PLAYGROUND(ctx, field, obj)
|
||||
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "DISABLE_MAIL_OTP_LOGIN":
|
||||
|
||||
out.Values[i] = ec._Env_DISABLE_MAIL_OTP_LOGIN(ctx, field, obj)
|
||||
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "DISABLE_TOTP_LOGIN":
|
||||
|
||||
out.Values[i] = ec._Env_DISABLE_TOTP_LOGIN(ctx, field, obj)
|
||||
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
|
@ -18942,6 +19442,15 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
|||
return ec._Mutation_resend_otp(ctx, field)
|
||||
})
|
||||
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "verify_totp":
|
||||
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_verify_totp(ctx, field)
|
||||
})
|
||||
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
|
@ -19744,6 +20253,10 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj
|
|||
|
||||
out.Values[i] = ec._User_app_data(ctx, field, obj)
|
||||
|
||||
case "totp_verified":
|
||||
|
||||
out.Values[i] = ec._User_totp_verified(ctx, field, obj)
|
||||
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
|
@ -21024,6 +21537,11 @@ func (ec *executionContext) unmarshalNVerifyOTPRequest2githubᚗcomᚋauthorizer
|
|||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNVerifyTOTPRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerifyTOTPRequest(ctx context.Context, v interface{}) (model.VerifyTOTPRequest, error) {
|
||||
res, err := ec.unmarshalInputVerifyTOTPRequest(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNWebhook2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐWebhook(ctx context.Context, sel ast.SelectionSet, v model.Webhook) graphql.Marshaler {
|
||||
return ec._Webhook(ctx, sel, &v)
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ type AuthResponse struct {
|
|||
RefreshToken *string `json:"refresh_token"`
|
||||
ExpiresIn *int64 `json:"expires_in"`
|
||||
User *User `json:"user"`
|
||||
TotpBase64URL *string `json:"totp_base64_url"`
|
||||
TotpToken *string `json:"totp_token"`
|
||||
}
|
||||
|
||||
type DeleteEmailTemplateRequest struct {
|
||||
|
@ -122,6 +124,8 @@ type Env struct {
|
|||
DefaultAuthorizeResponseType *string `json:"DEFAULT_AUTHORIZE_RESPONSE_TYPE"`
|
||||
DefaultAuthorizeResponseMode *string `json:"DEFAULT_AUTHORIZE_RESPONSE_MODE"`
|
||||
DisablePlayground bool `json:"DISABLE_PLAYGROUND"`
|
||||
DisableMailOtpLogin bool `json:"DISABLE_MAIL_OTP_LOGIN"`
|
||||
DisableTotpLogin bool `json:"DISABLE_TOTP_LOGIN"`
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
|
@ -381,6 +385,8 @@ type UpdateEnvInput struct {
|
|||
DefaultAuthorizeResponseType *string `json:"DEFAULT_AUTHORIZE_RESPONSE_TYPE"`
|
||||
DefaultAuthorizeResponseMode *string `json:"DEFAULT_AUTHORIZE_RESPONSE_MODE"`
|
||||
DisablePlayground *bool `json:"DISABLE_PLAYGROUND"`
|
||||
DisableMailOtpLogin *bool `json:"DISABLE_MAIL_OTP_LOGIN"`
|
||||
DisableTotpLogin *bool `json:"DISABLE_TOTP_LOGIN"`
|
||||
}
|
||||
|
||||
type UpdateProfileInput struct {
|
||||
|
@ -447,6 +453,7 @@ type User struct {
|
|||
RevokedTimestamp *int64 `json:"revoked_timestamp"`
|
||||
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"`
|
||||
AppData map[string]interface{} `json:"app_data"`
|
||||
TotpVerified *bool `json:"totp_verified"`
|
||||
}
|
||||
|
||||
type Users struct {
|
||||
|
@ -504,6 +511,12 @@ type VerifyOTPRequest struct {
|
|||
State *string `json:"state"`
|
||||
}
|
||||
|
||||
type VerifyTOTPRequest struct {
|
||||
Otp string `json:"otp"`
|
||||
Token string `json:"token"`
|
||||
State *string `json:"state"`
|
||||
}
|
||||
|
||||
type Webhook struct {
|
||||
ID string `json:"id"`
|
||||
EventName *string `json:"event_name"`
|
||||
|
|
|
@ -52,6 +52,7 @@ type User {
|
|||
revoked_timestamp: Int64
|
||||
is_multi_factor_auth_enabled: Boolean
|
||||
app_data: Map
|
||||
totp_verified: Boolean
|
||||
}
|
||||
|
||||
type Users {
|
||||
|
@ -99,6 +100,8 @@ type AuthResponse {
|
|||
refresh_token: String
|
||||
expires_in: Int64
|
||||
user: User
|
||||
totp_base64_url: String
|
||||
totp_token: String
|
||||
}
|
||||
|
||||
type Response {
|
||||
|
@ -173,6 +176,8 @@ type Env {
|
|||
DEFAULT_AUTHORIZE_RESPONSE_TYPE: String
|
||||
DEFAULT_AUTHORIZE_RESPONSE_MODE: String
|
||||
DISABLE_PLAYGROUND: Boolean!
|
||||
DISABLE_MAIL_OTP_LOGIN: Boolean!
|
||||
DISABLE_TOTP_LOGIN: Boolean!
|
||||
}
|
||||
|
||||
type ValidateJWTTokenResponse {
|
||||
|
@ -296,6 +301,8 @@ input UpdateEnvInput {
|
|||
DEFAULT_AUTHORIZE_RESPONSE_TYPE: String
|
||||
DEFAULT_AUTHORIZE_RESPONSE_MODE: String
|
||||
DISABLE_PLAYGROUND: Boolean
|
||||
DISABLE_MAIL_OTP_LOGIN: Boolean
|
||||
DISABLE_TOTP_LOGIN: Boolean
|
||||
}
|
||||
|
||||
input AdminLoginInput {
|
||||
|
@ -558,6 +565,12 @@ input VerifyOTPRequest {
|
|||
state: String
|
||||
}
|
||||
|
||||
input VerifyTOTPRequest {
|
||||
otp: String!
|
||||
token: String!
|
||||
state: String
|
||||
}
|
||||
|
||||
input ResendOTPRequest {
|
||||
email: String
|
||||
phone_number: String
|
||||
|
@ -587,6 +600,7 @@ type Mutation {
|
|||
revoke(params: OAuthRevokeInput!): Response!
|
||||
verify_otp(params: VerifyOTPRequest!): AuthResponse!
|
||||
resend_otp(params: ResendOTPRequest!): Response!
|
||||
verify_totp(params: VerifyTOTPRequest!): AuthResponse!
|
||||
# admin only apis
|
||||
_delete_user(params: DeleteUserInput!): Response!
|
||||
_update_user(params: UpdateUserInput!): User!
|
||||
|
|
|
@ -81,6 +81,11 @@ func (r *mutationResolver) ResendOtp(ctx context.Context, params model.ResendOTP
|
|||
return resolvers.ResendOTPResolver(ctx, params)
|
||||
}
|
||||
|
||||
// VerifyTotp is the resolver for the verify_totp field.
|
||||
func (r *mutationResolver) VerifyTotp(ctx context.Context, params model.VerifyTOTPRequest) (*model.AuthResponse, error) {
|
||||
return resolvers.VerifyTotpResolver(ctx, params)
|
||||
}
|
||||
|
||||
// DeleteUser is the resolver for the _delete_user field.
|
||||
func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) {
|
||||
return resolvers.DeleteUserResolver(ctx, params)
|
||||
|
|
|
@ -36,9 +36,11 @@ func InitMemStore() error {
|
|||
constants.EnvKeyIsSMSServiceEnabled: false,
|
||||
constants.EnvKeyEnforceMultiFactorAuthentication: false,
|
||||
constants.EnvKeyDisableMultiFactorAuthentication: false,
|
||||
constants.EnvKeyDisableTOTPLogin: false,
|
||||
constants.EnvKeyAppCookieSecure: true,
|
||||
constants.EnvKeyAdminCookieSecure: true,
|
||||
constants.EnvKeyDisablePlayGround: true,
|
||||
constants.EnvKeyDisableMailOTPLogin: true,
|
||||
}
|
||||
|
||||
requiredEnvs := RequiredEnvStoreObj.GetRequiredEnv()
|
||||
|
|
|
@ -176,7 +176,7 @@ func (c *provider) GetEnvStore() (map[string]interface{}, error) {
|
|||
return nil, err
|
||||
}
|
||||
for key, value := range data {
|
||||
if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableMobileBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword || key == constants.EnvKeyIsEmailServiceEnabled || key == constants.EnvKeyIsSMSServiceEnabled || key == constants.EnvKeyEnforceMultiFactorAuthentication || key == constants.EnvKeyDisableMultiFactorAuthentication || key == constants.EnvKeyAppCookieSecure || key == constants.EnvKeyAdminCookieSecure || key == constants.EnvKeyDisablePlayGround {
|
||||
if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableMobileBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword || key == constants.EnvKeyIsEmailServiceEnabled || key == constants.EnvKeyIsSMSServiceEnabled || key == constants.EnvKeyEnforceMultiFactorAuthentication || key == constants.EnvKeyDisableMultiFactorAuthentication || key == constants.EnvKeyAppCookieSecure || key == constants.EnvKeyAdminCookieSecure || key == constants.EnvKeyDisablePlayGround || key == constants.EnvKeyDisableTOTPLogin || key == constants.EnvKeyDisableMailOTPLogin {
|
||||
boolValue, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return res, err
|
||||
|
|
|
@ -203,6 +203,8 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||
res.AdminCookieSecure = store[constants.EnvKeyAdminCookieSecure].(bool)
|
||||
res.AppCookieSecure = store[constants.EnvKeyAppCookieSecure].(bool)
|
||||
res.DisablePlayground = store[constants.EnvKeyDisablePlayGround].(bool)
|
||||
res.DisableMailOtpLogin = store[constants.EnvKeyDisableMailOTPLogin].(bool)
|
||||
res.DisableTotpLogin = store[constants.EnvKeyDisableTOTPLogin].(bool)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
|
|
@ -7,11 +7,13 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/cookie"
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
"github.com/authorizerdev/authorizer/server/db"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/email"
|
||||
|
@ -110,8 +112,18 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
|||
log.Debug("MFA service not enabled: ", err)
|
||||
}
|
||||
|
||||
isTOTPLoginDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableTOTPLogin)
|
||||
if err != nil || !isTOTPLoginDisabled {
|
||||
log.Debug("totp service not enabled: ", err)
|
||||
}
|
||||
|
||||
isMailOTPDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMailOTPLogin)
|
||||
if err != nil || !isMailOTPDisabled {
|
||||
log.Debug("mail OTP service not enabled: ", err)
|
||||
}
|
||||
|
||||
// If email service is not enabled continue the process in any way
|
||||
if refs.BoolValue(user.IsMultiFactorAuthEnabled) && isEmailServiceEnabled && !isMFADisabled {
|
||||
if refs.BoolValue(user.IsMultiFactorAuthEnabled) && !isMailOTPDisabled && !isMFADisabled {
|
||||
otp := utils.GenerateOTP()
|
||||
expires := time.Now().Add(1 * time.Minute).Unix()
|
||||
otpData, err := db.Provider.UpsertOTP(ctx, &models.OTP{
|
||||
|
@ -150,6 +162,47 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
|||
}, nil
|
||||
}
|
||||
|
||||
// if mfa enabled and also totp enabled
|
||||
if !isMFADisabled && refs.BoolValue(user.IsMultiFactorAuthEnabled) && !isTOTPLoginDisabled {
|
||||
pubKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)
|
||||
if err != nil {
|
||||
log.Debug("error while getting public key")
|
||||
}
|
||||
|
||||
publicKey, err := crypto.ParseRsaPublicKeyFromPemStr(pubKey)
|
||||
if err != nil {
|
||||
log.Debug("error while parsing public key")
|
||||
}
|
||||
|
||||
//encrypting user id, so it can be used as token for verifying
|
||||
encryptedUserId, err := crypto.EncryptRSA(user.ID, *publicKey)
|
||||
if err != nil {
|
||||
log.Debug("error while encrypting user id")
|
||||
}
|
||||
|
||||
// for first time user or whose totp is not verified
|
||||
if !user.TotpVerified {
|
||||
base64URL, err := db.Provider.GenerateTotp(ctx, user.ID)
|
||||
if err != nil {
|
||||
log.Debug("error while generating base64 url: ", err)
|
||||
}
|
||||
// when user is first time registering for totp
|
||||
res = &model.AuthResponse{
|
||||
Message: `Proceed to totp screen`,
|
||||
TotpBase64URL: base64URL,
|
||||
TotpToken: &encryptedUserId,
|
||||
}
|
||||
return res, nil
|
||||
} else {
|
||||
//when user is already register for totp
|
||||
res = &model.AuthResponse{
|
||||
Message: `Proceed to totp screen`,
|
||||
TotpToken: &encryptedUserId,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
code := ""
|
||||
codeChallenge := ""
|
||||
nonce := ""
|
||||
|
|
|
@ -253,13 +253,15 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||
// in case SMTP is off but env is set to true
|
||||
if updatedData[constants.EnvKeySmtpHost] == "" || updatedData[constants.EnvKeySmtpUsername] == "" || updatedData[constants.EnvKeySmtpPassword] == "" || updatedData[constants.EnvKeySenderEmail] == "" && updatedData[constants.EnvKeySmtpPort] == "" {
|
||||
updatedData[constants.EnvKeyIsEmailServiceEnabled] = false
|
||||
updatedData[constants.EnvKeyDisableMultiFactorAuthentication] = true
|
||||
if !updatedData[constants.EnvKeyDisableEmailVerification].(bool) {
|
||||
updatedData[constants.EnvKeyDisableEmailVerification] = true
|
||||
}
|
||||
|
||||
if !updatedData[constants.EnvKeyDisableMailOTPLogin].(bool) {
|
||||
updatedData[constants.EnvKeyDisableMailOTPLogin] = true
|
||||
}
|
||||
if !updatedData[constants.EnvKeyDisableMagicLinkLogin].(bool) {
|
||||
updatedData[constants.EnvKeyDisableMagicLinkLogin] = true
|
||||
updatedData[constants.EnvKeyDisableMailOTPLogin] = true
|
||||
updatedData[constants.EnvKeyDisableTOTPLogin] = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,6 +276,21 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||
}
|
||||
}
|
||||
|
||||
if updatedData[constants.EnvKeyDisableMultiFactorAuthentication].(bool) {
|
||||
updatedData[constants.EnvKeyDisableTOTPLogin] = true
|
||||
updatedData[constants.EnvKeyDisableMailOTPLogin] = true
|
||||
} else {
|
||||
if !updatedData[constants.EnvKeyDisableMailOTPLogin].(bool) && !updatedData[constants.EnvKeyDisableTOTPLogin].(bool) {
|
||||
errors.New("can't enable both mfa methods at same time")
|
||||
updatedData[constants.EnvKeyDisableMailOTPLogin] = true
|
||||
updatedData[constants.EnvKeyDisableTOTPLogin] = false
|
||||
} else if updatedData[constants.EnvKeyDisableMailOTPLogin].(bool) && updatedData[constants.EnvKeyDisableTOTPLogin].(bool) {
|
||||
errors.New("can't disable both mfa methods at same time")
|
||||
updatedData[constants.EnvKeyDisableMailOTPLogin] = true
|
||||
updatedData[constants.EnvKeyDisableTOTPLogin] = false
|
||||
}
|
||||
}
|
||||
|
||||
if !currentData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && updatedData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && !updatedData[constants.EnvKeyDisableMultiFactorAuthentication].(bool) {
|
||||
go db.Provider.UpdateUsers(ctx, map[string]interface{}{
|
||||
"is_multi_factor_auth_enabled": true,
|
||||
|
|
136
server/resolvers/verify_totp.go
Normal file
136
server/resolvers/verify_totp.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
package resolvers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/cookie"
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
"github.com/authorizerdev/authorizer/server/db"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||
"github.com/authorizerdev/authorizer/server/refs"
|
||||
"github.com/authorizerdev/authorizer/server/token"
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
)
|
||||
|
||||
// VerifyTotpResolver resolver for verify totp mutation
|
||||
func VerifyTotpResolver(ctx context.Context, params model.VerifyTOTPRequest) (*model.AuthResponse, error) {
|
||||
var res *model.AuthResponse
|
||||
|
||||
encryptedkey := params.Token
|
||||
|
||||
pvtKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey)
|
||||
if err != nil {
|
||||
log.Debug("error while getting private key")
|
||||
}
|
||||
|
||||
privateKey, err := crypto.ParseRsaPrivateKeyFromPemStr(pvtKey)
|
||||
if err != nil {
|
||||
log.Debug("error while parsing private key")
|
||||
}
|
||||
|
||||
userID, err := crypto.DecryptRSA(encryptedkey, *privateKey)
|
||||
if err != nil {
|
||||
log.Debug("error while decrypting userId")
|
||||
}
|
||||
|
||||
gc, err := utils.GinContextFromContext(ctx)
|
||||
if err != nil {
|
||||
log.Debug("Failed to get GinContext: ", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
user, err := db.Provider.GetUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status, err := db.Provider.ValidatePasscode(ctx, params.Otp, userID)
|
||||
if err != nil || !status {
|
||||
return nil, fmt.Errorf("error while validating passcode")
|
||||
}
|
||||
|
||||
code := ""
|
||||
codeChallenge := ""
|
||||
nonce := ""
|
||||
|
||||
roles := strings.Split(user.Roles, ",")
|
||||
scope := []string{"openid", "email", "profile"}
|
||||
|
||||
// Get state from store
|
||||
if params.State != nil {
|
||||
authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State))
|
||||
if authorizeState != "" {
|
||||
authorizeStateSplit := strings.Split(authorizeState, "@@")
|
||||
if len(authorizeStateSplit) > 1 {
|
||||
code = authorizeStateSplit[0]
|
||||
codeChallenge = authorizeStateSplit[1]
|
||||
} else {
|
||||
nonce = authorizeState
|
||||
}
|
||||
go memorystore.Provider.RemoveState(refs.StringValue(params.State))
|
||||
}
|
||||
}
|
||||
|
||||
if nonce == "" {
|
||||
nonce = uuid.New().String()
|
||||
}
|
||||
|
||||
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, code)
|
||||
if err != nil {
|
||||
log.Debug("Failed to create auth token", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// TODO add to other login options as well
|
||||
// Code challenge could be optional if PKCE flow is not used
|
||||
if code != "" {
|
||||
if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
|
||||
log.Debug("SetState failed: ", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||
if expiresIn <= 0 {
|
||||
expiresIn = 1
|
||||
}
|
||||
|
||||
res = &model.AuthResponse{
|
||||
Message: `Logged in successfully`,
|
||||
AccessToken: &authToken.AccessToken.Token,
|
||||
IDToken: &authToken.IDToken.Token,
|
||||
ExpiresIn: &expiresIn,
|
||||
User: user.AsAPIUser(),
|
||||
}
|
||||
|
||||
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||
sessionStoreKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
|
||||
memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash, authToken.SessionTokenExpiresAt)
|
||||
memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token, authToken.AccessToken.ExpiresAt)
|
||||
|
||||
if authToken.RefreshToken != nil {
|
||||
res.RefreshToken = &authToken.RefreshToken.Token
|
||||
memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token, authToken.RefreshToken.ExpiresAt)
|
||||
}
|
||||
|
||||
go func() {
|
||||
utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, constants.AuthRecipeMethodBasicAuth, user)
|
||||
db.Provider.AddSession(ctx, &models.Session{
|
||||
UserID: user.ID,
|
||||
UserAgent: utils.GetUserAgent(gc.Request),
|
||||
IP: utils.GetIP(gc.Request),
|
||||
})
|
||||
}()
|
||||
|
||||
return res, nil
|
||||
}
|
|
@ -141,6 +141,7 @@ func TestResolvers(t *testing.T) {
|
|||
inviteUserTest(t, s)
|
||||
validateJwtTokenTest(t, s)
|
||||
verifyOTPTest(t, s)
|
||||
verifyTOTPTest(t, s)
|
||||
resendOTPTest(t, s)
|
||||
validateSessionTests(t, s)
|
||||
|
||||
|
|
|
@ -54,6 +54,9 @@ func resendOTPTest(t *testing.T, s TestSetup) {
|
|||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, updateRes)
|
||||
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMailOTPLogin, false)
|
||||
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableTOTPLogin, true)
|
||||
|
||||
// Resend otp should return error as no initial opt is being sent
|
||||
resendOtpRes, err := resolvers.ResendOTPResolver(ctx, model.ResendOTPRequest{
|
||||
Email: refs.NewStringRef(email),
|
||||
|
|
|
@ -54,6 +54,8 @@ func verifyOTPTest(t *testing.T, s TestSetup) {
|
|||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, updateProfileRes.Message)
|
||||
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMailOTPLogin, false)
|
||||
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableTOTPLogin, true)
|
||||
|
||||
// Login should not return error but access token should be empty as otp should have been sent
|
||||
loginRes, err = resolvers.LoginResolver(ctx, model.LoginInput{
|
||||
|
|
149
server/test/verify_totp_test.go
Normal file
149
server/test/verify_totp_test.go
Normal file
|
@ -0,0 +1,149 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gokyle/twofactor"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tuotoo/qrcode"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/db"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||
"github.com/authorizerdev/authorizer/server/refs"
|
||||
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||
"github.com/authorizerdev/authorizer/server/token"
|
||||
)
|
||||
|
||||
func verifyTOTPTest(t *testing.T, s TestSetup) {
|
||||
t.Helper()
|
||||
t.Run(`should verify totp`, func(t *testing.T) {
|
||||
req, ctx := createContext(s)
|
||||
email := "verify_totp." + s.TestInfo.Email
|
||||
cleanData(email)
|
||||
res, err := resolvers.SignupResolver(ctx, model.SignUpInput{
|
||||
Email: email,
|
||||
Password: s.TestInfo.Password,
|
||||
ConfirmPassword: s.TestInfo.Password,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, res)
|
||||
|
||||
// Login should fail as email is not verified
|
||||
loginRes, err := resolvers.LoginResolver(ctx, model.LoginInput{
|
||||
Email: email,
|
||||
Password: s.TestInfo.Password,
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, loginRes)
|
||||
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, email, verificationRequest.Email)
|
||||
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
|
||||
Token: verificationRequest.Token,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, verifyRes.AccessToken, "", "access token should not be empty")
|
||||
|
||||
// Using access token update profile
|
||||
s.GinContext.Request.Header.Set("Authorization", "Bearer "+refs.StringValue(verifyRes.AccessToken))
|
||||
ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
|
||||
updateProfileRes, err := resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{
|
||||
IsMultiFactorAuthEnabled: refs.NewBoolRef(true),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, updateProfileRes.Message)
|
||||
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableTOTPLogin, false)
|
||||
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMailOTPLogin, true)
|
||||
|
||||
// Login should not return error but access token should be empty
|
||||
loginRes, err = resolvers.LoginResolver(ctx, model.LoginInput{
|
||||
Email: email,
|
||||
Password: s.TestInfo.Password,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, loginRes)
|
||||
assert.NotNil(t, loginRes.TotpBase64URL)
|
||||
assert.NotNil(t, loginRes.TotpToken)
|
||||
assert.Nil(t, loginRes.AccessToken)
|
||||
assert.Equal(t, loginRes.Message, `Proceed to totp screen`)
|
||||
|
||||
// get totp url for validation
|
||||
pngBytes, err := base64.StdEncoding.DecodeString(*loginRes.TotpBase64URL)
|
||||
assert.NoError(t, err)
|
||||
qrmatrix, err := qrcode.Decode(bytes.NewReader(pngBytes))
|
||||
assert.NoError(t, err)
|
||||
tf, label, err := twofactor.FromURL(qrmatrix.Content)
|
||||
data := strings.Split(label, ":")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, email, data[1])
|
||||
assert.NotNil(t, tf)
|
||||
|
||||
code := tf.OTP()
|
||||
|
||||
assert.NotEmpty(t, code)
|
||||
|
||||
valid, err := resolvers.VerifyTotpResolver(ctx, model.VerifyTOTPRequest{
|
||||
Otp: code,
|
||||
Token: *loginRes.TotpToken,
|
||||
})
|
||||
|
||||
accessToken := *valid.AccessToken
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, accessToken)
|
||||
assert.Equal(t, `Logged in successfully`, valid.Message)
|
||||
|
||||
assert.NotEmpty(t, accessToken)
|
||||
claims, err := token.ParseJWTToken(accessToken)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, claims)
|
||||
loginMethod := claims["login_method"]
|
||||
sessionKey := verifyRes.User.ID
|
||||
if loginMethod != nil && loginMethod != "" {
|
||||
sessionKey = loginMethod.(string) + ":" + verifyRes.User.ID
|
||||
}
|
||||
|
||||
sessionToken, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, sessionToken)
|
||||
|
||||
cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken)
|
||||
cookie = strings.TrimSuffix(cookie, ";")
|
||||
|
||||
req.Header.Set("Cookie", cookie)
|
||||
//logged out
|
||||
logout, err := resolvers.LogoutResolver(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, logout.Message, `Logged out successfully`)
|
||||
|
||||
loginRes, err = resolvers.LoginResolver(ctx, model.LoginInput{
|
||||
Email: email,
|
||||
Password: s.TestInfo.Password,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, loginRes)
|
||||
assert.NotNil(t, loginRes.TotpToken)
|
||||
assert.Nil(t, loginRes.TotpBase64URL)
|
||||
assert.Nil(t, loginRes.AccessToken)
|
||||
assert.Equal(t, loginRes.Message, `Proceed to totp screen`)
|
||||
|
||||
code = tf.OTP()
|
||||
assert.NotEmpty(t, code)
|
||||
|
||||
valid, err = resolvers.VerifyTotpResolver(ctx, model.VerifyTOTPRequest{
|
||||
Otp: code,
|
||||
Token: *loginRes.TotpToken,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, *valid.AccessToken)
|
||||
assert.Equal(t, `Logged in successfully`, valid.Message)
|
||||
|
||||
cleanData(email)
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user