* added totp methods in db's providers
* adding totp in login method
This commit is contained in:
lemonScaletech 2023-08-31 19:50:44 +05:30
parent d9bfb74c98
commit 3f5283bf7f
16 changed files with 570 additions and 1 deletions

View File

@ -160,6 +160,9 @@ const (
// EnvKeyDisableMultiFactorAuthentication is key for env variable DISABLE_MULTI_FACTOR_AUTHENTICATION // 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 // this variable is used to completely disable multi factor authentication. It will have no effect on profile preference
EnvKeyDisableMultiFactorAuthentication = "DISABLE_MULTI_FACTOR_AUTHENTICATION" 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 // EnvKeyDisablePhoneVerification is key for env variable DISABLE_PHONE_VERIFICATION
// this variable is used to disable phone verification // this variable is used to disable phone verification
EnvKeyDisablePhoneVerification = "DISABLE_PHONE_VERIFICATION" EnvKeyDisablePhoneVerification = "DISABLE_PHONE_VERIFICATION"

View File

@ -34,6 +34,7 @@ type User struct {
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"` 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"` 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"` 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 { func (user *User) AsAPIUser() *model.User {

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -2,7 +2,6 @@ package providers
import ( import (
"context" "context"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
) )
@ -88,4 +87,9 @@ type Provider interface {
GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error)
// DeleteOTP to delete otp // DeleteOTP to delete otp
DeleteOTP(ctx context.Context, otp *models.OTP) error 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)
} }

View File

@ -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
}
}
}

View File

@ -21,6 +21,7 @@ require (
github.com/joho/godotenv v1.3.0 github.com/joho/godotenv v1.3.0
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // 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/redis/go-redis/v9 v9.0.3
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1

View File

@ -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/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 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 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 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= 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= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/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 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k=
github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=

View File

@ -52,6 +52,7 @@ type ComplexityRoot struct {
RefreshToken func(childComplexity int) int RefreshToken func(childComplexity int) int
ShouldShowEmailOtpScreen func(childComplexity int) int ShouldShowEmailOtpScreen func(childComplexity int) int
ShouldShowMobileOtpScreen func(childComplexity int) int ShouldShowMobileOtpScreen func(childComplexity int) int
TotpBase64url func(childComplexity int) int
User 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 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": case "AuthResponse.user":
if e.complexity.AuthResponse.User == nil { if e.complexity.AuthResponse.User == nil {
break break
@ -2301,6 +2309,7 @@ type AuthResponse {
refresh_token: String refresh_token: String
expires_in: Int64 expires_in: Int64
user: User user: User
totpBase64URL: String
} }
type Response { type Response {
@ -3845,6 +3854,47 @@ func (ec *executionContext) fieldContext_AuthResponse_user(ctx context.Context,
return fc, nil 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) { 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) fc, err := ec.fieldContext_EmailTemplate_id(ctx, field)
if err != nil { 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) return ec.fieldContext_AuthResponse_expires_in(ctx, field)
case "user": case "user":
return ec.fieldContext_AuthResponse_user(ctx, field) 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) 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) return ec.fieldContext_AuthResponse_expires_in(ctx, field)
case "user": case "user":
return ec.fieldContext_AuthResponse_user(ctx, field) 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) 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) return ec.fieldContext_AuthResponse_expires_in(ctx, field)
case "user": case "user":
return ec.fieldContext_AuthResponse_user(ctx, field) 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) 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) return ec.fieldContext_AuthResponse_expires_in(ctx, field)
case "user": case "user":
return ec.fieldContext_AuthResponse_user(ctx, field) 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) 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) return ec.fieldContext_AuthResponse_expires_in(ctx, field)
case "user": case "user":
return ec.fieldContext_AuthResponse_user(ctx, field) 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) 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) return ec.fieldContext_AuthResponse_expires_in(ctx, field)
case "user": case "user":
return ec.fieldContext_AuthResponse_user(ctx, field) 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) 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) return ec.fieldContext_AuthResponse_expires_in(ctx, field)
case "user": case "user":
return ec.fieldContext_AuthResponse_user(ctx, field) 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) 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) out.Values[i] = ec._AuthResponse_user(ctx, field, obj)
case "totpBase64URL":
out.Values[i] = ec._AuthResponse_totpBase64URL(ctx, field, obj)
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }

View File

@ -34,6 +34,7 @@ type AuthResponse struct {
RefreshToken *string `json:"refresh_token"` RefreshToken *string `json:"refresh_token"`
ExpiresIn *int64 `json:"expires_in"` ExpiresIn *int64 `json:"expires_in"`
User *User `json:"user"` User *User `json:"user"`
TotpBase64url *string `json:"totpBase64URL"`
} }
type DeleteEmailTemplateRequest struct { type DeleteEmailTemplateRequest struct {

View File

@ -99,6 +99,7 @@ type AuthResponse {
refresh_token: String refresh_token: String
expires_in: Int64 expires_in: Int64
user: User user: User
totpBase64URL: String
} }
type Response { type Response {

View File

@ -150,6 +150,16 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
}, nil }, 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 := "" code := ""
codeChallenge := "" codeChallenge := ""
nonce := "" nonce := ""