Refactor code for otp

This commit is contained in:
Lakhan Samani 2023-07-13 11:39:22 +05:30
parent c20e9b810a
commit d04f79557a
33 changed files with 279 additions and 687 deletions

View File

@ -7,6 +7,8 @@ const (
AuthRecipeMethodMobileBasicAuth = "mobile_basic_auth" AuthRecipeMethodMobileBasicAuth = "mobile_basic_auth"
// AuthRecipeMethodMagicLinkLogin is the magic_link_login auth method // AuthRecipeMethodMagicLinkLogin is the magic_link_login auth method
AuthRecipeMethodMagicLinkLogin = "magic_link_login" AuthRecipeMethodMagicLinkLogin = "magic_link_login"
// AuthRecipeMethodMobileOTP is the mobile_otp auth method
AuthRecipeMethodMobileOTP = "mobile_otp"
// AuthRecipeMethodGoogle is the google auth method // AuthRecipeMethodGoogle is the google auth method
AuthRecipeMethodGoogle = "google" AuthRecipeMethodGoogle = "google"
// AuthRecipeMethodGithub is the github auth method // AuthRecipeMethodGithub is the github auth method

View File

@ -1,12 +0,0 @@
package models
// SMSVerificationRequest is SMS verification requests model for database
type SMSVerificationRequest struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
PhoneNumber string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number" index:"phone_number,hash"`
Code string `json:"code" bson:"code" cql:"code" dynamo:"code"`
CodeExpiresAt int64 `json:"code_expires_at" bson:"code_expires_at" cql:"code_expires_at" dynamo:"code_expires_at"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

View File

@ -2,6 +2,7 @@ package arangodb
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"time" "time"
@ -12,17 +13,31 @@ import (
// UpsertOTP to add or update otp // UpsertOTP to add or update otp
func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) { func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) {
otp, _ := p.GetOTPByEmail(ctx, otpParam.Email) // check if email or phone number is present
if otpParam.Email == "" && otpParam.PhoneNumber == "" {
return nil, errors.New("email or phone_number is required")
}
uniqueField := models.FieldNameEmail
if otpParam.Email == "" && otpParam.PhoneNumber != "" {
uniqueField = models.FieldNamePhoneNumber
}
var otp *models.OTP
if uniqueField == models.FieldNameEmail {
otp, _ = p.GetOTPByEmail(ctx, otpParam.Email)
} else {
otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber)
}
shouldCreate := false shouldCreate := false
if otp == nil { if otp == nil {
id := uuid.NewString() id := uuid.NewString()
otp = &models.OTP{ otp = &models.OTP{
ID: id, ID: id,
Key: id, Key: id,
Otp: otpParam.Otp, Otp: otpParam.Otp,
Email: otpParam.Email, Email: otpParam.Email,
ExpiresAt: otpParam.ExpiresAt, PhoneNumber: otpParam.PhoneNumber,
CreatedAt: time.Now().Unix(), ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
} }
shouldCreate = true shouldCreate = true
} else { } else {
@ -67,7 +82,35 @@ func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*mod
for { for {
if !cursor.HasMore() { if !cursor.HasMore() {
if otp.Key == "" { if otp.Key == "" {
return nil, fmt.Errorf("email template not found") return nil, fmt.Errorf("otp with given email not found")
}
break
}
_, err := cursor.ReadDocument(ctx, &otp)
if err != nil {
return nil, err
}
}
return &otp, nil
}
// GetOTPByPhoneNumber to get otp for a given phone number
func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) {
var otp models.OTP
query := fmt.Sprintf("FOR d in %s FILTER d.phone_number == @phone_number RETURN d", models.Collections.OTP)
bindVars := map[string]interface{}{
"phone_number": phoneNumber,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if otp.Key == "" {
return nil, fmt.Errorf("otp with given phone_number not found")
} }
break break
} }

View File

@ -233,22 +233,7 @@ func NewProvider() (*provider, error) {
Unique: true, Unique: true,
Sparse: true, Sparse: true,
}) })
otpCollection.EnsureHashIndex(ctx, []string{"phone_number"}, &arangoDriver.EnsureHashIndexOptions{
smsVerificationCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.SMSVerificationRequest)
if err != nil {
return nil, err
}
if !smsVerificationCollectionExists {
_, err = arangodb.CreateCollection(ctx, models.Collections.SMSVerificationRequest, nil)
if err != nil {
return nil, err
}
}
smsVerificationCollection, err := arangodb.Collection(ctx, models.Collections.SMSVerificationRequest)
if err != nil {
return nil, err
}
smsVerificationCollection.EnsureHashIndex(ctx, []string{"phone_number"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true, Unique: true,
Sparse: true, Sparse: true,
}) })

View File

@ -1,22 +0,0 @@
package arangodb
import (
"context"
"github.com/authorizerdev/authorizer/server/db/models"
)
// UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return nil, nil
}
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
return nil, nil
}
// DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil
}

View File

@ -2,6 +2,7 @@ package cassandradb
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"time" "time"
@ -12,17 +13,31 @@ import (
// UpsertOTP to add or update otp // UpsertOTP to add or update otp
func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) { func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) {
otp, _ := p.GetOTPByEmail(ctx, otpParam.Email) // check if email or phone number is present
if otpParam.Email == "" && otpParam.PhoneNumber == "" {
return nil, errors.New("email or phone_number is required")
}
uniqueField := models.FieldNameEmail
if otpParam.Email == "" && otpParam.PhoneNumber != "" {
uniqueField = models.FieldNamePhoneNumber
}
var otp *models.OTP
if uniqueField == models.FieldNameEmail {
otp, _ = p.GetOTPByEmail(ctx, otpParam.Email)
} else {
otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber)
}
shouldCreate := false shouldCreate := false
if otp == nil { if otp == nil {
shouldCreate = true shouldCreate = true
otp = &models.OTP{ otp = &models.OTP{
ID: uuid.NewString(), ID: uuid.NewString(),
Otp: otpParam.Otp, Otp: otpParam.Otp,
Email: otpParam.Email, Email: otpParam.Email,
ExpiresAt: otpParam.ExpiresAt, PhoneNumber: otpParam.PhoneNumber,
CreatedAt: time.Now().Unix(), ExpiresAt: otpParam.ExpiresAt,
UpdatedAt: time.Now().Unix(), CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
} }
} else { } else {
otp.Otp = otpParam.Otp otp.Otp = otpParam.Otp
@ -32,7 +47,7 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
otp.UpdatedAt = time.Now().Unix() otp.UpdatedAt = time.Now().Unix()
query := "" query := ""
if shouldCreate { if shouldCreate {
query = fmt.Sprintf(`INSERT INTO %s (id, email, otp, expires_at, created_at, updated_at) VALUES ('%s', '%s', '%s', %d, %d, %d)`, KeySpace+"."+models.Collections.OTP, otp.ID, otp.Email, otp.Otp, otp.ExpiresAt, otp.CreatedAt, otp.UpdatedAt) query = fmt.Sprintf(`INSERT INTO %s (id, email, phone_number, otp, expires_at, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)`, KeySpace+"."+models.Collections.OTP, otp.ID, otp.Email, otp.PhoneNumber, otp.Otp, otp.ExpiresAt, otp.CreatedAt, otp.UpdatedAt)
} else { } else {
query = fmt.Sprintf(`UPDATE %s SET otp = '%s', expires_at = %d, updated_at = %d WHERE id = '%s'`, KeySpace+"."+models.Collections.OTP, otp.Otp, otp.ExpiresAt, otp.UpdatedAt, otp.ID) query = fmt.Sprintf(`UPDATE %s SET otp = '%s', expires_at = %d, updated_at = %d WHERE id = '%s'`, KeySpace+"."+models.Collections.OTP, otp.Otp, otp.ExpiresAt, otp.UpdatedAt, otp.ID)
} }
@ -48,8 +63,19 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
// GetOTPByEmail to get otp for a given email address // GetOTPByEmail to get otp for a given email address
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) { func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
var otp models.OTP var otp models.OTP
query := fmt.Sprintf(`SELECT id, email, otp, expires_at, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.OTP, emailAddress) query := fmt.Sprintf(`SELECT id, email, phone_number, otp, expires_at, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.OTP, emailAddress)
err := p.db.Query(query).Consistency(gocql.One).Scan(&otp.ID, &otp.Email, &otp.Otp, &otp.ExpiresAt, &otp.CreatedAt, &otp.UpdatedAt) err := p.db.Query(query).Consistency(gocql.One).Scan(&otp.ID, &otp.Email, &otp.PhoneNumber, &otp.Otp, &otp.ExpiresAt, &otp.CreatedAt, &otp.UpdatedAt)
if err != nil {
return nil, err
}
return &otp, nil
}
// GetOTPByPhoneNumber to get otp for a given phone number
func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) {
var otp models.OTP
query := fmt.Sprintf(`SELECT id, email, phone_number, otp, expires_at, created_at, updated_at FROM %s WHERE phone_number = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.OTP, phoneNumber)
err := p.db.Query(query).Consistency(gocql.One).Scan(&otp.ID, &otp.Email, &otp.PhoneNumber, &otp.Otp, &otp.ExpiresAt, &otp.CreatedAt, &otp.UpdatedAt)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -254,7 +254,11 @@ func NewProvider() (*provider, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
otpIndexQueryPhoneNumber := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_otp_phone_number ON %s.%s (phone_number)", KeySpace, models.Collections.OTP)
err = session.Query(otpIndexQueryPhoneNumber).Exec()
if err != nil {
return nil, err
}
return &provider{ return &provider{
db: session, db: session,
}, err }, err

View File

@ -1,22 +0,0 @@
package cassandradb
import (
"context"
"github.com/authorizerdev/authorizer/server/db/models"
)
// UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return nil, nil
}
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
return nil, nil
}
// DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil
}

View File

@ -2,6 +2,7 @@ package couchbase
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"time" "time"
@ -12,24 +13,36 @@ import (
// UpsertOTP to add or update otp // UpsertOTP to add or update otp
func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) { func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) {
otp, _ := p.GetOTPByEmail(ctx, otpParam.Email) // check if email or phone number is present
if otpParam.Email == "" && otpParam.PhoneNumber == "" {
return nil, errors.New("email or phone_number is required")
}
uniqueField := models.FieldNameEmail
if otpParam.Email == "" && otpParam.PhoneNumber != "" {
uniqueField = models.FieldNamePhoneNumber
}
var otp *models.OTP
if uniqueField == models.FieldNameEmail {
otp, _ = p.GetOTPByEmail(ctx, otpParam.Email)
} else {
otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber)
}
shouldCreate := false shouldCreate := false
if otp == nil { if otp == nil {
shouldCreate = true shouldCreate = true
otp = &models.OTP{ otp = &models.OTP{
ID: uuid.NewString(), ID: uuid.NewString(),
Otp: otpParam.Otp, Otp: otpParam.Otp,
Email: otpParam.Email, Email: otpParam.Email,
ExpiresAt: otpParam.ExpiresAt, PhoneNumber: otpParam.PhoneNumber,
CreatedAt: time.Now().Unix(), ExpiresAt: otpParam.ExpiresAt,
UpdatedAt: time.Now().Unix(), CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
} }
} else { } else {
otp.Otp = otpParam.Otp otp.Otp = otpParam.Otp
otp.ExpiresAt = otpParam.ExpiresAt otp.ExpiresAt = otpParam.ExpiresAt
} }
otp.UpdatedAt = time.Now().Unix() otp.UpdatedAt = time.Now().Unix()
if shouldCreate { if shouldCreate {
insertOpt := gocb.InsertOptions{ insertOpt := gocb.InsertOptions{
@ -54,7 +67,7 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
// GetOTPByEmail to get otp for a given email address // GetOTPByEmail to get otp for a given email address
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) { func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
otp := models.OTP{} otp := models.OTP{}
query := fmt.Sprintf(`SELECT _id, email, otp, expires_at, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1`, p.scopeName, models.Collections.OTP) query := fmt.Sprintf(`SELECT _id, email, phone_number, otp, expires_at, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1`, p.scopeName, models.Collections.OTP)
q, err := p.db.Query(query, &gocb.QueryOptions{ q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
PositionalParameters: []interface{}{emailAddress}, PositionalParameters: []interface{}{emailAddress},
@ -63,11 +76,27 @@ func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*mod
return nil, err return nil, err
} }
err = q.One(&otp) err = q.One(&otp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &otp, nil
}
// GetOTPByPhoneNumber to get otp for a given phone number
func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) {
otp := models.OTP{}
query := fmt.Sprintf(`SELECT _id, email, phone_number, otp, expires_at, created_at, updated_at FROM %s.%s WHERE phone_number = $1 LIMIT 1`, p.scopeName, models.Collections.OTP)
q, err := p.db.Query(query, &gocb.QueryOptions{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
PositionalParameters: []interface{}{phoneNumber},
})
if err != nil {
return nil, err
}
err = q.One(&otp)
if err != nil {
return nil, err
}
return &otp, nil return &otp, nil
} }

View File

@ -166,5 +166,9 @@ func GetIndex(scopeName string) map[string][]string {
otpIndex1 := fmt.Sprintf("CREATE INDEX OTPEmailIndex ON %s.%s(email)", scopeName, models.Collections.OTP) otpIndex1 := fmt.Sprintf("CREATE INDEX OTPEmailIndex ON %s.%s(email)", scopeName, models.Collections.OTP)
indices[models.Collections.OTP] = []string{otpIndex1} indices[models.Collections.OTP] = []string{otpIndex1}
// OTP index
otpIndex2 := fmt.Sprintf("CREATE INDEX OTPPhoneNumberIndex ON %s.%s(phone_number)", scopeName, models.Collections.OTP)
indices[models.Collections.OTP] = []string{otpIndex2}
return indices return indices
} }

View File

@ -1,22 +0,0 @@
package couchbase
import (
"context"
"github.com/authorizerdev/authorizer/server/db/models"
)
// UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return nil, nil
}
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
return nil, nil
}
// DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil
}

View File

@ -11,27 +11,39 @@ import (
// UpsertOTP to add or update otp // UpsertOTP to add or update otp
func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) { func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) {
otp, _ := p.GetOTPByEmail(ctx, otpParam.Email) // check if email or phone number is present
if otpParam.Email == "" && otpParam.PhoneNumber == "" {
return nil, errors.New("email or phone_number is required")
}
uniqueField := models.FieldNameEmail
if otpParam.Email == "" && otpParam.PhoneNumber != "" {
uniqueField = models.FieldNamePhoneNumber
}
var otp *models.OTP
if uniqueField == models.FieldNameEmail {
otp, _ = p.GetOTPByEmail(ctx, otpParam.Email)
} else {
otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber)
}
shouldCreate := false shouldCreate := false
if otp == nil { if otp == nil {
id := uuid.NewString() id := uuid.NewString()
otp = &models.OTP{ otp = &models.OTP{
ID: id, ID: id,
Key: id, Key: id,
Otp: otpParam.Otp, Otp: otpParam.Otp,
Email: otpParam.Email, Email: otpParam.Email,
ExpiresAt: otpParam.ExpiresAt, PhoneNumber: otpParam.PhoneNumber,
CreatedAt: time.Now().Unix(), ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
} }
shouldCreate = true shouldCreate = true
} else { } else {
otp.Otp = otpParam.Otp otp.Otp = otpParam.Otp
otp.ExpiresAt = otpParam.ExpiresAt otp.ExpiresAt = otpParam.ExpiresAt
} }
collection := p.db.Table(models.Collections.OTP) collection := p.db.Table(models.Collections.OTP)
otp.UpdatedAt = time.Now().Unix() otp.UpdatedAt = time.Now().Unix()
var err error var err error
if shouldCreate { if shouldCreate {
err = collection.Put(otp).RunWithContext(ctx) err = collection.Put(otp).RunWithContext(ctx)
@ -41,7 +53,6 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
if err != nil { if err != nil {
return nil, err return nil, err
} }
return otp, nil return otp, nil
} }
@ -49,20 +60,32 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) { func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
var otps []models.OTP var otps []models.OTP
var otp models.OTP var otp models.OTP
collection := p.db.Table(models.Collections.OTP) collection := p.db.Table(models.Collections.OTP)
err := collection.Scan().Index("email").Filter("'email' = ?", emailAddress).Limit(1).AllWithContext(ctx, &otps) err := collection.Scan().Index("email").Filter("'email' = ?", emailAddress).Limit(1).AllWithContext(ctx, &otps)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(otps) > 0 { if len(otps) > 0 {
otp = otps[0] otp = otps[0]
return &otp, nil return &otp, nil
} else {
return nil, errors.New("no docuemnt found")
} }
return nil, errors.New("no docuemnt found")
}
// GetOTPByPhoneNumber to get otp for a given phone number
func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) {
var otps []models.OTP
var otp models.OTP
collection := p.db.Table(models.Collections.OTP)
err := collection.Scan().Index("phone_number").Filter("'phone_number' = ?", phoneNumber).Limit(1).AllWithContext(ctx, &otps)
if err != nil {
return nil, err
}
if len(otps) > 0 {
otp = otps[0]
return &otp, nil
}
return nil, errors.New("no docuemnt found")
} }
// DeleteOTP to delete otp // DeleteOTP to delete otp
@ -75,6 +98,5 @@ func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
return err return err
} }
} }
return nil return nil
} }

View File

@ -1,22 +0,0 @@
package dynamodb
import (
"context"
"github.com/authorizerdev/authorizer/server/db/models"
)
// UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return nil, nil
}
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
return nil, nil
}
// DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil
}

View File

@ -13,29 +13,31 @@ import (
// UpsertOTP to add or update otp // UpsertOTP to add or update otp
func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) { func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) {
// check if email or phone number is present
if otpParam.Email == "" && otpParam.PhoneNumber == "" {
return nil, errors.New("email or phone_number is required")
}
// check if email or phone number is present // check if email or phone number is present
if otpParam.Email == "" && otpParam.PhoneNumber == "" { if otpParam.Email == "" && otpParam.PhoneNumber == "" {
return nil, errors.New("email or phone_number is required") return nil, errors.New("email or phone_number is required")
} }
uniqueField := models.FieldNameEmail uniqueField := models.FieldNameEmail
if otp.Email == "" && otp.PhoneNumber != "" { if otpParam.Email == "" && otpParam.PhoneNumber != "" {
uniqueField = models.FieldNamePhoneNumber uniqueField = models.FieldNamePhoneNumber
} }
otp, _ := p.GetOTPByEmail(ctx, otpParam.Email) var otp *models.OTP
if uniqueField == models.FieldNameEmail {
otp, _ = p.GetOTPByEmail(ctx, otpParam.Email)
} else {
otp, _ = p.GetOTPByPhoneNumber(ctx, otpParam.PhoneNumber)
}
shouldCreate := false shouldCreate := false
if otp == nil { if otp == nil {
id := uuid.NewString() id := uuid.NewString()
otp = &models.OTP{ otp = &models.OTP{
ID: id, ID: id,
Key: id, Key: id,
Otp: otpParam.Otp, Otp: otpParam.Otp,
Email: otpParam.Email, Email: otpParam.Email,
ExpiresAt: otpParam.ExpiresAt, PhoneNumber: otpParam.PhoneNumber,
CreatedAt: time.Now().Unix(), ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
} }
shouldCreate = true shouldCreate = true
} else { } else {
@ -54,20 +56,28 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
if err != nil { if err != nil {
return nil, err return nil, err
} }
return otp, nil return otp, nil
} }
// GetOTPByEmail to get otp for a given email address // GetOTPByEmail to get otp for a given email address
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) { func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
var otp models.OTP var otp models.OTP
otpCollection := p.db.Collection(models.Collections.OTP, options.Collection()) otpCollection := p.db.Collection(models.Collections.OTP, options.Collection())
err := otpCollection.FindOne(ctx, bson.M{"email": emailAddress}).Decode(&otp) err := otpCollection.FindOne(ctx, bson.M{"email": emailAddress}).Decode(&otp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &otp, nil
}
// GetOTPByPhoneNumber to get otp for a given phone number
func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) {
var otp models.OTP
otpCollection := p.db.Collection(models.Collections.OTP, options.Collection())
err := otpCollection.FindOne(ctx, bson.M{"phone_number": phoneNumber}).Decode(&otp)
if err != nil {
return nil, err
}
return &otp, nil return &otp, nil
} }

View File

@ -125,15 +125,6 @@ func NewProvider() (*provider, error) {
}, },
}, options.CreateIndexes()) }, options.CreateIndexes())
mongodb.CreateCollection(ctx, models.Collections.SMSVerificationRequest, options.CreateCollection())
smsCollection := mongodb.Collection(models.Collections.SMSVerificationRequest, options.Collection())
smsCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
{
Keys: bson.M{"phone_number": 1},
Options: options.Index().SetUnique(true).SetSparse(true),
},
}, options.CreateIndexes())
return &provider{ return &provider{
db: mongodb, db: mongodb,
}, nil }, nil

View File

@ -1,66 +0,0 @@
package mongodb
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
smsVerificationRequest, _ := p.GetCodeByPhone(ctx, smsRequest.PhoneNumber)
// Boolean to check if we should create a new record or update the existing one
shouldCreate := false
if smsVerificationRequest == nil {
id := uuid.NewString()
smsVerificationRequest = &models.SMSVerificationRequest{
ID: id,
CreatedAt: time.Now().Unix(),
Code: smsRequest.Code,
PhoneNumber: smsRequest.PhoneNumber,
CodeExpiresAt: smsRequest.CodeExpiresAt,
}
shouldCreate = true
}
var err error
smsVerificationRequest.UpdatedAt = time.Now().Unix()
smsRequestCollection := p.db.Collection(models.Collections.SMSVerificationRequest, options.Collection())
if shouldCreate {
_, err = smsRequestCollection.InsertOne(ctx, smsVerificationRequest)
} else {
_, err = smsRequestCollection.UpdateOne(ctx, bson.M{"phone_number": bson.M{"$eq": smsRequest.PhoneNumber}}, bson.M{"$set": smsVerificationRequest}, options.MergeUpdateOptions())
}
if err != nil {
return nil, err
}
return smsVerificationRequest, nil
}
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
var smsVerificationRequest models.SMSVerificationRequest
smsRequestCollection := p.db.Collection(models.Collections.SMSVerificationRequest, options.Collection())
err := smsRequestCollection.FindOne(ctx, bson.M{"phone_number": phoneNumber}).Decode(&smsVerificationRequest)
if err != nil {
return nil, err
}
return &smsVerificationRequest, nil
}
// DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
smsVerificationRequests := p.db.Collection(models.Collections.SMSVerificationRequest, options.Collection())
_, err := smsVerificationRequests.DeleteOne(nil, bson.M{"_id": smsRequest.ID}, options.Delete())
if err != nil {
return err
}
return nil
}

View File

@ -16,6 +16,11 @@ func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*mod
return nil, nil return nil, nil
} }
// GetOTPByPhoneNumber to get otp for a given phone number
func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) {
return nil, nil
}
// DeleteOTP to delete otp // DeleteOTP to delete otp
func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error { func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
return nil return nil

View File

@ -1,22 +0,0 @@
package provider_template
import (
"context"
"github.com/authorizerdev/authorizer/server/db/models"
)
// UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return nil, nil
}
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
return nil, nil
}
// DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil
}

View File

@ -86,11 +86,4 @@ 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
// UpsertSMSRequest adds/updates SMS verification request
UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error)
// GetCodeByPhone to get code for a given phone number
GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error)
// DeleteSMSRequest to delete SMS verification request
DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error
} }

View File

@ -77,7 +77,7 @@ func NewProvider() (*provider, error) {
logrus.Debug("Failed to drop phone number constraint:", err) logrus.Debug("Failed to drop phone number constraint:", err)
} }
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{}, models.EmailTemplate{}, &models.OTP{}, &models.SMSVerificationRequest{}) err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{}, models.EmailTemplate{}, &models.OTP{})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,49 +0,0 @@
package sql
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
"gorm.io/gorm/clause"
)
// UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
if smsRequest.ID == "" {
smsRequest.ID = uuid.New().String()
}
smsRequest.CreatedAt = time.Now().Unix()
smsRequest.UpdatedAt = time.Now().Unix()
res := p.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "phone_number"}},
DoUpdates: clause.AssignmentColumns([]string{"code", "code_expires_at", "updated_at"}),
}).Create(smsRequest)
if res.Error != nil {
return nil, res.Error
}
return smsRequest, nil
}
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
var sms_verification_request models.SMSVerificationRequest
result := p.db.Where("phone_number = ?", phoneNumber).First(&sms_verification_request)
if result.Error != nil {
return nil, result.Error
}
return &sms_verification_request, nil
}
// DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
result := p.db.Delete(&models.SMSVerificationRequest{
ID: smsRequest.ID,
})
if result.Error != nil {
return result.Error
}
return nil
}

View File

@ -198,7 +198,6 @@ type ComplexityRoot struct {
UpdateUser func(childComplexity int, params model.UpdateUserInput) int UpdateUser func(childComplexity int, params model.UpdateUserInput) int
UpdateWebhook func(childComplexity int, params model.UpdateWebhookRequest) int UpdateWebhook func(childComplexity int, params model.UpdateWebhookRequest) int
VerifyEmail func(childComplexity int, params model.VerifyEmailInput) int VerifyEmail func(childComplexity int, params model.VerifyEmailInput) int
VerifyMobile func(childComplexity int, params model.VerifyMobileRequest) int
VerifyOtp func(childComplexity int, params model.VerifyOTPRequest) int VerifyOtp func(childComplexity int, params model.VerifyOTPRequest) int
} }
@ -344,7 +343,6 @@ type MutationResolver interface {
Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error)
VerifyOtp(ctx context.Context, params model.VerifyOTPRequest) (*model.AuthResponse, error) VerifyOtp(ctx context.Context, params model.VerifyOTPRequest) (*model.AuthResponse, error)
ResendOtp(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) ResendOtp(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error)
VerifyMobile(ctx context.Context, params model.VerifyMobileRequest) (*model.AuthResponse, error)
DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error)
UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error)
AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error)
@ -1438,18 +1436,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.VerifyEmail(childComplexity, args["params"].(model.VerifyEmailInput)), true return e.complexity.Mutation.VerifyEmail(childComplexity, args["params"].(model.VerifyEmailInput)), true
case "Mutation.verify_mobile":
if e.complexity.Mutation.VerifyMobile == nil {
break
}
args, err := ec.field_Mutation_verify_mobile_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.VerifyMobile(childComplexity, args["params"].(model.VerifyMobileRequest)), true
case "Mutation.verify_otp": case "Mutation.verify_otp":
if e.complexity.Mutation.VerifyOtp == nil { if e.complexity.Mutation.VerifyOtp == nil {
break break
@ -2120,7 +2106,6 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputValidateJWTTokenInput, ec.unmarshalInputValidateJWTTokenInput,
ec.unmarshalInputValidateSessionInput, ec.unmarshalInputValidateSessionInput,
ec.unmarshalInputVerifyEmailInput, ec.unmarshalInputVerifyEmailInput,
ec.unmarshalInputVerifyMobileRequest,
ec.unmarshalInputVerifyOTPRequest, ec.unmarshalInputVerifyOTPRequest,
ec.unmarshalInputWebhookRequest, ec.unmarshalInputWebhookRequest,
) )
@ -2269,11 +2254,6 @@ type SMSVerificationRequests {
updated_at: Int64 updated_at: Int64
} }
input VerifyMobileRequest {
phone_number: String!
code: String!
}
type Error { type Error {
message: String! message: String!
reason: String! reason: String!
@ -2728,7 +2708,9 @@ input DeleteEmailTemplateRequest {
} }
input VerifyOTPRequest { input VerifyOTPRequest {
email: String! # either email or phone_number is required
email: String
phone_number: String
otp: String! otp: String!
# state is used for authorization code grant flow # state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login # it is used to get code for an on-going auth process during login
@ -2764,7 +2746,6 @@ type Mutation {
revoke(params: OAuthRevokeInput!): Response! revoke(params: OAuthRevokeInput!): Response!
verify_otp(params: VerifyOTPRequest!): AuthResponse! verify_otp(params: VerifyOTPRequest!): AuthResponse!
resend_otp(params: ResendOTPRequest!): Response! resend_otp(params: ResendOTPRequest!): Response!
verify_mobile(params: VerifyMobileRequest!): AuthResponse!
# admin only apis # admin only apis
_delete_user(params: DeleteUserInput!): Response! _delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User! _update_user(params: UpdateUserInput!): User!
@ -3230,21 +3211,6 @@ func (ec *executionContext) field_Mutation_verify_email_args(ctx context.Context
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_verify_mobile_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 model.VerifyMobileRequest
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalNVerifyMobileRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerifyMobileRequest(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_verify_otp_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Mutation_verify_otp_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@ -8635,77 +8601,6 @@ func (ec *executionContext) fieldContext_Mutation_resend_otp(ctx context.Context
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_verify_mobile(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_verify_mobile(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().VerifyMobile(rctx, fc.Args["params"].(model.VerifyMobileRequest))
})
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_mobile(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_otp_screen":
return ec.fieldContext_AuthResponse_should_show_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)
}
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_mobile_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) { func (ec *executionContext) _Mutation__delete_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation__delete_user(ctx, field) fc, err := ec.fieldContext_Mutation__delete_user(ctx, field)
if err != nil { if err != nil {
@ -17781,42 +17676,6 @@ func (ec *executionContext) unmarshalInputVerifyEmailInput(ctx context.Context,
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputVerifyMobileRequest(ctx context.Context, obj interface{}) (model.VerifyMobileRequest, error) {
var it model.VerifyMobileRequest
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
fieldsInOrder := [...]string{"phone_number", "code"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "phone_number":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("phone_number"))
it.PhoneNumber, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "code":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("code"))
it.Code, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context, obj interface{}) (model.VerifyOTPRequest, error) { func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context, obj interface{}) (model.VerifyOTPRequest, error) {
var it model.VerifyOTPRequest var it model.VerifyOTPRequest
asMap := map[string]interface{}{} asMap := map[string]interface{}{}
@ -17824,7 +17683,7 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context,
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"email", "otp", "state"} fieldsInOrder := [...]string{"email", "phone_number", "otp", "state"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@ -17835,7 +17694,15 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context,
var err error var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email"))
it.Email, err = ec.unmarshalNString2string(ctx, v) it.Email, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "phone_number":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("phone_number"))
it.PhoneNumber, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
@ -18723,15 +18590,6 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
return ec._Mutation_resend_otp(ctx, field) return ec._Mutation_resend_otp(ctx, field)
}) })
if out.Values[i] == graphql.Null {
invalids++
}
case "verify_mobile":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_verify_mobile(ctx, field)
})
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
@ -20798,11 +20656,6 @@ func (ec *executionContext) unmarshalNVerifyEmailInput2githubᚗcomᚋauthorizer
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)
} }
func (ec *executionContext) unmarshalNVerifyMobileRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerifyMobileRequest(ctx context.Context, v interface{}) (model.VerifyMobileRequest, error) {
res, err := ec.unmarshalInputVerifyMobileRequest(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNVerifyOTPRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerifyOTPRequest(ctx context.Context, v interface{}) (model.VerifyOTPRequest, error) { func (ec *executionContext) unmarshalNVerifyOTPRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerifyOTPRequest(ctx context.Context, v interface{}) (model.VerifyOTPRequest, error) {
res, err := ec.unmarshalInputVerifyOTPRequest(ctx, v) res, err := ec.unmarshalInputVerifyOTPRequest(ctx, v)
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)

View File

@ -486,15 +486,11 @@ type VerifyEmailInput struct {
State *string `json:"state"` State *string `json:"state"`
} }
type VerifyMobileRequest struct {
PhoneNumber string `json:"phone_number"`
Code string `json:"code"`
}
type VerifyOTPRequest struct { type VerifyOTPRequest struct {
Email string `json:"email"` Email *string `json:"email"`
Otp string `json:"otp"` PhoneNumber *string `json:"phone_number"`
State *string `json:"state"` Otp string `json:"otp"`
State *string `json:"state"`
} }
type Webhook struct { type Webhook struct {

View File

@ -84,11 +84,6 @@ type SMSVerificationRequests {
updated_at: Int64 updated_at: Int64
} }
input VerifyMobileRequest {
phone_number: String!
code: String!
}
type Error { type Error {
message: String! message: String!
reason: String! reason: String!
@ -543,7 +538,9 @@ input DeleteEmailTemplateRequest {
} }
input VerifyOTPRequest { input VerifyOTPRequest {
email: String! # either email or phone_number is required
email: String
phone_number: String
otp: String! otp: String!
# state is used for authorization code grant flow # state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login # it is used to get code for an on-going auth process during login
@ -579,7 +576,6 @@ type Mutation {
revoke(params: OAuthRevokeInput!): Response! revoke(params: OAuthRevokeInput!): Response!
verify_otp(params: VerifyOTPRequest!): AuthResponse! verify_otp(params: VerifyOTPRequest!): AuthResponse!
resend_otp(params: ResendOTPRequest!): Response! resend_otp(params: ResendOTPRequest!): Response!
verify_mobile(params: VerifyMobileRequest!): AuthResponse!
# admin only apis # admin only apis
_delete_user(params: DeleteUserInput!): Response! _delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User! _update_user(params: UpdateUserInput!): User!

View File

@ -81,11 +81,6 @@ func (r *mutationResolver) ResendOtp(ctx context.Context, params model.ResendOTP
return resolvers.ResendOTPResolver(ctx, params) return resolvers.ResendOTPResolver(ctx, params)
} }
// VerifyMobile is the resolver for the verify_mobile field.
func (r *mutationResolver) VerifyMobile(ctx context.Context, params model.VerifyMobileRequest) (*model.AuthResponse, error) {
return resolvers.VerifyMobileResolver(ctx, params)
}
// DeleteUser is the resolver for the _delete_user field. // DeleteUser is the resolver for the _delete_user field.
func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) { func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) {
return resolvers.DeleteUserResolver(ctx, params) return resolvers.DeleteUserResolver(ctx, params)

View File

@ -17,9 +17,9 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs" "github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/smsproviders"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/smsproviders"
"github.com/authorizerdev/authorizer/server/validators" "github.com/authorizerdev/authorizer/server/validators"
) )
@ -133,8 +133,8 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput)
} }
user := models.User{ user := models.User{
Email: emailInput, Email: emailInput,
PhoneNumber: &mobile, PhoneNumber: &mobile,
} }
user.Roles = strings.Join(inputRoles, ",") user.Roles = strings.Join(inputRoles, ",")
@ -213,10 +213,10 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput)
} }
go func() { go func() {
db.Provider.UpsertSMSRequest(ctx, &models.SMSVerificationRequest{ db.Provider.UpsertOTP(ctx, &models.OTP{
PhoneNumber: mobile, PhoneNumber: mobile,
Code: smsCode, Otp: smsCode,
CodeExpiresAt: time.Now().Add(duration).Unix(), ExpiresAt: time.Now().Add(duration).Unix(),
}) })
smsproviders.SendSMS(mobile, smsBody.String()) smsproviders.SendSMS(mobile, smsBody.String())
}() }()

View File

@ -1,62 +0,0 @@
package resolvers
import (
"fmt"
"context"
"time"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/db"
log "github.com/sirupsen/logrus"
)
func VerifyMobileResolver(ctx context.Context, params model.VerifyMobileRequest) (*model.AuthResponse, error) {
var res *model.AuthResponse
_, err := utils.GinContextFromContext(ctx)
if err != nil {
log.Debug("Failed to get GinContext: ", err)
return res, err
}
smsVerificationRequest, err := db.Provider.GetCodeByPhone(ctx, params.PhoneNumber)
if err != nil {
log.Debug("Failed to get sms request by phone: ", err)
return res, err
}
if smsVerificationRequest.Code != params.Code {
log.Debug("Failed to verify request: bad credentials")
return res, fmt.Errorf(`bad credentials`)
}
expiresIn := smsVerificationRequest.CodeExpiresAt - time.Now().Unix()
if expiresIn < 0 {
log.Debug("Failed to verify sms request: Timeout")
return res, fmt.Errorf("time expired")
}
res = &model.AuthResponse{
Message: "successful",
}
user, err := db.Provider.GetUserByPhoneNumber(ctx, params.PhoneNumber)
if user.PhoneNumberVerifiedAt == nil {
now := time.Now().Unix()
user.PhoneNumberVerifiedAt = &now
}
_, err = db.Provider.UpdateUser(ctx, *user)
if err != nil {
log.Debug("Failed to update user: ", err)
return res, err
}
err = db.Provider.DeleteSMSRequest(ctx, smsVerificationRequest)
if err != nil {
log.Debug("Failed to delete sms request: ", err.Error())
}
return res, err
}

View File

@ -27,36 +27,53 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod
log.Debug("Failed to get GinContext: ", err) log.Debug("Failed to get GinContext: ", err)
return res, err return res, err
} }
if refs.StringValue(params.Email) == "" && refs.StringValue(params.PhoneNumber) == "" {
otp, err := db.Provider.GetOTPByEmail(ctx, params.Email) log.Debug("Email or phone number is required")
if err != nil { return res, fmt.Errorf(`email or phone_number is required`)
log.Debug("Failed to get otp request by email: ", err)
return res, fmt.Errorf(`invalid email: %s`, err.Error())
} }
currentField := models.FieldNameEmail
if refs.StringValue(params.Email) == "" {
currentField = models.FieldNamePhoneNumber
}
var otp *models.OTP
if currentField == models.FieldNameEmail {
otp, err = db.Provider.GetOTPByEmail(ctx, refs.StringValue(params.Email))
} else {
otp, err = db.Provider.GetOTPByPhoneNumber(ctx, refs.StringValue(params.PhoneNumber))
}
if otp == nil && err != nil {
log.Debugf("Failed to get otp request for %s: %s", currentField, err.Error())
return res, fmt.Errorf(`invalid %s: %s`, currentField, err.Error())
}
if params.Otp != otp.Otp { if params.Otp != otp.Otp {
log.Debug("Failed to verify otp request: Incorrect value") log.Debug("Failed to verify otp request: Incorrect value")
return res, fmt.Errorf(`invalid otp`) return res, fmt.Errorf(`invalid otp`)
} }
expiresIn := otp.ExpiresAt - time.Now().Unix() expiresIn := otp.ExpiresAt - time.Now().Unix()
if expiresIn < 0 { if expiresIn < 0 {
log.Debug("Failed to verify otp request: Timeout") log.Debug("Failed to verify otp request: Timeout")
return res, fmt.Errorf("otp expired") return res, fmt.Errorf("otp expired")
} }
var user models.User
user, err := db.Provider.GetUserByEmail(ctx, params.Email) if currentField == models.FieldNameEmail {
if err != nil { user, err = db.Provider.GetUserByEmail(ctx, refs.StringValue(params.Email))
} else {
// TODO fix after refs of db providers are fixed
var u *models.User
u, err = db.Provider.GetUserByPhoneNumber(ctx, refs.StringValue(params.PhoneNumber))
user = *u
}
if user.ID == "" && err != nil {
log.Debug("Failed to get user by email: ", err) log.Debug("Failed to get user by email: ", err)
return res, err return res, err
} }
isSignUp := user.EmailVerifiedAt == nil && user.PhoneNumberVerifiedAt == nil
isSignUp := user.EmailVerifiedAt == nil
// TODO - Add Login method in DB when we introduce OTP for social media login // TODO - Add Login method in DB when we introduce OTP for social media login
loginMethod := constants.AuthRecipeMethodBasicAuth loginMethod := constants.AuthRecipeMethodBasicAuth
if currentField == models.FieldNamePhoneNumber {
loginMethod = constants.AuthRecipeMethodMobileOTP
}
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")
scope := []string{"openid", "email", "profile"} scope := []string{"openid", "email", "profile"}
code := "" code := ""

View File

@ -5,8 +5,8 @@ import (
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/refs" "github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -54,13 +54,13 @@ func mobileLoginTests(t *testing.T, s TestSetup) {
assert.NotNil(t, err, "should fail because phone is not verified") assert.NotNil(t, err, "should fail because phone is not verified")
assert.Nil(t, res) assert.Nil(t, res)
smsRequest, err := db.Provider.GetCodeByPhone(ctx, phoneNumber) smsRequest, err := db.Provider.GetOTPByPhoneNumber(ctx, phoneNumber)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, smsRequest.Code) assert.NotEmpty(t, smsRequest.Otp)
verifySMSRequest, err := resolvers.VerifyMobileResolver(ctx, model.VerifyMobileRequest{ verifySMSRequest, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{
PhoneNumber: phoneNumber, PhoneNumber: &phoneNumber,
Code: smsRequest.Code, Otp: smsRequest.Otp,
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.NotEqual(t, verifySMSRequest.Message, "", "message should not be empty") assert.NotEqual(t, verifySMSRequest.Message, "", "message should not be empty")

View File

@ -84,13 +84,13 @@ func resendOTPTest(t *testing.T, s TestSetup) {
// Should return error for older otp // Should return error for older otp
verifyOtpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ verifyOtpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{
Email: email, Email: &email,
Otp: otp.Otp, Otp: otp.Otp,
}) })
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, verifyOtpRes) assert.Nil(t, verifyOtpRes)
verifyOtpRes, err = resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ verifyOtpRes, err = resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{
Email: email, Email: &email,
Otp: newOtp.Otp, Otp: newOtp.Otp,
}) })
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -135,7 +135,6 @@ func TestResolvers(t *testing.T) {
validateJwtTokenTest(t, s) validateJwtTokenTest(t, s)
verifyOTPTest(t, s) verifyOTPTest(t, s)
resendOTPTest(t, s) resendOTPTest(t, s)
verifyMobileTest(t, s)
validateSessionTests(t, s) validateSessionTests(t, s)
updateAllUsersTest(t, s) updateAllUsersTest(t, s)

View File

@ -1,79 +0,0 @@
package test
import (
"strings"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func verifyMobileTest(t *testing.T, s TestSetup) {
t.Helper()
t.Run(`should verify mobile`, func(t *testing.T) {
_, ctx := createContext(s)
email := "mobile_verification." + s.TestInfo.Email
phoneNumber := "2234567890"
signUpRes, err := resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
Email: refs.NewStringRef(email),
PhoneNumber: phoneNumber,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NoError(t, err)
assert.NotNil(t, signUpRes)
assert.Equal(t, email, signUpRes.User.Email)
assert.Equal(t, phoneNumber, refs.StringValue(signUpRes.User.PhoneNumber))
assert.True(t, strings.Contains(signUpRes.User.SignupMethods, constants.AuthRecipeMethodMobileBasicAuth))
assert.Len(t, strings.Split(signUpRes.User.SignupMethods, ","), 1)
res, err := resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{
PhoneNumber: phoneNumber,
Password: "random_test",
})
assert.Error(t, err)
assert.Nil(t, res)
// should fail because phone is not verified
res, err = resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{
PhoneNumber: phoneNumber,
Password: s.TestInfo.Password,
})
assert.NotNil(t, err, "should fail because phone is not verified")
assert.Nil(t, res)
// get code from db
smsRequest, err := db.Provider.GetCodeByPhone(ctx, phoneNumber)
assert.NoError(t, err)
assert.NotEmpty(t, smsRequest.Code)
// throw an error if the code is not correct
verifySMSRequest, err := resolvers.VerifyMobileResolver(ctx, model.VerifyMobileRequest{
PhoneNumber: phoneNumber,
Code: "rand_12@1",
})
assert.NotNil(t, err, "should fail because of bad credentials")
assert.Nil(t, verifySMSRequest)
verifySMSRequest, err = resolvers.VerifyMobileResolver(ctx, model.VerifyMobileRequest{
PhoneNumber: phoneNumber,
Code: smsRequest.Code,
})
assert.Nil(t, err)
assert.NotEqual(t, verifySMSRequest.Message, "", "message should not be empty")
res, err = resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{
PhoneNumber: phoneNumber,
Password: s.TestInfo.Password,
})
assert.NoError(t, err)
assert.NotEmpty(t, res.AccessToken)
assert.NotEmpty(t, res.IDToken)
cleanData(email)
})
}

View File

@ -65,7 +65,7 @@ func verifyOTPTest(t *testing.T, s TestSetup) {
assert.NotEmpty(t, otp.Otp) assert.NotEmpty(t, otp.Otp)
verifyOtpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ verifyOtpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{
Email: email, Email: &email,
Otp: otp.Otp, Otp: otp.Otp,
}) })
assert.Nil(t, err) assert.Nil(t, err)