Compare commits

..

14 Commits

Author SHA1 Message Date
Lakhan Samani
c80b0d7028 Merge pull request #368 from authorizerdev/fix-sms-verification-for-alldb
Move sms verificaiton to otp models
2023-07-23 10:04:18 +05:30
Lakhan Samani
55fc4b2608 Update resend otp 2023-07-23 10:03:37 +05:30
Lakhan Samani
fac333e195 fix: tests for otp refactor 2023-07-23 07:29:29 +05:30
Lakhan Samani
edb5412c17 Fix tests 2023-07-18 22:50:23 +05:30
Lakhan Samani
87a962504f Increase timeout for redis 2023-07-16 22:57:56 +05:30
Lakhan Samani
d04f79557a Refactor code for otp 2023-07-13 11:39:22 +05:30
Lakhan Samani
c20e9b810a Merge branch 'main' of https://github.com/authorizerdev/authorizer into fix-sms-verification-for-alldb 2023-07-12 22:16:07 +05:30
Lakhan Samani
8d145bd5fe Merge pull request #369 from authorizerdev/feat-add-validate-cookie-api
feat: add resolver to validate browser session
2023-07-12 22:13:47 +05:30
Lakhan Samani
6fa0ad1809 feat: add resolver to validate browser session 2023-07-12 22:12:17 +05:30
Lakhan Samani
abe809ca68 [draft] Move sms verificaiton to otp models 2023-07-12 11:24:13 +05:30
Lakhan Samani
07f71e883b Add comments for twillio 2023-07-11 14:49:16 +05:30
Lakhan Samani
6cef9064c3 Update provider template for sms verification 2023-07-11 14:48:37 +05:30
Lakhan Samani
9ae616b6b5 Merge pull request #365 from JokerQyou/patch-1
Fix wrong response_type parsed when missing response_mode
2023-06-30 18:10:31 +05:30
Joker_
356428ea02 Fix wrong response_type parsed when missing response_mode 2023-06-29 23:10:44 +08:00
60 changed files with 1023 additions and 856 deletions

View File

@@ -7,5 +7,9 @@ SMTP_PORT=2525
SMTP_USERNAME=test
SMTP_PASSWORD=test
SENDER_EMAIL="info@authorizer.dev"
TWILIO_API_KEY=test
TWILIO_API_SECRET=test
TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TWILIO_SENDER=909921212112
SENDER_NAME="Authorizer"
AWS_REGION=ap-south-1

View File

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

View File

@@ -66,6 +66,8 @@ const (
EnvKeySenderName = "SENDER_NAME"
// EnvKeyIsEmailServiceEnabled key for env variable IS_EMAIL_SERVICE_ENABLED
EnvKeyIsEmailServiceEnabled = "IS_EMAIL_SERVICE_ENABLED"
// EnvKeyIsSMSServiceEnabled key for env variable IS_SMS_SERVICE_ENABLED
EnvKeyIsSMSServiceEnabled = "IS_SMS_SERVICE_ENABLED"
// EnvKeyAppCookieSecure key for env variable APP_COOKIE_SECURE
EnvKeyAppCookieSecure = "APP_COOKIE_SECURE"
// EnvKeyAdminCookieSecure key for env variable ADMIN_COOKIE_SECURE
@@ -158,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"
// EnvKeyDisablePhoneVerification is key for env variable DISABLE_PHONE_VERIFICATION
// this variable is used to disable phone verification
EnvKeyDisablePhoneVerification = "DISABLE_PHONE_VERIFICATION"
// Slice variables
// EnvKeyRoles key for env variable ROLES
@@ -177,12 +182,13 @@ const (
// This env is used for setting default response mode in authorize handler
EnvKeyDefaultAuthorizeResponseMode = "DEFAULT_AUTHORIZE_RESPONSE_MODE"
// Phone verification setting
EnvKeyDisablePhoneVerification = "DISABLE_PHONE_VERIFICATION"
// Twilio env variables
// EnvKeyTwilioAPIKey key for env variable TWILIO_API_KEY
EnvKeyTwilioAPIKey = "TWILIO_API_KEY"
// EnvKeyTwilioAPISecret key for env variable TWILIO_API_SECRET
EnvKeyTwilioAPISecret = "TWILIO_API_SECRET"
// EnvKeyTwilioAccountSID key for env variable TWILIO_ACCOUNT_SID
EnvKeyTwilioAccountSID = "TWILIO_ACCOUNT_SID"
EnvKeyTwilioSenderFrom = "TWILIO_SENDER_FROM"
// EnvKeyTwilioSender key for env variable TWILIO_SENDER
EnvKeyTwilioSender = "TWILIO_SENDER"
)

View File

@@ -2,14 +2,14 @@ package models
// Collections / Tables available for authorizer in the database
type CollectionList struct {
User string
VerificationRequest string
Session string
Env string
Webhook string
WebhookLog string
EmailTemplate string
OTP string
User string
VerificationRequest string
Session string
Env string
Webhook string
WebhookLog string
EmailTemplate string
OTP string
SMSVerificationRequest string
}
@@ -18,14 +18,14 @@ var (
Prefix = "authorizer_"
// Collections / Tables available for authorizer in the database (used for dbs other than gorm)
Collections = CollectionList{
User: Prefix + "users",
VerificationRequest: Prefix + "verification_requests",
Session: Prefix + "sessions",
Env: Prefix + "env",
Webhook: Prefix + "webhooks",
WebhookLog: Prefix + "webhook_logs",
EmailTemplate: Prefix + "email_templates",
OTP: Prefix + "otps",
SMSVerificationRequest: Prefix + "sms_verification_requests",
User: Prefix + "users",
VerificationRequest: Prefix + "verification_requests",
Session: Prefix + "sessions",
Env: Prefix + "env",
Webhook: Prefix + "webhooks",
WebhookLog: Prefix + "webhook_logs",
EmailTemplate: Prefix + "email_templates",
OTP: Prefix + "otps",
SMSVerificationRequest: Prefix + "sms_verification_requests",
}
)

View File

@@ -1,14 +1,22 @@
package models
const (
// FieldName email is the field name for email
FieldNameEmail = "email"
// FieldNamePhoneNumber is the field name for phone number
FieldNamePhoneNumber = "phone_number"
)
// OTP model for database
type OTP 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"`
Email string `gorm:"unique" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"`
Otp string `json:"otp" bson:"otp" cql:"otp" dynamo:"otp"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"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"`
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"`
Email string `gorm:"unique" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"`
PhoneNumber string `gorm:"index:unique_index_phone_number,unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number"`
Otp string `json:"otp" bson:"otp" cql:"otp" dynamo:"otp"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"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"`
}
type Paging struct {

View File

@@ -1,11 +0,0 @@
package models
// SMS verification requests model for database
type SMSVerificationRequest struct {
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 (
"context"
"errors"
"fmt"
"time"
@@ -12,17 +13,31 @@ import (
// UpsertOTP to add or update otp
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
if otp == nil {
id := uuid.NewString()
otp = &models.OTP{
ID: id,
Key: id,
Otp: otpParam.Otp,
Email: otpParam.Email,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
ID: id,
Key: id,
Otp: otpParam.Otp,
Email: otpParam.Email,
PhoneNumber: otpParam.PhoneNumber,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
}
shouldCreate = true
} else {
@@ -67,7 +82,35 @@ func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*mod
for {
if !cursor.HasMore() {
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
}

View File

@@ -225,16 +225,14 @@ func NewProvider() (*provider, error) {
return nil, err
}
}
otpCollection, err := arangodb.Collection(ctx, models.Collections.OTP)
if err != nil {
return nil, err
}
otpCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{
otpCollection.EnsureHashIndex(ctx, []string{models.FieldNameEmail, models.FieldNamePhoneNumber}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
return &provider{
db: arangodb,
}, err

View File

@@ -1,23 +0,0 @@
package arangodb
import (
"context"
"github.com/authorizerdev/authorizer/server/db/models"
)
// SMS verification Request
func (p *provider) UpsertSMSRequest(ctx context.Context, sms_code *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return sms_code, nil
}
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
var sms_verification_request models.SMSVerificationRequest
return &sms_verification_request, nil
}
func(p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil
}

View File

@@ -2,6 +2,7 @@ package cassandradb
import (
"context"
"errors"
"fmt"
"time"
@@ -12,17 +13,31 @@ import (
// UpsertOTP to add or update otp
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
if otp == nil {
shouldCreate = true
otp = &models.OTP{
ID: uuid.NewString(),
Otp: otpParam.Otp,
Email: otpParam.Email,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
ID: uuid.NewString(),
Otp: otpParam.Otp,
Email: otpParam.Email,
PhoneNumber: otpParam.PhoneNumber,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
}
} else {
otp.Otp = otpParam.Otp
@@ -32,7 +47,7 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
otp.UpdatedAt = time.Now().Unix()
query := ""
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 {
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
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
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)
err := p.db.Query(query).Consistency(gocql.One).Scan(&otp.ID, &otp.Email, &otp.Otp, &otp.ExpiresAt, &otp.CreatedAt, &otp.UpdatedAt)
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.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 {
return nil, err
}

View File

@@ -254,7 +254,19 @@ func NewProvider() (*provider, error) {
if err != nil {
return nil, err
}
// Add phone_number column to otp table
otpAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (phone_number text);`, KeySpace, models.Collections.OTP)
err = session.Query(otpAlterQuery).Exec()
if err != nil {
log.Debug("Failed to alter table as column exists: ", err)
// continue
}
// Add phone number index
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{
db: session,
}, err

View File

@@ -1,23 +0,0 @@
package cassandradb
import (
"context"
"github.com/authorizerdev/authorizer/server/db/models"
)
// SMS verification Request
func (p *provider) UpsertSMSRequest(ctx context.Context, sms_code *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return sms_code, nil
}
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
var sms_verification_request models.SMSVerificationRequest
return &sms_verification_request, nil
}
func(p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil
}

View File

@@ -2,6 +2,7 @@ package couchbase
import (
"context"
"errors"
"fmt"
"time"
@@ -12,24 +13,36 @@ import (
// UpsertOTP to add or update otp
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
if otp == nil {
shouldCreate = true
otp = &models.OTP{
ID: uuid.NewString(),
Otp: otpParam.Otp,
Email: otpParam.Email,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
ID: uuid.NewString(),
Otp: otpParam.Otp,
Email: otpParam.Email,
PhoneNumber: otpParam.PhoneNumber,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
}
} else {
otp.Otp = otpParam.Otp
otp.ExpiresAt = otpParam.ExpiresAt
}
otp.UpdatedAt = time.Now().Unix()
if shouldCreate {
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
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
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{
ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
PositionalParameters: []interface{}{emailAddress},
@@ -63,11 +76,27 @@ func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*mod
return nil, err
}
err = q.One(&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) {
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
}

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

View File

@@ -11,24 +11,19 @@ import (
func GetSetFields(webhookMap map[string]interface{}) (string, map[string]interface{}) {
params := make(map[string]interface{}, 1)
updateFields := ""
for key, value := range webhookMap {
if key == "_id" {
continue
}
if key == "_key" {
continue
}
if value == nil {
updateFields += fmt.Sprintf("%s=$%s,", key, key)
params[key] = "null"
continue
}
valueType := reflect.TypeOf(value)
if valueType.Name() == "string" {
updateFields += fmt.Sprintf("%s = $%s, ", key, key)

View File

@@ -1,23 +0,0 @@
package couchbase
import (
"context"
"github.com/authorizerdev/authorizer/server/db/models"
)
// SMS verification Request
func (p *provider) UpsertSMSRequest(ctx context.Context, sms_code *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return sms_code, nil
}
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
var sms_verification_request models.SMSVerificationRequest
return &sms_verification_request, nil
}
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
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
if otp == nil {
id := uuid.NewString()
otp = &models.OTP{
ID: id,
Key: id,
Otp: otpParam.Otp,
Email: otpParam.Email,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
ID: id,
Key: id,
Otp: otpParam.Otp,
Email: otpParam.Email,
PhoneNumber: otpParam.PhoneNumber,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
}
shouldCreate = true
} else {
otp.Otp = otpParam.Otp
otp.ExpiresAt = otpParam.ExpiresAt
}
collection := p.db.Table(models.Collections.OTP)
otp.UpdatedAt = time.Now().Unix()
var err error
if shouldCreate {
err = collection.Put(otp).RunWithContext(ctx)
@@ -41,7 +53,6 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
if err != nil {
return nil, err
}
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) {
var otps []models.OTP
var otp models.OTP
collection := p.db.Table(models.Collections.OTP)
err := collection.Scan().Index("email").Filter("'email' = ?", emailAddress).Limit(1).AllWithContext(ctx, &otps)
if err != nil {
return nil, err
}
if len(otps) > 0 {
otp = otps[0]
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().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
@@ -75,6 +98,5 @@ func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
return err
}
}
return nil
}

View File

@@ -31,11 +31,11 @@ func NewProvider() (*provider, error) {
if awsRegion != "" {
config.Region = aws.String(awsRegion)
}
// custom awsAccessKeyID, awsSecretAccessKey took first priority, if not then fetch config from aws credentials
if awsAccessKeyID != "" && awsSecretAccessKey != "" {
config.Credentials = credentials.NewStaticCredentials(awsAccessKeyID, awsSecretAccessKey, "")
} else if dbURL != "" {
log.Debug("Tring to use database url for dynamodb")
// static config in case of testing or local-setup
config.Credentials = credentials.NewStaticCredentials("key", "key", "")
config.Endpoint = aws.String(dbURL)

View File

@@ -1,23 +0,0 @@
package dynamodb
import (
"context"
"github.com/authorizerdev/authorizer/server/db/models"
)
// SMS verification Request
func (p *provider) UpsertSMSRequest(ctx context.Context, sms_code *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return sms_code, nil
}
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
var sms_verification_request models.SMSVerificationRequest
return &sms_verification_request, nil
}
func(p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil
}

View File

@@ -2,6 +2,7 @@ package mongodb
import (
"context"
"errors"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -12,17 +13,31 @@ import (
// UpsertOTP to add or update otp
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
if otp == nil {
id := uuid.NewString()
otp = &models.OTP{
ID: id,
Key: id,
Otp: otpParam.Otp,
Email: otpParam.Email,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
ID: id,
Key: id,
Otp: otpParam.Otp,
Email: otpParam.Email,
PhoneNumber: otpParam.PhoneNumber,
ExpiresAt: otpParam.ExpiresAt,
CreatedAt: time.Now().Unix(),
}
shouldCreate = true
} else {
@@ -41,20 +56,28 @@ func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models
if err != nil {
return nil, err
}
return otp, nil
}
// GetOTPByEmail to get otp for a given email address
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
var otp models.OTP
otpCollection := p.db.Collection(models.Collections.OTP, options.Collection())
err := otpCollection.FindOne(ctx, bson.M{"email": emailAddress}).Decode(&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
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
}

View File

@@ -118,10 +118,7 @@ func NewProvider() (*provider, error) {
Options: options.Index().SetUnique(true).SetSparse(true),
},
}, options.CreateIndexes())
mongodb.CreateCollection(ctx, models.Collections.SMSVerificationRequest, options.CreateCollection())
smsCollection := mongodb.Collection(models.Collections.SMSVerificationRequest, options.Collection())
smsCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
otpCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
{
Keys: bson.M{"phone_number": 1},
Options: options.Index().SetUnique(true).SetSparse(true),

View File

@@ -1,69 +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"
)
// SMS verification Request
func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
smsVerificationRequest, _ := p.GetCodeByPhone(ctx, smsRequest.PhoneNumber)
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
}
smsVerificationRequest.UpdatedAt = time.Now().Unix()
smsRequestCollection := p.db.Collection(models.Collections.SMSVerificationRequest, options.Collection())
var err error
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
}
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
}
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
}
// 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
func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
return nil

View File

@@ -82,13 +82,8 @@ type Provider interface {
UpsertOTP(ctx context.Context, otp *models.OTP) (*models.OTP, error)
// GetOTPByEmail to get otp for a given email address
GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error)
// GetOTPByPhoneNumber to get otp for a given phone number
GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error)
// DeleteOTP to delete otp
DeleteOTP(ctx context.Context, otp *models.OTP) error
// Upsert SMS code request
UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error)
// Get sms code by phone number
GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error)
// Delete sms
DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error
}

View File

@@ -2,6 +2,7 @@ package sql
import (
"context"
"errors"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -14,13 +15,19 @@ func (p *provider) UpsertOTP(ctx context.Context, otp *models.OTP) (*models.OTP,
if otp.ID == "" {
otp.ID = uuid.New().String()
}
// check if email or phone number is present
if otp.Email == "" && otp.PhoneNumber == "" {
return nil, errors.New("email or phone_number is required")
}
uniqueField := models.FieldNameEmail
if otp.Email == "" && otp.PhoneNumber != "" {
uniqueField = models.FieldNamePhoneNumber
}
otp.Key = otp.ID
otp.CreatedAt = time.Now().Unix()
otp.UpdatedAt = time.Now().Unix()
res := p.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}},
Columns: []clause.Column{{Name: uniqueField}},
DoUpdates: clause.AssignmentColumns([]string{"otp", "expires_at", "updated_at"}),
}).Create(&otp)
if res.Error != nil {
@@ -33,7 +40,6 @@ func (p *provider) UpsertOTP(ctx context.Context, otp *models.OTP) (*models.OTP,
// GetOTPByEmail to get otp for a given email address
func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
var otp models.OTP
result := p.db.Where("email = ?", emailAddress).First(&otp)
if result.Error != nil {
return nil, result.Error
@@ -41,6 +47,16 @@ func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*mod
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
result := p.db.Where("phone_number = ?", phoneNumber).First(&otp)
if result.Error != nil {
return nil, result.Error
}
return &otp, nil
}
// DeleteOTP to delete otp
func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
result := p.db.Delete(&models.OTP{

View File

@@ -77,7 +77,7 @@ func NewProvider() (*provider, error) {
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 {
return nil, err
}

View File

@@ -1,51 +0,0 @@
package sql
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
"gorm.io/gorm/clause"
)
// 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"}),
}).Create(smsRequest)
if res.Error != nil {
return nil, res.Error
}
return smsRequest, nil
}
// GetOTPByEmail to get otp for a given email address
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
}
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
}

49
server/env/env.go vendored
View File

@@ -104,6 +104,13 @@ func InitAllEnv() error {
osDisableStrongPassword := os.Getenv(constants.EnvKeyDisableStrongPassword)
osEnforceMultiFactorAuthentication := os.Getenv(constants.EnvKeyEnforceMultiFactorAuthentication)
osDisableMultiFactorAuthentication := os.Getenv(constants.EnvKeyDisableMultiFactorAuthentication)
// phone verification var
osDisablePhoneVerification := os.Getenv(constants.EnvKeyDisablePhoneVerification)
// twilio vars
osTwilioApiKey := os.Getenv(constants.EnvKeyTwilioAPIKey)
osTwilioApiSecret := os.Getenv(constants.EnvKeyTwilioAPISecret)
osTwilioAccountSid := os.Getenv(constants.EnvKeyTwilioAccountSID)
osTwilioSender := os.Getenv(constants.EnvKeyTwilioSender)
// os slice vars
osAllowedOrigins := os.Getenv(constants.EnvKeyAllowedOrigins)
@@ -111,15 +118,6 @@ func InitAllEnv() error {
osDefaultRoles := os.Getenv(constants.EnvKeyDefaultRoles)
osProtectedRoles := os.Getenv(constants.EnvKeyProtectedRoles)
// phone verification var
osDisablePhoneVerification := os.Getenv(constants.EnvKeyDisablePhoneVerification)
// twilio vars
osTwilioApiKey := os.Getenv(constants.EnvKeyTwilioAPIKey)
osTwilioApiSecret := os.Getenv(constants.EnvKeyTwilioAPISecret)
osTwilioAccountSid := os.Getenv(constants.EnvKeyTwilioAccountSID)
osTwilioSenderFrom := os.Getenv(constants.EnvKeyTwilioSenderFrom)
ienv, ok := envData[constants.EnvKeyEnv]
if !ok || ienv == "" {
envData[constants.EnvKeyEnv] = osEnv
@@ -145,7 +143,7 @@ func InitAllEnv() error {
if val, ok := envData[constants.EnvAwsRegion]; !ok || val == "" {
envData[constants.EnvAwsRegion] = osAwsRegion
}
if osAwsRegion != "" && envData[constants.EnvAwsRegion] != osAwsRegion {
envData[constants.EnvAwsRegion] = osAwsRegion
}
@@ -691,11 +689,11 @@ func InitAllEnv() error {
envData[constants.EnvKeyIsEmailServiceEnabled] = false
}
if envData[constants.EnvKeySmtpHost] != "" || envData[constants.EnvKeySmtpUsername] != "" || envData[constants.EnvKeySmtpPassword] != "" || envData[constants.EnvKeySenderEmail] != "" && envData[constants.EnvKeySmtpPort] != "" {
if envData[constants.EnvKeySmtpHost] != "" && envData[constants.EnvKeySmtpUsername] != "" && envData[constants.EnvKeySmtpPassword] != "" && envData[constants.EnvKeySenderEmail] != "" && envData[constants.EnvKeySmtpPort] != "" {
envData[constants.EnvKeyIsEmailServiceEnabled] = true
}
if envData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && !envData[constants.EnvKeyIsEmailServiceEnabled].(bool) {
if envData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && !envData[constants.EnvKeyIsEmailServiceEnabled].(bool) && !envData[constants.EnvKeyIsSMSServiceEnabled].(bool) {
return errors.New("to enable multi factor authentication, please enable email service")
}
@@ -777,29 +775,39 @@ func InitAllEnv() error {
envData[constants.EnvKeyDefaultAuthorizeResponseMode] = osAuthorizeResponseMode
}
if val, ok := envData[constants.EnvKeyTwilioAPISecret]; !ok || val == "" {
envData[constants.EnvKeyTwilioAPISecret] = osTwilioApiSecret
}
if osTwilioApiSecret != "" && envData[constants.EnvKeyTwilioAPISecret] != osTwilioApiSecret {
envData[constants.EnvKeyTwilioAPISecret] = osTwilioApiSecret
}
if val, ok := envData[constants.EnvKeyTwilioAPIKey]; !ok || val == "" {
envData[constants.EnvKeyTwilioAPIKey] = osTwilioApiKey
}
if osTwilioApiKey != "" && envData[constants.EnvKeyTwilioAPIKey] != osTwilioApiKey {
envData[constants.EnvKeyTwilioAPIKey] = osTwilioApiKey
}
if val, ok := envData[constants.EnvKeyTwilioAccountSID]; !ok || val == "" {
envData[constants.EnvKeyTwilioAccountSID] = osTwilioAccountSid
}
if osTwilioAccountSid != "" && envData[constants.EnvKeyTwilioAccountSID] != osTwilioAccountSid {
envData[constants.EnvKeyTwilioAccountSID] = osTwilioAccountSid
}
if osTwilioSenderFrom != "" && envData[constants.EnvKeyTwilioSenderFrom] != osTwilioSenderFrom {
envData[constants.EnvKeyTwilioSenderFrom] = osTwilioSenderFrom
if val, ok := envData[constants.EnvKeyTwilioSender]; !ok || val == "" {
envData[constants.EnvKeyTwilioSender] = osTwilioSender
}
if osTwilioSender != "" && envData[constants.EnvKeyTwilioSender] != osTwilioSender {
envData[constants.EnvKeyTwilioSender] = osTwilioSender
}
if _, ok := envData[constants.EnvKeyDisablePhoneVerification]; !ok {
envData[constants.EnvKeyDisablePhoneVerification] = osDisablePhoneVerification == "false"
}
if osDisablePhoneVerification != "" {
boolValue, err := strconv.ParseBool(osDisablePhoneVerification)
if err != nil {
return err
}
@@ -808,6 +816,15 @@ func InitAllEnv() error {
}
}
if envData[constants.EnvKeyTwilioAPIKey] == "" || envData[constants.EnvKeyTwilioAPISecret] == "" || envData[constants.EnvKeyTwilioAccountSID] == "" || envData[constants.EnvKeyTwilioSender] == "" {
envData[constants.EnvKeyDisablePhoneVerification] = true
envData[constants.EnvKeyIsSMSServiceEnabled] = false
}
if envData[constants.EnvKeyTwilioAPIKey] != "" && envData[constants.EnvKeyTwilioAPISecret] != "" && envData[constants.EnvKeyTwilioAccountSID] != "" && envData[constants.EnvKeyTwilioSender] != "" {
envData[constants.EnvKeyDisablePhoneVerification] = false
envData[constants.EnvKeyIsSMSServiceEnabled] = true
}
err = memorystore.Provider.UpdateEnvStore(envData)
if err != nil {
log.Debug("Error while updating env store: ", err)

View File

@@ -200,7 +200,7 @@ func PersistEnv() error {
envValue := strings.TrimSpace(os.Getenv(key))
if envValue != "" {
switch key {
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableMobileBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure, constants.EnvKeyDisablePhoneVerification:
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableMobileBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyIsSMSServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure, constants.EnvKeyDisablePhoneVerification:
if envValueBool, err := strconv.ParseBool(envValue); err == nil {
if value.(bool) != envValueBool {
storeData[key] = envValueBool

View File

@@ -5,7 +5,7 @@ go 1.16
require (
github.com/99designs/gqlgen v0.17.20
github.com/arangodb/go-driver v1.2.1
github.com/aws/aws-sdk-go v1.44.109
github.com/aws/aws-sdk-go v1.44.298
github.com/coreos/go-oidc/v3 v3.1.0
github.com/couchbase/gocb/v2 v2.6.0
github.com/gin-gonic/gin v1.8.1
@@ -17,7 +17,7 @@ require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/uuid v1.3.0
github.com/guregu/dynamo v1.16.0
github.com/guregu/dynamo v1.20.0
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

View File

@@ -51,9 +51,8 @@ github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e h1:Xg+hGrY2
github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e/go.mod h1:mq7Shfa/CaixoDxiyAAc5jZ6CVBAyPaNQCGS7mkj4Ho=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/aws/aws-sdk-go v1.42.47/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc=
github.com/aws/aws-sdk-go v1.44.109 h1:+Na5JPeS0kiEHoBp5Umcuuf+IDqXqD0lXnM920E31YI=
github.com/aws/aws-sdk-go v1.44.109/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.298 h1:5qTxdubgV7PptZJmp/2qDwD2JL187ePL7VOxsSh1i3g=
github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
@@ -63,8 +62,8 @@ 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=
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -130,8 +129,6 @@ github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gocql/gocql v1.2.0 h1:TZhsCd7fRuye4VyHr3WCvWwIQaZUmjsqnSIXK9FcVCE=
github.com/gocql/gocql v1.2.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8=
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
@@ -206,8 +203,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/guregu/dynamo v1.16.0 h1:gmI8oi1VHwYQtq7+RPBeOiSssVLgxH/Az2t+NtDtL2c=
github.com/guregu/dynamo v1.16.0/go.mod h1:W2Gqcf3MtkrS+Q6fHPGAmRtT0Dyq+TGrqfqrUC9+R/c=
github.com/guregu/dynamo v1.20.0 h1:PDdVVhRSXQFFIHlkhoKF6D8kiwI9IU6uUdz/fF6Iiy4=
github.com/guregu/dynamo v1.20.0/go.mod h1:YQ92BTYVSMIKpFEzhaVqmCJnnSIGxbNF5zvECUaEZRE=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -438,12 +435,12 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -462,8 +459,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc=
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -508,12 +506,16 @@ golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -523,8 +525,10 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@@ -198,7 +198,6 @@ type ComplexityRoot struct {
UpdateUser func(childComplexity int, params model.UpdateUserInput) int
UpdateWebhook func(childComplexity int, params model.UpdateWebhookRequest) 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
}
@@ -219,6 +218,7 @@ type ComplexityRoot struct {
User func(childComplexity int, params model.GetUserRequest) int
Users func(childComplexity int, params *model.PaginatedInput) int
ValidateJwtToken func(childComplexity int, params model.ValidateJWTTokenInput) int
ValidateSession func(childComplexity int, params *model.ValidateSessionInput) int
VerificationRequests func(childComplexity int, params *model.PaginatedInput) int
Webhook func(childComplexity int, params model.WebhookRequest) int
WebhookLogs func(childComplexity int, params *model.ListWebhookLogRequest) int
@@ -275,6 +275,10 @@ type ComplexityRoot struct {
IsValid func(childComplexity int) int
}
ValidateSessionResponse struct {
IsValid func(childComplexity int) int
}
VerificationRequest struct {
CreatedAt func(childComplexity int) int
Email func(childComplexity int) int
@@ -339,7 +343,6 @@ type MutationResolver interface {
Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error)
VerifyOtp(ctx context.Context, params model.VerifyOTPRequest) (*model.AuthResponse, error)
ResendOtp(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error)
VerifyMobile(ctx context.Context, params model.VerifyMobileRequest) (*model.AuthResponse, error)
DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error)
UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error)
AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error)
@@ -363,6 +366,7 @@ type QueryResolver interface {
Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error)
Profile(ctx context.Context) (*model.User, error)
ValidateJwtToken(ctx context.Context, params model.ValidateJWTTokenInput) (*model.ValidateJWTTokenResponse, error)
ValidateSession(ctx context.Context, params *model.ValidateSessionInput) (*model.ValidateSessionResponse, error)
Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error)
User(ctx context.Context, params model.GetUserRequest) (*model.User, error)
VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error)
@@ -1432,18 +1436,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
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":
if e.complexity.Mutation.VerifyOtp == nil {
break
@@ -1572,6 +1564,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.ValidateJwtToken(childComplexity, args["params"].(model.ValidateJWTTokenInput)), true
case "Query.validate_session":
if e.complexity.Query.ValidateSession == nil {
break
}
args, err := ec.field_Query_validate_session_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Query.ValidateSession(childComplexity, args["params"].(*model.ValidateSessionInput)), true
case "Query._verification_requests":
if e.complexity.Query.VerificationRequests == nil {
break
@@ -1844,6 +1848,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ValidateJWTTokenResponse.IsValid(childComplexity), true
case "ValidateSessionResponse.is_valid":
if e.complexity.ValidateSessionResponse.IsValid == nil {
break
}
return e.complexity.ValidateSessionResponse.IsValid(childComplexity), true
case "VerificationRequest.created_at":
if e.complexity.VerificationRequest.CreatedAt == nil {
break
@@ -2093,8 +2104,8 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputUpdateUserInput,
ec.unmarshalInputUpdateWebhookRequest,
ec.unmarshalInputValidateJWTTokenInput,
ec.unmarshalInputValidateSessionInput,
ec.unmarshalInputVerifyEmailInput,
ec.unmarshalInputVerifyMobileRequest,
ec.unmarshalInputVerifyOTPRequest,
ec.unmarshalInputWebhookRequest,
)
@@ -2243,11 +2254,6 @@ type SMSVerificationRequests {
updated_at: Int64
}
input VerifyMobileRequest {
phone_number: String!
code: String!
}
type Error {
message: String!
reason: String!
@@ -2341,6 +2347,10 @@ type ValidateJWTTokenResponse {
claims: Map
}
type ValidateSessionResponse {
is_valid: Boolean!
}
type GenerateJWTKeysResponse {
secret: String
public_key: String
@@ -2633,6 +2643,11 @@ input ValidateJWTTokenInput {
roles: [String!]
}
input ValidateSessionInput {
cookie: String!
roles: [String!]
}
input GenerateJWTKeysInput {
type: String!
}
@@ -2693,7 +2708,9 @@ input DeleteEmailTemplateRequest {
}
input VerifyOTPRequest {
email: String!
# either email or phone_number is required
email: String
phone_number: String
otp: String!
# state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login
@@ -2702,7 +2719,8 @@ input VerifyOTPRequest {
}
input ResendOTPRequest {
email: String!
email: String
phone_number: String
# state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login
# and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
@@ -2729,7 +2747,6 @@ type Mutation {
revoke(params: OAuthRevokeInput!): Response!
verify_otp(params: VerifyOTPRequest!): AuthResponse!
resend_otp(params: ResendOTPRequest!): Response!
verify_mobile(params: VerifyMobileRequest!): AuthResponse!
# admin only apis
_delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User!
@@ -2755,6 +2772,7 @@ type Query {
session(params: SessionQueryInput): AuthResponse!
profile: User!
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
validate_session(params: ValidateSessionInput): ValidateSessionResponse!
# admin only apis
_users(params: PaginatedInput): Users!
_user(params: GetUserRequest!): User!
@@ -3194,21 +3212,6 @@ func (ec *executionContext) field_Mutation_verify_email_args(ctx context.Context
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) {
var err error
args := map[string]interface{}{}
@@ -3374,6 +3377,21 @@ func (ec *executionContext) field_Query_validate_jwt_token_args(ctx context.Cont
return args, nil
}
func (ec *executionContext) field_Query_validate_session_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 *model.ValidateSessionInput
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalOValidateSessionInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateSessionInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -8584,77 +8602,6 @@ func (ec *executionContext) fieldContext_Mutation_resend_otp(ctx context.Context
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) {
fc, err := ec.fieldContext_Mutation__delete_user(ctx, field)
if err != nil {
@@ -10159,6 +10106,65 @@ func (ec *executionContext) fieldContext_Query_validate_jwt_token(ctx context.Co
return fc, nil
}
func (ec *executionContext) _Query_validate_session(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Query_validate_session(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.Query().ValidateSession(rctx, fc.Args["params"].(*model.ValidateSessionInput))
})
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.ValidateSessionResponse)
fc.Result = res
return ec.marshalNValidateSessionResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateSessionResponse(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Query_validate_session(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Query",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "is_valid":
return ec.fieldContext_ValidateSessionResponse_is_valid(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type ValidateSessionResponse", 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_Query_validate_session_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return
}
return fc, nil
}
func (ec *executionContext) _Query__users(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Query__users(ctx, field)
if err != nil {
@@ -12381,6 +12387,50 @@ func (ec *executionContext) fieldContext_ValidateJWTTokenResponse_claims(ctx con
return fc, nil
}
func (ec *executionContext) _ValidateSessionResponse_is_valid(ctx context.Context, field graphql.CollectedField, obj *model.ValidateSessionResponse) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_ValidateSessionResponse_is_valid(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.IsValid, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_ValidateSessionResponse_is_valid(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ValidateSessionResponse",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _VerificationRequest_id(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_VerificationRequest_id(ctx, field)
if err != nil {
@@ -16326,7 +16376,7 @@ func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context,
asMap[k] = v
}
fieldsInOrder := [...]string{"email", "state"}
fieldsInOrder := [...]string{"email", "phone_number", "state"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
@@ -16337,7 +16387,15 @@ func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context,
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email"))
it.Email, err = ec.unmarshalNString2string(ctx, v)
it.Email, err = ec.unmarshalOString2string(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 {
return it, err
}
@@ -17555,6 +17613,42 @@ func (ec *executionContext) unmarshalInputValidateJWTTokenInput(ctx context.Cont
return it, nil
}
func (ec *executionContext) unmarshalInputValidateSessionInput(ctx context.Context, obj interface{}) (model.ValidateSessionInput, error) {
var it model.ValidateSessionInput
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
fieldsInOrder := [...]string{"cookie", "roles"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "cookie":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("cookie"))
it.Cookie, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "roles":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles"))
it.Roles, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputVerifyEmailInput(ctx context.Context, obj interface{}) (model.VerifyEmailInput, error) {
var it model.VerifyEmailInput
asMap := map[string]interface{}{}
@@ -17591,42 +17685,6 @@ func (ec *executionContext) unmarshalInputVerifyEmailInput(ctx context.Context,
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) {
var it model.VerifyOTPRequest
asMap := map[string]interface{}{}
@@ -17634,7 +17692,7 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context,
asMap[k] = v
}
fieldsInOrder := [...]string{"email", "otp", "state"}
fieldsInOrder := [...]string{"email", "phone_number", "otp", "state"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
@@ -17645,7 +17703,15 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context,
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email"))
it.Email, err = ec.unmarshalNString2string(ctx, v)
it.Email, err = ec.unmarshalOString2string(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 {
return it, err
}
@@ -18533,15 +18599,6 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
return ec._Mutation_resend_otp(ctx, field)
})
if out.Values[i] == graphql.Null {
invalids++
}
case "verify_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 {
invalids++
}
@@ -18866,6 +18923,29 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc)
}
out.Concurrently(i, func() graphql.Marshaler {
return rrm(innerCtx)
})
case "validate_session":
field := field
innerFunc := func(ctx context.Context) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_validate_session(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
}
rrm := func(ctx context.Context) graphql.Marshaler {
return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc)
}
out.Concurrently(i, func() graphql.Marshaler {
return rrm(innerCtx)
})
@@ -19395,6 +19475,34 @@ func (ec *executionContext) _ValidateJWTTokenResponse(ctx context.Context, sel a
return out
}
var validateSessionResponseImplementors = []string{"ValidateSessionResponse"}
func (ec *executionContext) _ValidateSessionResponse(ctx context.Context, sel ast.SelectionSet, obj *model.ValidateSessionResponse) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, validateSessionResponseImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ValidateSessionResponse")
case "is_valid":
out.Values[i] = ec._ValidateSessionResponse_is_valid(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var verificationRequestImplementors = []string{"VerificationRequest"}
func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.SelectionSet, obj *model.VerificationRequest) graphql.Marshaler {
@@ -20470,6 +20578,20 @@ func (ec *executionContext) marshalNValidateJWTTokenResponse2ᚖgithubᚗcomᚋa
return ec._ValidateJWTTokenResponse(ctx, sel, v)
}
func (ec *executionContext) marshalNValidateSessionResponse2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateSessionResponse(ctx context.Context, sel ast.SelectionSet, v model.ValidateSessionResponse) graphql.Marshaler {
return ec._ValidateSessionResponse(ctx, sel, &v)
}
func (ec *executionContext) marshalNValidateSessionResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateSessionResponse(ctx context.Context, sel ast.SelectionSet, v *model.ValidateSessionResponse) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._ValidateSessionResponse(ctx, sel, v)
}
func (ec *executionContext) marshalNVerificationRequest2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerificationRequestᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.VerificationRequest) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
@@ -20543,11 +20665,6 @@ func (ec *executionContext) unmarshalNVerifyEmailInput2githubᚗcomᚋauthorizer
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) {
res, err := ec.unmarshalInputVerifyOTPRequest(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
@@ -21158,6 +21275,14 @@ func (ec *executionContext) marshalOUser2ᚖgithubᚗcomᚋauthorizerdevᚋautho
return ec._User(ctx, sel, v)
}
func (ec *executionContext) unmarshalOValidateSessionInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateSessionInput(ctx context.Context, v interface{}) (*model.ValidateSessionInput, error) {
if v == nil {
return nil, nil
}
res, err := ec.unmarshalInputValidateSessionInput(ctx, v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler {
if v == nil {
return graphql.Null

View File

@@ -120,7 +120,6 @@ type Env struct {
AdminCookieSecure bool `json:"ADMIN_COOKIE_SECURE"`
DefaultAuthorizeResponseType *string `json:"DEFAULT_AUTHORIZE_RESPONSE_TYPE"`
DefaultAuthorizeResponseMode *string `json:"DEFAULT_AUTHORIZE_RESPONSE_MODE"`
SmsCodeExpiryTime *string `json:"SMS_CODE_EXPIRY_TIME"`
}
type Error struct {
@@ -246,8 +245,9 @@ type PaginationInput struct {
}
type ResendOTPRequest struct {
Email string `json:"email"`
State *string `json:"state"`
Email *string `json:"email"`
PhoneNumber *string `json:"phone_number"`
State *string `json:"state"`
}
type ResendVerifyEmailInput struct {
@@ -456,6 +456,15 @@ type ValidateJWTTokenResponse struct {
Claims map[string]interface{} `json:"claims"`
}
type ValidateSessionInput struct {
Cookie string `json:"cookie"`
Roles []string `json:"roles"`
}
type ValidateSessionResponse struct {
IsValid bool `json:"is_valid"`
}
type VerificationRequest struct {
ID string `json:"id"`
Identifier *string `json:"identifier"`
@@ -478,15 +487,11 @@ type VerifyEmailInput struct {
State *string `json:"state"`
}
type VerifyMobileRequest struct {
PhoneNumber string `json:"phone_number"`
Code string `json:"code"`
}
type VerifyOTPRequest struct {
Email string `json:"email"`
Otp string `json:"otp"`
State *string `json:"state"`
Email *string `json:"email"`
PhoneNumber *string `json:"phone_number"`
Otp string `json:"otp"`
State *string `json:"state"`
}
type Webhook struct {

View File

@@ -84,11 +84,6 @@ type SMSVerificationRequests {
updated_at: Int64
}
input VerifyMobileRequest {
phone_number: String!
code: String!
}
type Error {
message: String!
reason: String!
@@ -182,6 +177,10 @@ type ValidateJWTTokenResponse {
claims: Map
}
type ValidateSessionResponse {
is_valid: Boolean!
}
type GenerateJWTKeysResponse {
secret: String
public_key: String
@@ -474,6 +473,11 @@ input ValidateJWTTokenInput {
roles: [String!]
}
input ValidateSessionInput {
cookie: String!
roles: [String!]
}
input GenerateJWTKeysInput {
type: String!
}
@@ -534,7 +538,9 @@ input DeleteEmailTemplateRequest {
}
input VerifyOTPRequest {
email: String!
# either email or phone_number is required
email: String
phone_number: String
otp: String!
# state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login
@@ -543,7 +549,8 @@ input VerifyOTPRequest {
}
input ResendOTPRequest {
email: String!
email: String
phone_number: String
# state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login
# and use that code for setting `c_hash` in id_token
@@ -570,7 +577,6 @@ type Mutation {
revoke(params: OAuthRevokeInput!): Response!
verify_otp(params: VerifyOTPRequest!): AuthResponse!
resend_otp(params: ResendOTPRequest!): Response!
verify_mobile(params: VerifyMobileRequest!): AuthResponse!
# admin only apis
_delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User!
@@ -596,6 +602,7 @@ type Query {
session(params: SessionQueryInput): AuthResponse!
profile: User!
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
validate_session(params: ValidateSessionInput): ValidateSessionResponse!
# admin only apis
_users(params: PaginatedInput): Users!
_user(params: GetUserRequest!): User!

View File

@@ -81,11 +81,6 @@ func (r *mutationResolver) ResendOtp(ctx context.Context, params model.ResendOTP
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.
func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) {
return resolvers.DeleteUserResolver(ctx, params)
@@ -191,6 +186,11 @@ func (r *queryResolver) ValidateJwtToken(ctx context.Context, params model.Valid
return resolvers.ValidateJwtTokenResolver(ctx, params)
}
// ValidateSession is the resolver for the validate_session field.
func (r *queryResolver) ValidateSession(ctx context.Context, params *model.ValidateSessionInput) (*model.ValidateSessionResponse, error) {
return resolvers.ValidateSessionResolver(ctx, params)
}
// Users is the resolver for the _users field.
func (r *queryResolver) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) {
return resolvers.UsersResolver(ctx, params)

View File

@@ -84,9 +84,9 @@ func AuthorizeHandler() gin.HandlerFunc {
if responseMode == "" {
if val, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultAuthorizeResponseMode); err == nil {
responseType = val
responseMode = val
} else {
responseType = constants.ResponseModeQuery
responseMode = constants.ResponseModeQuery
}
}

View File

@@ -33,6 +33,7 @@ func InitMemStore() error {
constants.EnvKeyDisableSignUp: false,
constants.EnvKeyDisableStrongPassword: false,
constants.EnvKeyIsEmailServiceEnabled: false,
constants.EnvKeyIsSMSServiceEnabled: false,
constants.EnvKeyEnforceMultiFactorAuthentication: false,
constants.EnvKeyDisableMultiFactorAuthentication: false,
constants.EnvKeyAppCookieSecure: true,

View File

@@ -9,6 +9,10 @@ import (
log "github.com/sirupsen/logrus"
)
const (
dialTimeout = 60 * time.Second
)
// RedisClient is the interface for redis client & redis cluster client
type RedisClient interface {
HMSet(ctx context.Context, key string, values ...interface{}) *redis.BoolCmd
@@ -41,8 +45,7 @@ func NewRedisProvider(redisURL string) (*provider, error) {
urls := []string{opt.Addr}
urlList := redisURLHostPortsList[1:]
urls = append(urls, urlList...)
clusterOpt := &redis.ClusterOptions{Addrs: urls}
clusterOpt := &redis.ClusterOptions{Addrs: urls, DialTimeout: dialTimeout}
rdb := redis.NewClusterClient(clusterOpt)
ctx := context.Background()
_, err = rdb.Ping(ctx).Result()
@@ -62,7 +65,7 @@ func NewRedisProvider(redisURL string) (*provider, error) {
log.Debug("error parsing redis url: ", err)
return nil, err
}
opt.DialTimeout = dialTimeout
rdb := redis.NewClient(opt)
ctx := context.Background()
_, err = rdb.Ping(ctx).Result()

View File

@@ -143,7 +143,7 @@ func (c *provider) GetEnvStore() (map[string]interface{}, error) {
return nil, err
}
for key, value := range data {
if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableMobileBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword || key == constants.EnvKeyIsEmailServiceEnabled || key == constants.EnvKeyEnforceMultiFactorAuthentication || key == constants.EnvKeyDisableMultiFactorAuthentication || key == constants.EnvKeyAppCookieSecure || key == constants.EnvKeyAdminCookieSecure {
if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableMobileBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword || key == constants.EnvKeyIsEmailServiceEnabled || key == constants.EnvKeyIsSMSServiceEnabled || key == constants.EnvKeyEnforceMultiFactorAuthentication || key == constants.EnvKeyDisableMultiFactorAuthentication || key == constants.EnvKeyAppCookieSecure || key == constants.EnvKeyAdminCookieSecure {
boolValue, err := strconv.ParseBool(value)
if err != nil {
return res, err

View File

@@ -106,7 +106,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
}
isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication)
if err != nil || !isEmailServiceEnabled {
if err != nil || !isMFADisabled {
log.Debug("MFA service not enabled: ", err)
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/smsproviders"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/validators"
@@ -94,55 +95,57 @@ func MobileLoginResolver(ctx context.Context, params model.MobileLoginInput) (*m
roles = params.Roles
}
disablePhoneVerification, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisablePhoneVerification)
if err != nil {
log.Debug("Error getting disable phone verification: ", err)
}
if disablePhoneVerification {
now := time.Now().Unix()
user.PhoneNumberVerifiedAt = &now
}
isSMSServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsSMSServiceEnabled)
if err != nil || !isSMSServiceEnabled {
log.Debug("SMS service not enabled: ", err)
}
if disablePhoneVerification {
now := time.Now().Unix()
user.PhoneNumberVerifiedAt = &now
}
isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication)
if err != nil || !isMFADisabled {
log.Debug("MFA service not enabled: ", err)
}
if !disablePhoneVerification && isSMSServiceEnabled && !isMFADisabled {
duration, _ := time.ParseDuration("10m")
smsCode := utils.GenerateOTP()
smsBody := strings.Builder{}
smsBody.WriteString("Your verification code is: ")
smsBody.WriteString(smsCode)
_, err := db.Provider.UpsertOTP(ctx, &models.OTP{
PhoneNumber: params.PhoneNumber,
Otp: smsCode,
ExpiresAt: time.Now().Add(duration).Unix(),
})
if err != nil {
log.Debug("error while upserting OTP: ", err.Error())
return nil, err
}
go func() {
utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, constants.AuthRecipeMethodMobileBasicAuth, *user)
smsproviders.SendSMS(params.PhoneNumber, smsBody.String())
}()
return &model.AuthResponse{
Message: "Please check the OTP",
ShouldShowOtpScreen: refs.NewBoolRef(true),
}, nil
}
scope := []string{"openid", "email", "profile"}
if params.Scope != nil && len(scope) > 0 {
scope = params.Scope
}
/*
// TODO use sms authentication for MFA
isEmailServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled)
if err != nil || !isEmailServiceEnabled {
log.Debug("Email service not enabled: ", err)
}
isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication)
if err != nil || !isEmailServiceEnabled {
log.Debug("MFA service not enabled: ", err)
}
// If email service is not enabled continue the process in any way
if refs.BoolValue(user.IsMultiFactorAuthEnabled) && isEmailServiceEnabled && !isMFADisabled {
otp := utils.GenerateOTP()
otpData, err := db.Provider.UpsertOTP(ctx, &models.OTP{
Email: user.Email,
Otp: otp,
ExpiresAt: time.Now().Add(1 * time.Minute).Unix(),
})
if err != nil {
log.Debug("Failed to add otp: ", err)
return nil, err
}
go func() {
// exec it as go routine so that we can reduce the api latency
go email.SendEmail([]string{params.PhoneNumber}, constants.VerificationTypeOTP, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"otp": otpData.Otp,
})
if err != nil {
log.Debug("Failed to send otp email: ", err)
}
}()
return &model.AuthResponse{
Message: "Please check the OTP in your inbox",
ShouldShowOtpScreen: refs.NewBoolRef(true),
}, nil
}
*/
code := ""
codeChallenge := ""
nonce := ""

View File

@@ -8,7 +8,7 @@ import (
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
@@ -17,9 +17,9 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/smsproviders"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/smsproviders"
"github.com/authorizerdev/authorizer/server/validators"
)
@@ -92,7 +92,6 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput)
if err != nil {
log.Debug("Failed to get user by email: ", err)
}
if existingUser != nil {
if existingUser.PhoneNumberVerifiedAt != nil {
// email is verified
@@ -105,7 +104,6 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput)
}
inputRoles := []string{}
if len(params.Roles) > 0 {
// check if roles exists
rolesString, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyRoles)
@@ -133,8 +131,8 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput)
}
user := models.User{
Email: emailInput,
PhoneNumber: &mobile,
Email: emailInput,
PhoneNumber: &mobile,
}
user.Roles = strings.Join(inputRoles, ",")
@@ -179,7 +177,7 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput)
log.Debug("MFA service not enabled: ", err)
isMFAEnforced = false
}
if isMFAEnforced {
user.IsMultiFactorAuthEnabled = refs.NewBoolRef(true)
}
@@ -189,6 +187,10 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput)
now := time.Now().Unix()
user.PhoneNumberVerifiedAt = &now
}
isSMSServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsSMSServiceEnabled)
if err != nil || !isSMSServiceEnabled {
log.Debug("SMS service not enabled: ", err)
}
user.SignupMethods = constants.AuthRecipeMethodMobileBasicAuth
user, err = db.Provider.AddUser(ctx, user)
@@ -197,11 +199,10 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput)
log.Debug("Failed to add user: ", err)
return res, err
}
if !disablePhoneVerification {
if !disablePhoneVerification && isSMSServiceEnabled {
duration, _ := time.ParseDuration("10m")
smsCode := utils.GenerateOTP()
smsBody := strings.Builder{}
smsBody.WriteString("Your verification code is: ")
smsBody.WriteString(smsCode)
@@ -211,15 +212,22 @@ func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput)
log.Debug("error while upserting user: ", err.Error())
return nil, err
}
_, err = db.Provider.UpsertOTP(ctx, &models.OTP{
PhoneNumber: mobile,
Otp: smsCode,
ExpiresAt: time.Now().Add(duration).Unix(),
})
if err != nil {
log.Debug("error while upserting OTP: ", err.Error())
return nil, err
}
go func() {
db.Provider.UpsertSMSRequest(ctx, &models.SMSVerificationRequest{
PhoneNumber: mobile,
Code: smsCode,
CodeExpiresAt: time.Now().Add(duration).Unix(),
})
smsproviders.SendSMS(mobile, smsBody.String())
}()
return &model.AuthResponse{
Message: "Please check the OTP in your inbox",
ShouldShowOtpScreen: refs.NewBoolRef(true),
}, nil
}
roles := strings.Split(user.Roles, ",")

View File

@@ -12,23 +12,49 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/email"
emailHelper "github.com/authorizerdev/authorizer/server/email"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/smsproviders"
"github.com/authorizerdev/authorizer/server/utils"
)
// ResendOTPResolver is a resolver for resend otp mutation
func ResendOTPResolver(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) {
email := strings.ToLower(strings.Trim(refs.StringValue(params.Email), " "))
phoneNumber := strings.Trim(refs.StringValue(params.PhoneNumber), " ")
log := log.WithFields(log.Fields{
"email": params.Email,
"email": email,
"phone_number": phoneNumber,
})
params.Email = strings.ToLower(params.Email)
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
if email == "" && phoneNumber == "" {
log.Debug("Email or phone number is required")
return nil, errors.New("email or phone number is required")
}
var user models.User
var err error
if email != "" {
isEmailServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled)
if err != nil || !isEmailServiceEnabled {
log.Debug("Email service not enabled: ", err)
return nil, errors.New("email service not enabled")
}
user, err = db.Provider.GetUserByEmail(ctx, email)
} else {
isSMSServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled)
if err != nil || !isSMSServiceEnabled {
log.Debug("Email service not enabled: ", err)
return nil, errors.New("email service not enabled")
}
// TODO fix after refs fixes
var u *models.User
u, err = db.Provider.GetUserByPhoneNumber(ctx, phoneNumber)
user = *u
}
if err != nil {
log.Debug("Failed to get user by email: ", err)
return nil, fmt.Errorf(`user with this email not found`)
return nil, fmt.Errorf(`user with this email/phone not found`)
}
if user.RevokedTimestamp != nil {
@@ -36,35 +62,38 @@ func ResendOTPResolver(ctx context.Context, params model.ResendOTPRequest) (*mod
return nil, fmt.Errorf(`user access has been revoked`)
}
if user.EmailVerifiedAt == nil {
if email != "" && user.EmailVerifiedAt == nil {
log.Debug("User email is not verified")
return nil, fmt.Errorf(`email not verified`)
}
if phoneNumber != "" && user.PhoneNumberVerifiedAt == nil {
log.Debug("User phone number is not verified")
return nil, fmt.Errorf(`phone number not verified`)
}
if !refs.BoolValue(user.IsMultiFactorAuthEnabled) {
log.Debug("User multi factor authentication is not enabled")
return nil, fmt.Errorf(`multi factor authentication not enabled`)
}
isEmailServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled)
if err != nil || !isEmailServiceEnabled {
log.Debug("Email service not enabled: ", err)
return nil, errors.New("email service not enabled")
}
isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication)
if err != nil || isMFADisabled {
log.Debug("MFA service not enabled: ", err)
return nil, errors.New("multi factor authentication is disabled for this instance")
}
// get otp by email
otpData, err := db.Provider.GetOTPByEmail(ctx, params.Email)
// get otp by email or phone number
var otpData *models.OTP
if email != "" {
otpData, err = db.Provider.GetOTPByEmail(ctx, refs.StringValue(params.Email))
} else {
otpData, err = db.Provider.GetOTPByPhoneNumber(ctx, refs.StringValue(params.PhoneNumber))
}
if err != nil {
log.Debug("Failed to get otp for given email: ", err)
return nil, err
}
if otpData == nil {
log.Debug("No otp found for given email: ", params.Email)
return &model.Response{
@@ -73,28 +102,30 @@ func ResendOTPResolver(ctx context.Context, params model.ResendOTPRequest) (*mod
}
otp := utils.GenerateOTP()
otpData, err = db.Provider.UpsertOTP(ctx, &models.OTP{
if _, err := db.Provider.UpsertOTP(ctx, &models.OTP{
Email: user.Email,
Otp: otp,
ExpiresAt: time.Now().Add(1 * time.Minute).Unix(),
})
if err != nil {
log.Debug("Error generating new otp: ", err)
}); err != nil {
log.Debug("Error upserting otp: ", err)
return nil, err
}
go func() {
if email != "" {
// exec it as go routine so that we can reduce the api latency
go email.SendEmail([]string{params.Email}, constants.VerificationTypeOTP, map[string]interface{}{
go emailHelper.SendEmail([]string{email}, constants.VerificationTypeOTP, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"otp": otp,
})
if err != nil {
log.Debug("Error sending otp email: ", otp)
}
}()
} else {
smsBody := strings.Builder{}
smsBody.WriteString("Your verification code is: ")
smsBody.WriteString(otp)
// exec it as go routine so that we can reduce the api latency
go smsproviders.SendSMS(phoneNumber, smsBody.String())
}
log.Info("OTP has been resent")
return &model.Response{
Message: `OTP has been sent. Please check your inbox`,
}, nil

View File

@@ -267,6 +267,13 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
updatedData[constants.EnvKeyIsEmailServiceEnabled] = true
}
if updatedData[constants.EnvKeyTwilioAPIKey] == "" || updatedData[constants.EnvKeyTwilioAPISecret] == "" || updatedData[constants.EnvKeyTwilioAccountSID] == "" || updatedData[constants.EnvKeyTwilioSender] == "" {
updatedData[constants.EnvKeyIsSMSServiceEnabled] = false
if !updatedData[constants.EnvKeyIsSMSServiceEnabled].(bool) {
updatedData[constants.EnvKeyDisablePhoneVerification] = true
}
}
if !currentData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && updatedData[constants.EnvKeyEnforceMultiFactorAuthentication].(bool) && !updatedData[constants.EnvKeyDisableMultiFactorAuthentication].(bool) {
go db.Provider.UpdateUsers(ctx, map[string]interface{}{
"is_multi_factor_auth_enabled": true,

View File

@@ -0,0 +1,59 @@
package resolvers
import (
"context"
"errors"
"fmt"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
log "github.com/sirupsen/logrus"
)
// ValidateSessionResolver is used to validate a cookie session without its rotation
func ValidateSessionResolver(ctx context.Context, params *model.ValidateSessionInput) (*model.ValidateSessionResponse, error) {
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
log.Debug("Failed to get GinContext: ", err)
return nil, err
}
sessionToken := params.Cookie
if sessionToken == "" {
sessionToken, err = cookie.GetSession(gc)
if err != nil {
log.Debug("Failed to get session token: ", err)
return nil, errors.New("unauthorized")
}
}
claims, err := token.ValidateBrowserSession(gc, sessionToken)
if err != nil {
log.Debug("Failed to validate session token", err)
return nil, errors.New("unauthorized")
}
userID := claims.Subject
log := log.WithFields(log.Fields{
"user_id": userID,
})
_, err = db.Provider.GetUserByID(ctx, userID)
if err != nil {
return nil, err
}
// refresh token has "roles" as claim
claimRoleInterface := claims.Roles
claimRoles := []string{}
claimRoles = append(claimRoles, claimRoleInterface...)
if params != nil && params.Roles != nil && len(params.Roles) > 0 {
for _, v := range params.Roles {
if !utils.StringSliceContains(claimRoles, v) {
log.Debug("User does not have required role: ", claimRoles, v)
return nil, fmt.Errorf(`unauthorized`)
}
}
}
return &model.ValidateSessionResponse{
IsValid: true,
}, nil
}

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)
return res, err
}
otp, err := db.Provider.GetOTPByEmail(ctx, params.Email)
if err != nil {
log.Debug("Failed to get otp request by email: ", err)
return res, fmt.Errorf(`invalid email: %s`, err.Error())
if refs.StringValue(params.Email) == "" && refs.StringValue(params.PhoneNumber) == "" {
log.Debug("Email or phone number is required")
return res, fmt.Errorf(`email or phone_number is required`)
}
currentField := models.FieldNameEmail
if refs.StringValue(params.Email) == "" {
currentField = models.FieldNamePhoneNumber
}
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 {
log.Debug("Failed to verify otp request: Incorrect value")
return res, fmt.Errorf(`invalid otp`)
}
expiresIn := otp.ExpiresAt - time.Now().Unix()
if expiresIn < 0 {
log.Debug("Failed to verify otp request: Timeout")
return res, fmt.Errorf("otp expired")
}
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
if err != nil {
var user models.User
if currentField == models.FieldNameEmail {
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)
return res, err
}
isSignUp := user.EmailVerifiedAt == nil
isSignUp := user.EmailVerifiedAt == nil && user.PhoneNumberVerifiedAt == nil
// TODO - Add Login method in DB when we introduce OTP for social media login
loginMethod := constants.AuthRecipeMethodBasicAuth
if currentField == models.FieldNamePhoneNumber {
loginMethod = constants.AuthRecipeMethodMobileOTP
}
roles := strings.Split(user.Roles, ",")
scope := []string{"openid", "email", "profile"}
code := ""

View File

@@ -1,43 +1,38 @@
package smsproviders
import (
twilio "github.com/twilio/twilio-go"
api "github.com/twilio/twilio-go/rest/api/v2010"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
log "github.com/sirupsen/logrus"
twilio "github.com/twilio/twilio-go"
api "github.com/twilio/twilio-go/rest/api/v2010"
)
// SendSMS util to send sms
// TODO: Should be restructured to interface when another provider is added
func SendSMS(sendTo, messageBody string) error {
twilioAPISecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioAPISecret)
if err != nil || twilioAPISecret == ""{
log.Errorf("Failed to get api secret: ", err)
if err != nil || twilioAPISecret == "" {
log.Debug("Failed to get api secret: ", err)
return err
}
twilioAPIKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioAPIKey)
if err != nil || twilioAPIKey == ""{
log.Errorf("Failed to get api key: ", err)
if err != nil || twilioAPIKey == "" {
log.Debug("Failed to get api key: ", err)
return err
}
twilioSenderFrom, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioSenderFrom)
twilioSenderFrom, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioSender)
if err != nil || twilioSenderFrom == "" {
log.Errorf("Failed to get sender: ", err)
log.Debug("Failed to get sender: ", err)
return err
}
// accountSID is not a must to send sms on twilio
twilioAccountSID, _ := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioAccountSID)
client := twilio.NewRestClientWithParams(twilio.ClientParams{
Username: twilioAPIKey,
Password: twilioAPISecret,
AccountSid: twilioAccountSID,
})
message := &api.CreateMessageParams{}
message.SetBody(messageBody)
message.SetFrom(twilioSenderFrom)

View File

@@ -46,7 +46,6 @@ func TestResolvers(t *testing.T) {
for dbType, dbURL := range databases {
ctx := context.Background()
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseURL, dbURL)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseType, dbType)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseName, testDb)
@@ -57,6 +56,11 @@ func TestResolvers(t *testing.T) {
if dbType == constants.DbTypeDynamoDB {
memorystore.Provider.UpdateEnvVariable(constants.EnvAwsRegion, "ap-south-1")
os.Setenv(constants.EnvAwsRegion, "ap-south-1")
os.Unsetenv(constants.EnvAwsAccessKeyID)
os.Unsetenv(constants.EnvAwsSecretAccessKey)
// Remove aws credentials from env, so that local dynamodb can be used
memorystore.Provider.UpdateEnvVariable(constants.EnvAwsAccessKeyID, "")
memorystore.Provider.UpdateEnvVariable(constants.EnvAwsSecretAccessKey, "")
}
if dbType == constants.DbTypeCouchbaseDB {
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseUsername, "Administrator")
@@ -135,7 +139,7 @@ func TestResolvers(t *testing.T) {
validateJwtTokenTest(t, s)
verifyOTPTest(t, s)
resendOTPTest(t, s)
verifyMobileTest(t, s)
validateSessionTests(t, s)
updateAllUsersTest(t, s)
webhookLogsTest(t, s) // get logs after above resolver tests are done

View File

@@ -1,12 +1,10 @@
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/graph/model"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
@@ -26,11 +24,6 @@ func mobileLoginTests(t *testing.T, s TestSetup) {
})
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",
@@ -45,7 +38,6 @@ func mobileLoginTests(t *testing.T, s TestSetup) {
})
assert.Error(t, err)
assert.Nil(t, res)
// should fail because phone is not verified
res, err = resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{
PhoneNumber: phoneNumber,
@@ -53,26 +45,17 @@ func mobileLoginTests(t *testing.T, s TestSetup) {
})
assert.NotNil(t, err, "should fail because phone is not verified")
assert.Nil(t, res)
smsRequest, err := db.Provider.GetCodeByPhone(ctx, phoneNumber)
smsRequest, err := db.Provider.GetOTPByPhoneNumber(ctx, phoneNumber)
assert.NoError(t, err)
assert.NotEmpty(t, smsRequest.Code)
verifySMSRequest, err := resolvers.VerifyMobileResolver(ctx, model.VerifyMobileRequest{
PhoneNumber: phoneNumber,
Code: smsRequest.Code,
assert.NotEmpty(t, smsRequest.Otp)
verifySMSRequest, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{
PhoneNumber: &phoneNumber,
Otp: smsRequest.Otp,
})
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)
assert.NotEmpty(t, verifySMSRequest.AccessToken)
assert.NotEmpty(t, verifySMSRequest.IDToken)
cleanData(email)
})
}

View File

@@ -4,6 +4,7 @@ import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
@@ -65,16 +66,25 @@ func mobileSingupTest(t *testing.T, s TestSetup) {
})
assert.Error(t, err)
assert.Nil(t, res)
phoneNumber := "1234567890"
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
PhoneNumber: "1234567890",
PhoneNumber: phoneNumber,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NoError(t, err)
assert.NotEmpty(t, res.AccessToken)
assert.Equal(t, "1234567890@authorizer.dev", res.User.Email)
assert.NotNil(t, res)
assert.True(t, *res.ShouldShowOtpScreen)
// Verify with otp
otp, err := db.Provider.GetOTPByPhoneNumber(ctx, phoneNumber)
assert.Nil(t, err)
assert.NotEmpty(t, otp.Otp)
otpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{
PhoneNumber: &phoneNumber,
Otp: otp.Otp,
})
assert.Nil(t, err)
assert.NotEmpty(t, otpRes.Message)
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
PhoneNumber: "1234567890",
Password: s.TestInfo.Password,

View File

@@ -51,7 +51,7 @@ func resendOTPTest(t *testing.T, s TestSetup) {
assert.NotNil(t, updateRes)
// Resend otp should return error as no initial opt is being sent
resendOtpRes, err := resolvers.ResendOTPResolver(ctx, model.ResendOTPRequest{
Email: email,
Email: refs.NewStringRef(email),
})
assert.Error(t, err)
assert.Nil(t, resendOtpRes)
@@ -72,7 +72,7 @@ func resendOTPTest(t *testing.T, s TestSetup) {
// resend otp
resendOtpRes, err = resolvers.ResendOTPResolver(ctx, model.ResendOTPRequest{
Email: email,
Email: refs.NewStringRef(email),
})
assert.NoError(t, err)
assert.NotEmpty(t, resendOtpRes.Message)
@@ -84,13 +84,13 @@ func resendOTPTest(t *testing.T, s TestSetup) {
// Should return error for older otp
verifyOtpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{
Email: email,
Email: &email,
Otp: otp.Otp,
})
assert.Error(t, err)
assert.Nil(t, verifyOtpRes)
verifyOtpRes, err = resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{
Email: email,
Email: &email,
Otp: newOtp.Otp,
})
assert.NoError(t, err)

View File

@@ -126,6 +126,10 @@ func testSetup() TestSetup {
memorystore.Provider.UpdateEnvVariable(constants.EnvKeySmtpPassword, "test")
memorystore.Provider.UpdateEnvVariable(constants.EnvKeySenderEmail, "info@yopmail.com")
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyProtectedRoles, "admin")
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyTwilioAPIKey, "test")
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyTwilioAPISecret, "test")
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyTwilioAccountSID, "test")
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyTwilioSender, "1234567890")
err = db.InitDB()
if err != nil {

View File

@@ -27,7 +27,7 @@ func updateWebhookTest(t *testing.T, s TestSetup) {
webhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent)
assert.NoError(t, err)
assert.NotNil(t, webhooks)
assert.Equal(t, 2, len(webhooks))
assert.GreaterOrEqual(t, len(webhooks), 2)
for _, webhook := range webhooks {
// it should completely replace headers
webhook.Headers = map[string]interface{}{
@@ -58,7 +58,7 @@ func updateWebhookTest(t *testing.T, s TestSetup) {
// Check if webhooks with new name is as per expected len
accessWebhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserAccessEnabledWebhookEvent)
assert.NoError(t, err)
assert.Equal(t, 3, len(accessWebhooks))
assert.GreaterOrEqual(t, len(accessWebhooks), 3)
// Revert name change
res, err = resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
ID: w.ID,
@@ -69,7 +69,7 @@ func updateWebhookTest(t *testing.T, s TestSetup) {
updatedWebhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent)
assert.NoError(t, err)
assert.NotNil(t, updatedWebhooks)
assert.Equal(t, 2, len(updatedWebhooks))
assert.GreaterOrEqual(t, len(updatedWebhooks), 2)
for _, updatedWebhook := range updatedWebhooks {
assert.Contains(t, refs.StringValue(updatedWebhook.EventName), constants.UserDeletedWebhookEvent)
assert.Len(t, updatedWebhook.Headers, 1)

View File

@@ -0,0 +1,61 @@
package test
import (
"fmt"
"strings"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/stretchr/testify/assert"
)
// ValidateSessionTests tests all the validate session resolvers
func validateSessionTests(t *testing.T, s TestSetup) {
t.Helper()
t.Run(`should validate session`, func(t *testing.T) {
req, ctx := createContext(s)
email := "validate_session." + s.TestInfo.Email
resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err := resolvers.ValidateSessionResolver(ctx, &model.ValidateSessionInput{})
assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
assert.NoError(t, err)
assert.NotNil(t, verificationRequest)
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
assert.NoError(t, err)
assert.NotNil(t, verifyRes)
accessToken := *verifyRes.AccessToken
assert.NotEmpty(t, accessToken)
claims, err := token.ParseJWTToken(accessToken)
assert.NoError(t, err)
assert.NotEmpty(t, claims)
sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + verifyRes.User.ID
sessionToken, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
assert.NoError(t, err)
assert.NotEmpty(t, sessionToken)
cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken)
cookie = strings.TrimSuffix(cookie, ";")
res, err := resolvers.ValidateSessionResolver(ctx, &model.ValidateSessionInput{
Cookie: sessionToken,
})
assert.Nil(t, err)
assert.True(t, res.IsValid)
req.Header.Set("Cookie", cookie)
res, err = resolvers.ValidateSessionResolver(ctx, &model.ValidateSessionInput{})
assert.Nil(t, err)
assert.True(t, res.IsValid)
cleanData(email)
})
}

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)
verifyOtpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{
Email: email,
Email: &email,
Otp: otp.Otp,
})
assert.Nil(t, err)

View File

@@ -28,7 +28,7 @@ func webhookTest(t *testing.T, s TestSetup) {
webhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserCreatedWebhookEvent)
assert.NoError(t, err)
assert.NotNil(t, webhooks)
assert.Equal(t, 2, len(webhooks))
assert.GreaterOrEqual(t, len(webhooks), 2)
for _, webhook := range webhooks {
res, err := resolvers.WebhookResolver(ctx, model.WebhookRequest{
ID: webhook.ID,

View File

@@ -30,6 +30,6 @@ func webhooksTest(t *testing.T, s TestSetup) {
})
assert.NoError(t, err)
assert.NotEmpty(t, webhooks)
assert.Len(t, webhooks.Webhooks, len(s.TestInfo.TestWebhookEventTypes)*2)
assert.GreaterOrEqual(t, len(webhooks.Webhooks), len(s.TestInfo.TestWebhookEventTypes)*2)
})
}

View File

@@ -16,6 +16,8 @@ import (
log "github.com/sirupsen/logrus"
)
// RegisterEvent util to register event
// TODO change user to user ref
func RegisterEvent(ctx context.Context, eventName string, authRecipe string, user models.User) error {
webhooks, err := db.Provider.GetWebhookByEventName(ctx, eventName)
if err != nil {