From 3f5283bf7f4d30595ea2dce6c14691ccda75f5fe Mon Sep 17 00:00:00 2001 From: lemonScaletech Date: Thu, 31 Aug 2023 19:50:44 +0530 Subject: [PATCH] feat: * added totp methods in db's providers * adding totp in login method --- server/constants/env.go | 3 + server/db/models/user.go | 1 + server/db/providers/arangodb/totp.go | 68 +++++++++++++++++++ server/db/providers/cassandradb/totp.go | 68 +++++++++++++++++++ server/db/providers/couchbase/totp.go | 68 +++++++++++++++++++ server/db/providers/dynamodb/totp.go | 68 +++++++++++++++++++ server/db/providers/mongodb/totp.go | 68 +++++++++++++++++++ server/db/providers/provider_template/totp.go | 68 +++++++++++++++++++ server/db/providers/providers.go | 6 +- server/db/providers/sql/totp.go | 68 +++++++++++++++++++ server/go.mod | 1 + server/go.sum | 4 ++ server/graph/generated/generated.go | 68 +++++++++++++++++++ server/graph/model/models_gen.go | 1 + server/graph/schema.graphqls | 1 + server/resolvers/login.go | 10 +++ 16 files changed, 570 insertions(+), 1 deletion(-) create mode 100644 server/db/providers/arangodb/totp.go create mode 100644 server/db/providers/cassandradb/totp.go create mode 100644 server/db/providers/couchbase/totp.go create mode 100644 server/db/providers/dynamodb/totp.go create mode 100644 server/db/providers/mongodb/totp.go create mode 100644 server/db/providers/provider_template/totp.go create mode 100644 server/db/providers/sql/totp.go diff --git a/server/constants/env.go b/server/constants/env.go index 828d4b8..649603c 100644 --- a/server/constants/env.go +++ b/server/constants/env.go @@ -160,6 +160,9 @@ 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" + // EnvKeyDisableTotpAuthentication is key for env variable DISABLE_TOTP_AUTHENTICATION + // this variable is used to completely disable totp verification + EnvKeyDisableTotpAuthentication = "DISABLE_TOTP_AUTHENTICATION" // EnvKeyDisablePhoneVerification is key for env variable DISABLE_PHONE_VERIFICATION // this variable is used to disable phone verification EnvKeyDisablePhoneVerification = "DISABLE_PHONE_VERIFICATION" diff --git a/server/db/models/user.go b/server/db/models/user.go index a262823..c077356 100644 --- a/server/db/models/user.go +++ b/server/db/models/user.go @@ -34,6 +34,7 @@ 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"` } func (user *User) AsAPIUser() *model.User { diff --git a/server/db/providers/arangodb/totp.go b/server/db/providers/arangodb/totp.go new file mode 100644 index 0000000..7bdae0b --- /dev/null +++ b/server/db/providers/arangodb/totp.go @@ -0,0 +1,68 @@ +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") + } + + // validate passcode inputted by user + for { + status := totp.Validate(passcode, *user.TotpSecret) + if status { + return status, nil + } + } +} diff --git a/server/db/providers/cassandradb/totp.go b/server/db/providers/cassandradb/totp.go new file mode 100644 index 0000000..7732d04 --- /dev/null +++ b/server/db/providers/cassandradb/totp.go @@ -0,0 +1,68 @@ +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") + } + + // validate passcode inputted by user + for { + status := totp.Validate(passcode, *user.TotpSecret) + if status { + return status, nil + } + } +} diff --git a/server/db/providers/couchbase/totp.go b/server/db/providers/couchbase/totp.go new file mode 100644 index 0000000..a7e25fc --- /dev/null +++ b/server/db/providers/couchbase/totp.go @@ -0,0 +1,68 @@ +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") + } + + // validate passcode inputted by user + for { + status := totp.Validate(passcode, *user.TotpSecret) + if status { + return status, nil + } + } +} diff --git a/server/db/providers/dynamodb/totp.go b/server/db/providers/dynamodb/totp.go new file mode 100644 index 0000000..5207709 --- /dev/null +++ b/server/db/providers/dynamodb/totp.go @@ -0,0 +1,68 @@ +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") + } + + // validate passcode inputted by user + for { + status := totp.Validate(passcode, *user.TotpSecret) + if status { + return status, nil + } + } +} diff --git a/server/db/providers/mongodb/totp.go b/server/db/providers/mongodb/totp.go new file mode 100644 index 0000000..031d732 --- /dev/null +++ b/server/db/providers/mongodb/totp.go @@ -0,0 +1,68 @@ +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") + } + + // validate passcode inputted by user + for { + status := totp.Validate(passcode, *user.TotpSecret) + if status { + return status, nil + } + } +} diff --git a/server/db/providers/provider_template/totp.go b/server/db/providers/provider_template/totp.go new file mode 100644 index 0000000..1e2fda6 --- /dev/null +++ b/server/db/providers/provider_template/totp.go @@ -0,0 +1,68 @@ +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") + } + + // validate passcode inputted by user + for { + status := totp.Validate(passcode, *user.TotpSecret) + if status { + return status, nil + } + } +} diff --git a/server/db/providers/providers.go b/server/db/providers/providers.go index 65b9010..c34c34e 100644 --- a/server/db/providers/providers.go +++ b/server/db/providers/providers.go @@ -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) } diff --git a/server/db/providers/sql/totp.go b/server/db/providers/sql/totp.go new file mode 100644 index 0000000..cb4957b --- /dev/null +++ b/server/db/providers/sql/totp.go @@ -0,0 +1,68 @@ +package sql + +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") + } + + // validate passcode inputted by user + for { + status := totp.Validate(passcode, *user.TotpSecret) + if status { + return status, nil + } + } +} diff --git a/server/go.mod b/server/go.mod index d1747e2..8404573 100644 --- a/server/go.mod +++ b/server/go.mod @@ -21,6 +21,7 @@ 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 diff --git a/server/go.sum b/server/go.sum index 4a2c928..4b61a81 100644 --- a/server/go.sum +++ b/server/go.sum @@ -58,6 +58,8 @@ github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYE github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= 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= @@ -285,6 +287,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= diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index f323e42..84523cc 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -52,6 +52,7 @@ type ComplexityRoot struct { RefreshToken func(childComplexity int) int ShouldShowEmailOtpScreen func(childComplexity int) int ShouldShowMobileOtpScreen func(childComplexity int) int + TotpBase64url func(childComplexity int) int User func(childComplexity int) int } @@ -446,6 +447,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.AuthResponse.ShouldShowMobileOtpScreen(childComplexity), true + case "AuthResponse.totpBase64URL": + if e.complexity.AuthResponse.TotpBase64url == nil { + break + } + + return e.complexity.AuthResponse.TotpBase64url(childComplexity), true + case "AuthResponse.user": if e.complexity.AuthResponse.User == nil { break @@ -2301,6 +2309,7 @@ type AuthResponse { refresh_token: String expires_in: Int64 user: User + totpBase64URL: String } type Response { @@ -3845,6 +3854,47 @@ func (ec *executionContext) fieldContext_AuthResponse_user(ctx context.Context, return fc, nil } +func (ec *executionContext) _AuthResponse_totpBase64URL(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_AuthResponse_totpBase64URL(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_totpBase64URL(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 { @@ -7901,6 +7951,8 @@ 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 "totpBase64URL": + return ec.fieldContext_AuthResponse_totpBase64URL(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name) }, @@ -7974,6 +8026,8 @@ 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 "totpBase64URL": + return ec.fieldContext_AuthResponse_totpBase64URL(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name) }, @@ -8047,6 +8101,8 @@ 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 "totpBase64URL": + return ec.fieldContext_AuthResponse_totpBase64URL(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name) }, @@ -8120,6 +8176,8 @@ 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 "totpBase64URL": + return ec.fieldContext_AuthResponse_totpBase64URL(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name) }, @@ -8359,6 +8417,8 @@ 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 "totpBase64URL": + return ec.fieldContext_AuthResponse_totpBase64URL(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name) }, @@ -8668,6 +8728,8 @@ 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 "totpBase64URL": + return ec.fieldContext_AuthResponse_totpBase64URL(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name) }, @@ -10090,6 +10152,8 @@ 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 "totpBase64URL": + return ec.fieldContext_AuthResponse_totpBase64URL(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name) }, @@ -18150,6 +18214,10 @@ func (ec *executionContext) _AuthResponse(ctx context.Context, sel ast.Selection out.Values[i] = ec._AuthResponse_user(ctx, field, obj) + case "totpBase64URL": + + out.Values[i] = ec._AuthResponse_totpBase64URL(ctx, field, obj) + default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 4cdd8d0..49adb71 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -34,6 +34,7 @@ type AuthResponse struct { RefreshToken *string `json:"refresh_token"` ExpiresIn *int64 `json:"expires_in"` User *User `json:"user"` + TotpBase64url *string `json:"totpBase64URL"` } type DeleteEmailTemplateRequest struct { diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 1c7de22..44a90ba 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -99,6 +99,7 @@ type AuthResponse { refresh_token: String expires_in: Int64 user: User + totpBase64URL: String } type Response { diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 6b16c3e..bc1f3d9 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -150,6 +150,16 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes }, nil } + if !isMFADisabled && refs.BoolValue(user.IsMultiFactorAuthEnabled) { + if user.TotpSecret == nil { + base64URL, err := db.Provider.GenerateTotp(ctx, user.ID) + if err != nil { + log.Debug("error while generating base64 url: ", err) + } + res.TotpBase64url = base64URL + } + } + code := "" codeChallenge := "" nonce := ""