From dba89445653c4a4b9beb7ac251a3ef39e6e994de Mon Sep 17 00:00:00 2001 From: Manoj Date: Mon, 3 Oct 2022 01:08:12 +0530 Subject: [PATCH 01/11] provider: dynamo-db support added --- Makefile | 8 +- server/constants/db_types.go | 2 + server/db/db.go | 13 +- server/db/models/email_templates.go | 16 +- server/db/models/env.go | 12 +- server/db/models/otp.go | 18 +- server/db/models/session.go | 14 +- server/db/models/user.go | 40 ++-- server/db/models/verification_requests.go | 20 +- server/db/models/webhook.go | 16 +- server/db/models/webhook_log.go | 16 +- .../db/providers/dynamodb/email_template.go | 121 +++++++++++ server/db/providers/dynamodb/env.go | 72 +++++++ server/db/providers/dynamodb/otp.go | 80 +++++++ server/db/providers/dynamodb/provider.go | 53 +++++ server/db/providers/dynamodb/session.go | 28 +++ server/db/providers/dynamodb/shared.go | 46 +++++ server/db/providers/dynamodb/user.go | 195 ++++++++++++++++++ .../dynamodb/verification_requests.go | 116 +++++++++++ server/db/providers/dynamodb/webhook.go | 147 +++++++++++++ server/db/providers/dynamodb/webhook_log.go | 78 +++++++ server/go.mod | 3 +- server/go.sum | 18 ++ server/test/resolvers_test.go | 1 + 24 files changed, 1056 insertions(+), 77 deletions(-) create mode 100644 server/db/providers/dynamodb/email_template.go create mode 100644 server/db/providers/dynamodb/env.go create mode 100644 server/db/providers/dynamodb/otp.go create mode 100644 server/db/providers/dynamodb/provider.go create mode 100644 server/db/providers/dynamodb/session.go create mode 100644 server/db/providers/dynamodb/shared.go create mode 100644 server/db/providers/dynamodb/user.go create mode 100644 server/db/providers/dynamodb/verification_requests.go create mode 100644 server/db/providers/dynamodb/webhook.go create mode 100644 server/db/providers/dynamodb/webhook_log.go diff --git a/Makefile b/Makefile index ea87ffa..29e6652 100644 --- a/Makefile +++ b/Makefile @@ -23,15 +23,21 @@ test-arangodb: docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4 cd server && go clean --testcache && TEST_DBS="arangodb" go test -p 1 -v ./test docker rm -vf authorizer_arangodb +test-dynamodb: + docker run -d --name dynamodb-local-test -p 8000:8000 amazon/dynamodb-local:latest + cd server && go clean --testcache && TEST_DBS="dynamodb" go test -p 1 -v ./test + docker rm -vf dynamodb-local-test test-all-db: rm -rf server/test/test.db && rm -rf test.db docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla docker run -d --name authorizer_mongodb_db -p 27017:27017 mongo:4.4.15 docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4 - cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb" go test -p 1 -v ./test + docker run -d --name dynamodb-local-test -p 8000:8000 amazon/dynamodb-local:latest + cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb,dynamodb" go test -p 1 -v ./test docker rm -vf authorizer_scylla_db docker rm -vf authorizer_mongodb_db docker rm -vf authorizer_arangodb + docker rm -vf dynamodb-local-test generate: cd server && go get github.com/99designs/gqlgen/cmd@v0.14.0 && go run github.com/99designs/gqlgen generate \ No newline at end of file diff --git a/server/constants/db_types.go b/server/constants/db_types.go index 72e2668..ead7fc6 100644 --- a/server/constants/db_types.go +++ b/server/constants/db_types.go @@ -25,4 +25,6 @@ const ( DbTypeCockroachDB = "cockroachdb" // DbTypePlanetScaleDB is the planetscale database type DbTypePlanetScaleDB = "planetscale" + // DbTypeDynamoDB is the Dynamo database type + DbTypeDynamoDB = "dynamodb" ) diff --git a/server/db/db.go b/server/db/db.go index 3de2f61..5230306 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -7,6 +7,7 @@ import ( "github.com/authorizerdev/authorizer/server/db/providers" "github.com/authorizerdev/authorizer/server/db/providers/arangodb" "github.com/authorizerdev/authorizer/server/db/providers/cassandradb" + "github.com/authorizerdev/authorizer/server/db/providers/dynamodb" "github.com/authorizerdev/authorizer/server/db/providers/mongodb" "github.com/authorizerdev/authorizer/server/db/providers/sql" "github.com/authorizerdev/authorizer/server/memorystore" @@ -20,10 +21,11 @@ func InitDB() error { envs := memorystore.RequiredEnvStoreObj.GetRequiredEnv() - isSQL := envs.DatabaseType != constants.DbTypeArangodb && envs.DatabaseType != constants.DbTypeMongodb && envs.DatabaseType != constants.DbTypeCassandraDB && envs.DatabaseType != constants.DbTypeScyllaDB + isSQL := envs.DatabaseType != constants.DbTypeArangodb && envs.DatabaseType != constants.DbTypeMongodb && envs.DatabaseType != constants.DbTypeCassandraDB && envs.DatabaseType != constants.DbTypeScyllaDB && envs.DatabaseType != constants.DbTypeDynamoDB isArangoDB := envs.DatabaseType == constants.DbTypeArangodb isMongoDB := envs.DatabaseType == constants.DbTypeMongodb isCassandra := envs.DatabaseType == constants.DbTypeCassandraDB || envs.DatabaseType == constants.DbTypeScyllaDB + isDynamoDB := envs.DatabaseType == constants.DbTypeDynamoDB if isSQL { log.Info("Initializing SQL Driver for: ", envs.DatabaseType) @@ -61,5 +63,14 @@ func InitDB() error { } } + if isDynamoDB { + log.Info("Initializing DynamoDB Driver for: ", envs.DatabaseType) + Provider, err = dynamodb.NewProvider() + if err != nil { + log.Fatal("Failed to initialize DynamoDB driver: ", err) + return err + } + } + return nil } diff --git a/server/db/models/email_templates.go b/server/db/models/email_templates.go index 210848e..95e5891 100644 --- a/server/db/models/email_templates.go +++ b/server/db/models/email_templates.go @@ -9,14 +9,14 @@ import ( // EmailTemplate model for database type EmailTemplate struct { - Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb - ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"` - EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name"` - Subject string `gorm:"type:text" json:"subject" bson:"subject" cql:"subject"` - Template string `gorm:"type:text" json:"template" bson:"template" cql:"template"` - Design string `gorm:"type:text" json:"design" bson:"design" cql:"design"` - CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"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"` + EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name"` + Subject string `gorm:"type:text" json:"subject" bson:"subject" cql:"subject" dynamo:"subject"` + Template string `gorm:"type:text" json:"template" bson:"template" cql:"template" dynamo:"template"` + Design string `gorm:"type:text" json:"design" bson:"design" cql:"design" dynamo:"design"` + 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"` } // AsAPIEmailTemplate to return email template as graphql response object diff --git a/server/db/models/env.go b/server/db/models/env.go index 959284a..1aea291 100644 --- a/server/db/models/env.go +++ b/server/db/models/env.go @@ -4,10 +4,10 @@ package models // Env model for db type Env struct { - Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb - ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"` - EnvData string `gorm:"type:text" json:"env" bson:"env" cql:"env"` - Hash string `gorm:"type:text" json:"hash" bson:"hash" cql:"hash"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"` - CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_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"` + EnvData string `gorm:"type:text" json:"env" bson:"env" cql:"env" dynamo:"env"` + Hash string `gorm:"type:text" json:"hash" bson:"hash" cql:"hash" dynamo:"hash"` + UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"` + CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"` } diff --git a/server/db/models/otp.go b/server/db/models/otp.go index cf0300b..8898c95 100644 --- a/server/db/models/otp.go +++ b/server/db/models/otp.go @@ -2,11 +2,15 @@ package models // OTP model for database type OTP struct { - Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb - ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"` - Email string `gorm:"unique" json:"email" bson:"email" cql:"email"` - Otp string `json:"otp" bson:"otp" cql:"otp"` - ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at"` - CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"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"` + 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 { + ID string `json:"id,omitempty" dynamo:"id,hash"` } diff --git a/server/db/models/session.go b/server/db/models/session.go index 3591593..cd98c85 100644 --- a/server/db/models/session.go +++ b/server/db/models/session.go @@ -4,11 +4,11 @@ package models // Session model for db type Session struct { - Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb - ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"` - UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id"` - UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent"` - IP string `json:"ip" bson:"ip" cql:"ip"` - CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"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"` + UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id" dynamo:"user_id"` + UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent" dynamo:"user_agent"` + IP string `json:"ip" bson:"ip" cql:"ip" dynamo:"ip"` + 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"` } diff --git a/server/db/models/user.go b/server/db/models/user.go index f8a054d..7f6b2ae 100644 --- a/server/db/models/user.go +++ b/server/db/models/user.go @@ -12,27 +12,27 @@ import ( // User model for db type User struct { - Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb - ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"` + 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"` - EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at" cql:"email_verified_at"` - Password *string `gorm:"type:text" json:"password" bson:"password" cql:"password"` - SignupMethods string `json:"signup_methods" bson:"signup_methods" cql:"signup_methods"` - GivenName *string `json:"given_name" bson:"given_name" cql:"given_name"` - FamilyName *string `json:"family_name" bson:"family_name" cql:"family_name"` - MiddleName *string `json:"middle_name" bson:"middle_name" cql:"middle_name"` - Nickname *string `json:"nickname" bson:"nickname" cql:"nickname"` - Gender *string `json:"gender" bson:"gender" cql:"gender"` - Birthdate *string `json:"birthdate" bson:"birthdate" cql:"birthdate"` - PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number"` - PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at" cql:"phone_number_verified_at"` - Picture *string `gorm:"type:text" json:"picture" bson:"picture" cql:"picture"` - Roles string `json:"roles" bson:"roles" cql:"roles"` - RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp" cql:"revoked_timestamp"` - IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled" bson:"is_multi_factor_auth_enabled" cql:"is_multi_factor_auth_enabled"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"` - CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"` + Email string `gorm:"unique" json:"email" bson:"email" cql:"email" dynamo:"email"` + EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at" cql:"email_verified_at" dynamo:"email_verified_at"` + Password *string `gorm:"type:text" json:"password" bson:"password" cql:"password" dynamo:"password"` + SignupMethods string `json:"signup_methods" bson:"signup_methods" cql:"signup_methods" dynamo:"signup_methods"` + GivenName *string `json:"given_name" bson:"given_name" cql:"given_name" dynamo:"given_name"` + FamilyName *string `json:"family_name" bson:"family_name" cql:"family_name" dynamo:"family_name"` + MiddleName *string `json:"middle_name" bson:"middle_name" cql:"middle_name" dynamo:"middle_name"` + Nickname *string `json:"nickname" bson:"nickname" cql:"nickname" dynamo:"nickname"` + Gender *string `json:"gender" bson:"gender" cql:"gender" dynamo:"gender"` + Birthdate *string `json:"birthdate" bson:"birthdate" cql:"birthdate" dynamo:"birthdate"` + PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number"` + PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at" cql:"phone_number_verified_at" dynamo:"phone_number_verified_at"` + Picture *string `gorm:"type:text" json:"picture" bson:"picture" cql:"picture" dynamo:"picture"` + Roles string `json:"roles" bson:"roles" cql:"roles" dynamo:"roles"` + RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp" cql:"revoked_timestamp" dynamo:"revoked_timestamp"` + IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled" bson:"is_multi_factor_auth_enabled" cql:"is_multi_factor_auth_enabled" dynamo:"is_multi_factor_auth_enabled"` + UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"` + CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"` } func (user *User) AsAPIUser() *model.User { diff --git a/server/db/models/verification_requests.go b/server/db/models/verification_requests.go index 992d9d8..a625ab3 100644 --- a/server/db/models/verification_requests.go +++ b/server/db/models/verification_requests.go @@ -11,16 +11,16 @@ import ( // VerificationRequest model for db type VerificationRequest struct { - Key string `json:"_key,omitempty" bson:"_key" cql:"_key,omitempty"` // for arangodb - ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"` - Token string `gorm:"type:text" json:"token" bson:"token" cql:"jwt_token"` // token is reserved keyword in cassandra - Identifier string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(64)" json:"identifier" bson:"identifier" cql:"identifier"` - ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at"` - Email string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email" cql:"email"` - Nonce string `gorm:"type:text" json:"nonce" bson:"nonce" cql:"nonce"` - RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri" cql:"redirect_uri"` - CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"` + Key string `json:"_key,omitempty" bson:"_key" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb + ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"` + Token string `gorm:"type:text" json:"token" bson:"token" cql:"jwt_token" dynamo:"token"` // token is reserved keyword in cassandra + Identifier string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(64)" json:"identifier" bson:"identifier" cql:"identifier" dynamo:"identifier"` + ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"` + Email string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email" cql:"email" dynamo:"email"` + Nonce string `gorm:"type:text" json:"nonce" bson:"nonce" cql:"nonce" dynamo:"nonce"` + RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri" cql:"redirect_uri" dynamo:"redirect_uri"` + 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"` } func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest { diff --git a/server/db/models/webhook.go b/server/db/models/webhook.go index cf7c460..a2f8cfb 100644 --- a/server/db/models/webhook.go +++ b/server/db/models/webhook.go @@ -12,14 +12,14 @@ import ( // Webhook model for db type Webhook struct { - Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb - ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"` - EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name"` - EndPoint string `gorm:"type:text" json:"endpoint" bson:"endpoint" cql:"endpoint"` - Headers string `gorm:"type:text" json:"headers" bson:"headers" cql:"headers"` - Enabled bool `json:"enabled" bson:"enabled" cql:"enabled"` - CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"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"` + EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name"` + EndPoint string `gorm:"type:text" json:"endpoint" bson:"endpoint" cql:"endpoint" dynamo:"endpoint"` + Headers string `gorm:"type:text" json:"headers" bson:"headers" cql:"headers" dynamo:"headers"` + Enabled bool `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"` + 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"` } // AsAPIWebhook to return webhook as graphql response object diff --git a/server/db/models/webhook_log.go b/server/db/models/webhook_log.go index 7305a1d..a74426e 100644 --- a/server/db/models/webhook_log.go +++ b/server/db/models/webhook_log.go @@ -11,14 +11,14 @@ import ( // WebhookLog model for db type WebhookLog struct { - Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb - ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"` - HttpStatus int64 `json:"http_status" bson:"http_status" cql:"http_status"` - Response string `gorm:"type:text" json:"response" bson:"response" cql:"response"` - Request string `gorm:"type:text" json:"request" bson:"request" cql:"request"` - WebhookID string `gorm:"type:char(36)" json:"webhook_id" bson:"webhook_id" cql:"webhook_id"` - CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"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"` + HttpStatus int64 `json:"http_status" bson:"http_status" cql:"http_status" dynamo:"http_status"` + Response string `gorm:"type:text" json:"response" bson:"response" cql:"response" dynamo:"response"` + Request string `gorm:"type:text" json:"request" bson:"request" cql:"request" dynamo:"request"` + WebhookID string `gorm:"type:char(36)" json:"webhook_id" bson:"webhook_id" cql:"webhook_id" dynamo:"webhook_id"` + 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"` } // AsAPIWebhookLog to return webhook log as graphql response object diff --git a/server/db/providers/dynamodb/email_template.go b/server/db/providers/dynamodb/email_template.go new file mode 100644 index 0000000..ae07b00 --- /dev/null +++ b/server/db/providers/dynamodb/email_template.go @@ -0,0 +1,121 @@ +package dynamodb + +import ( + "context" + "errors" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/google/uuid" + "github.com/guregu/dynamo" +) + +// AddEmailTemplate to add EmailTemplate +func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) { + collection := p.db.Table(models.Collections.EmailTemplate) + if emailTemplate.ID == "" { + emailTemplate.ID = uuid.New().String() + } + + emailTemplate.Key = emailTemplate.ID + emailTemplate.CreatedAt = time.Now().Unix() + emailTemplate.UpdatedAt = time.Now().Unix() + err := collection.Put(emailTemplate).RunWithContext(ctx) + + if err != nil { + return emailTemplate.AsAPIEmailTemplate(), err + } + + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// UpdateEmailTemplate to update EmailTemplate +func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) { + collection := p.db.Table(models.Collections.EmailTemplate) + emailTemplate.UpdatedAt = time.Now().Unix() + err := UpdateByHashKey(collection, "id", emailTemplate.ID, emailTemplate) + if err != nil { + return emailTemplate.AsAPIEmailTemplate(), err + } + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// ListEmailTemplates to list EmailTemplate +func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) { + + var emailTemplate models.EmailTemplate + var iter dynamo.PagingIter + var lastEval dynamo.PagingKey + var iteration int64 = 0 + + collection := p.db.Table(models.Collections.EmailTemplate) + emailTemplates := []*model.EmailTemplate{} + paginationClone := pagination + scanner := collection.Scan() + count, err := scanner.Count() + + if err != nil { + return nil, err + } + + for (paginationClone.Offset + paginationClone.Limit) > iteration { + iter = scanner.StartFrom(lastEval).Limit(paginationClone.Limit).Iter() + for iter.NextWithContext(ctx, &emailTemplate) { + if paginationClone.Offset == iteration { + emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate()) + } + } + lastEval = iter.LastEvaluatedKey() + iteration += paginationClone.Limit + } + + paginationClone.Total = count + + return &model.EmailTemplates{ + Pagination: &paginationClone, + EmailTemplates: emailTemplates, + }, nil +} + +// GetEmailTemplateByID to get EmailTemplate by id +func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) { + collection := p.db.Table(models.Collections.EmailTemplate) + var emailTemplate models.EmailTemplate + err := collection.Get("id", emailTemplateID).OneWithContext(ctx, &emailTemplate) + if err != nil { + return nil, err + } + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// GetEmailTemplateByEventName to get EmailTemplate by event_name +func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) { + collection := p.db.Table(models.Collections.EmailTemplate) + var emailTemplates []models.EmailTemplate + var emailTemplate models.EmailTemplate + + err := collection.Scan().Filter("'event_name' = ?", eventName).Limit(1).AllWithContext(ctx, &emailTemplates) + if err != nil { + return nil, err + } + if len(emailTemplates) > 0 { + emailTemplate = emailTemplates[0] + return emailTemplate.AsAPIEmailTemplate(), nil + } else { + return nil, errors.New("no record found") + } + +} + +// DeleteEmailTemplate to delete EmailTemplate +func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error { + collection := p.db.Table(models.Collections.EmailTemplate) + err := collection.Delete("id", emailTemplate.ID).RunWithContext(ctx) + + if err != nil { + return err + } + + return nil +} diff --git a/server/db/providers/dynamodb/env.go b/server/db/providers/dynamodb/env.go new file mode 100644 index 0000000..7179555 --- /dev/null +++ b/server/db/providers/dynamodb/env.go @@ -0,0 +1,72 @@ +package dynamodb + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/google/uuid" +) + +// AddEnv to save environment information in database +func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) { + collection := p.db.Table(models.Collections.Env) + + if env.ID == "" { + env.ID = uuid.New().String() + } + + env.Key = env.ID + + env.CreatedAt = time.Now().Unix() + env.UpdatedAt = time.Now().Unix() + + err := collection.Put(env).RunWithContext(ctx) + + if err != nil { + return env, err + } + + return env, nil +} + +// UpdateEnv to update environment information in database +func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) { + + collection := p.db.Table(models.Collections.Env) + env.UpdatedAt = time.Now().Unix() + + err := UpdateByHashKey(collection, "id", env.ID, env) + + if err != nil { + return env, err + } + return env, nil +} + +// GetEnv to get environment information from database +func (p *provider) GetEnv(ctx context.Context) (models.Env, error) { + var env models.Env + + collection := p.db.Table(models.Collections.Env) + // As there is no Findone supported. + iter := collection.Scan().Limit(1).Iter() + + for iter.NextWithContext(ctx, &env) { + if env.ID == "" { + return env, errors.New("no documets found") + } else { + return env, nil + } + } + + err := iter.Err() + + if err != nil { + return env, fmt.Errorf("config not found") + } + + return env, nil +} diff --git a/server/db/providers/dynamodb/otp.go b/server/db/providers/dynamodb/otp.go new file mode 100644 index 0000000..42e10ab --- /dev/null +++ b/server/db/providers/dynamodb/otp.go @@ -0,0 +1,80 @@ +package dynamodb + +import ( + "context" + "errors" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/google/uuid" +) + +// 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) + 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(), + } + 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) + } else { + err = UpdateByHashKey(collection, "id", otp.ID, otp) + } + 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 otps []models.OTP + var otp models.OTP + + collection := p.db.Table(models.Collections.OTP) + + err := collection.Scan().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") + } +} + +// DeleteOTP to delete otp +func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error { + collection := p.db.Table(models.Collections.OTP) + + if otp.ID != "" { + err := collection.Delete("id", otp.ID).RunWithContext(ctx) + if err != nil { + return err + } + } + + return nil +} diff --git a/server/db/providers/dynamodb/provider.go b/server/db/providers/dynamodb/provider.go new file mode 100644 index 0000000..9b5efc8 --- /dev/null +++ b/server/db/providers/dynamodb/provider.go @@ -0,0 +1,53 @@ +package dynamodb + +import ( + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/guregu/dynamo" +) + +// TODO change following provider to new db provider +type provider struct { + db *dynamo.DB +} + +// NewProvider returns a new SQL provider +// TODO change following provider to new db provider +func NewProvider() (*provider, error) { + config := aws.Config{ + Endpoint: aws.String("http://localhost:8000"), + Region: aws.String("us-east-1"), + } + session := session.Must(session.NewSession()) + db := dynamo.New(session, &config) + + if err := db.CreateTable(models.Collections.User, models.User{}).Wait(); err != nil { + // fmt.Println(" User", err) + } + if err := db.CreateTable(models.Collections.Session, models.Session{}).Wait(); err != nil { + // fmt.Println("Session error", err) + } + if err := db.CreateTable(models.Collections.EmailTemplate, models.EmailTemplate{}).Wait(); err != nil { + // fmt.Println(" EmailTemplate", err) + } + if err := db.CreateTable(models.Collections.Env, models.Env{}).Wait(); err != nil { + // fmt.Println(" Env", err) + } + if err := db.CreateTable(models.Collections.OTP, models.OTP{}).Wait(); err != nil { + // fmt.Println(" OTP", err) + } + if err := db.CreateTable(models.Collections.VerificationRequest, models.VerificationRequest{}).Wait(); err != nil { + // fmt.Println(" VerificationRequest", err) + } + if err := db.CreateTable(models.Collections.Webhook, models.Webhook{}).Wait(); err != nil { + // fmt.Println(" Webhook", err) + } + if err := db.CreateTable(models.Collections.WebhookLog, models.WebhookLog{}).Wait(); err != nil { + // fmt.Println(" WebhookLog", err) + } + + return &provider{ + db: db, + }, nil +} diff --git a/server/db/providers/dynamodb/session.go b/server/db/providers/dynamodb/session.go new file mode 100644 index 0000000..68457e5 --- /dev/null +++ b/server/db/providers/dynamodb/session.go @@ -0,0 +1,28 @@ +package dynamodb + +import ( + "context" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/google/uuid" +) + +// AddSession to save session information in database +func (p *provider) AddSession(ctx context.Context, session models.Session) error { + collection := p.db.Table(models.Collections.Session) + + if session.ID == "" { + session.ID = uuid.New().String() + } + + session.CreatedAt = time.Now().Unix() + session.UpdatedAt = time.Now().Unix() + err := collection.Put(session).RunWithContext(ctx) + return err +} + +// DeleteSession to delete session information from database +func (p *provider) DeleteSession(ctx context.Context, userId string) error { + return nil +} diff --git a/server/db/providers/dynamodb/shared.go b/server/db/providers/dynamodb/shared.go new file mode 100644 index 0000000..ed91eca --- /dev/null +++ b/server/db/providers/dynamodb/shared.go @@ -0,0 +1,46 @@ +package dynamodb + +import ( + "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/guregu/dynamo" +) + +// As updpate all item not supported so set manually via Set and SetNullable for empty field +func UpdateByHashKey(table dynamo.Table, hashKey string, hashValue string, item interface{}) error { + existingValue, err := dynamo.MarshalItem(item) + var i interface{} + + if err != nil { + return err + } + + nullableValue, err := dynamodbattribute.MarshalMap(item) + if err != nil { + return err + } + + u := table.Update(hashKey, hashValue) + for k, v := range existingValue { + if k == hashKey { + continue + } + u = u.Set(k, v) + } + + for k, v := range nullableValue { + if k == hashKey { + continue + } + dynamodbattribute.Unmarshal(v, &i) + if i == nil { + u = u.SetNullable(k, v) + } + } + + err = u.Run() + if err != nil { + return err + } + + return nil +} diff --git a/server/db/providers/dynamodb/user.go b/server/db/providers/dynamodb/user.go new file mode 100644 index 0000000..5617065 --- /dev/null +++ b/server/db/providers/dynamodb/user.go @@ -0,0 +1,195 @@ +package dynamodb + +import ( + "context" + "errors" + "time" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/google/uuid" + "github.com/guregu/dynamo" + log "github.com/sirupsen/logrus" +) + +// AddUser to save user information in database +func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) { + collection := p.db.Table(models.Collections.User) + + if user.ID == "" { + user.ID = uuid.New().String() + } + + if user.Roles == "" { + defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles) + if err != nil { + return user, err + } + user.Roles = defaultRoles + } + + user.CreatedAt = time.Now().Unix() + user.UpdatedAt = time.Now().Unix() + + err := collection.Put(user).RunWithContext(ctx) + + if err != nil { + return user, err + } + return user, nil +} + +// UpdateUser to update user information in database +func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) { + collection := p.db.Table(models.Collections.User) + + if user.ID != "" { + + user.UpdatedAt = time.Now().Unix() + + err := UpdateByHashKey(collection, "id", user.ID, user) + if err != nil { + return user, err + } + + if err != nil { + return user, err + } + + } + return user, nil +} + +// DeleteUser to delete user information from database +func (p *provider) DeleteUser(ctx context.Context, user models.User) error { + collection := p.db.Table(models.Collections.User) + sessionCollection := p.db.Table(models.Collections.Session) + + if user.ID != "" { + err := collection.Delete("id", user.ID).Run() + if err != nil { + return err + } + + _, err = sessionCollection.Batch("id").Write().Delete(dynamo.Keys{"user_id", user.ID}).RunWithContext(ctx) + + if err != nil { + return err + } + } + return nil +} + +// ListUsers to get list of users from database +func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) { + var user models.User + var lastEval dynamo.PagingKey + var iter dynamo.PagingIter + var iteration int64 = 0 + + collection := p.db.Table(models.Collections.User) + users := []*model.User{} + + paginationClone := pagination + scanner := collection.Scan() + count, err := scanner.Count() + + if err != nil { + return nil, err + } + + for (paginationClone.Offset + paginationClone.Limit) > iteration { + iter = scanner.StartFrom(lastEval).Limit(paginationClone.Limit).Iter() + for iter.NextWithContext(ctx, &user) { + if paginationClone.Offset == iteration { + users = append(users, user.AsAPIUser()) + } + } + lastEval = iter.LastEvaluatedKey() + iteration += paginationClone.Limit + } + + err = iter.Err() + + if err != nil { + return nil, err + } + + paginationClone.Total = count + + return &model.Users{ + Pagination: &paginationClone, + Users: users, + }, nil +} + +// GetUserByEmail to get user information from database using email address +func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) { + var users []models.User + var user models.User + + collection := p.db.Table(models.Collections.User) + err := collection.Scan().Filter("'email' = ?", email).AllWithContext(ctx, &users) + + if err != nil { + return user, nil + } + + if len(users) > 0 { + user = users[0] + return user, nil + } else { + return user, errors.New("no record found") + } + +} + +// GetUserByID to get user information from database using user ID +func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) { + collection := p.db.Table(models.Collections.User) + var user models.User + err := collection.Get("id", id).OneWithContext(ctx, &user) + + if err != nil { + if user.Email == "" { + return user, errors.New("no documets found") + } else { + return user, nil + } + } + return user, nil +} + +// UpdateUsers to update multiple users, with parameters of user IDs slice +// If ids set to nil / empty all the users will be updated +func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{}, ids []string) error { + // set updated_at time for all users + userCollection := p.db.Table(models.Collections.User) + var allUsers []models.User + var res int64 = 0 + var err error + if len(ids) > 0 { + for _, v := range ids { + err = UpdateByHashKey(userCollection, "id", v, data) + } + } else { + // as there is no facility to update all doc - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SQLtoNoSQL.UpdateData.html + userCollection.Scan().All(&allUsers) + + for _, user := range allUsers { + err = UpdateByHashKey(userCollection, "id", user.ID, data) + if err != nil { + res = res + 1 + } + } + } + + if err != nil { + return err + } else { + log.Info("Updated users: ", res) + } + return nil +} diff --git a/server/db/providers/dynamodb/verification_requests.go b/server/db/providers/dynamodb/verification_requests.go new file mode 100644 index 0000000..990c288 --- /dev/null +++ b/server/db/providers/dynamodb/verification_requests.go @@ -0,0 +1,116 @@ +package dynamodb + +import ( + "context" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/google/uuid" + "github.com/guregu/dynamo" +) + +// AddVerification to save verification request in database +func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) { + collection := p.db.Table(models.Collections.VerificationRequest) + + if verificationRequest.ID == "" { + verificationRequest.ID = uuid.New().String() + verificationRequest.CreatedAt = time.Now().Unix() + verificationRequest.UpdatedAt = time.Now().Unix() + err := collection.Put(verificationRequest).RunWithContext(ctx) + if err != nil { + return verificationRequest, err + } + } + + return verificationRequest, nil +} + +// GetVerificationRequestByToken to get verification request from database using token +func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) { + collection := p.db.Table(models.Collections.VerificationRequest) + var verificationRequest models.VerificationRequest + + iter := collection.Scan().Filter("'token' = ?", token).Iter() + for iter.NextWithContext(ctx, &verificationRequest) { + return verificationRequest, nil + } + + err := iter.Err() + if err != nil { + return verificationRequest, err + } + return verificationRequest, nil +} + +// GetVerificationRequestByEmail to get verification request by email from database +func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) { + var verificationRequest models.VerificationRequest + collection := p.db.Table(models.Collections.VerificationRequest) + iter := collection.Scan().Filter("'email' = ?", email).Filter("'identifier' = ?", identifier).Iter() + for iter.NextWithContext(ctx, &verificationRequest) { + return verificationRequest, nil + } + + err := iter.Err() + if err != nil { + return verificationRequest, err + } + return verificationRequest, nil +} + +// ListVerificationRequests to get list of verification requests from database +func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) { + verificationRequests := []*model.VerificationRequest{} + var verificationRequest models.VerificationRequest + var lastEval dynamo.PagingKey + var iter dynamo.PagingIter + var iteration int64 = 0 + + collection := p.db.Table(models.Collections.VerificationRequest) + paginationClone := pagination + + scanner := collection.Scan() + count, err := scanner.Count() + + if err != nil { + return nil, err + } + + for (paginationClone.Offset + paginationClone.Limit) > iteration { + iter = scanner.StartFrom(lastEval).Limit(paginationClone.Limit).Iter() + for iter.NextWithContext(ctx, &verificationRequest) { + if paginationClone.Offset == iteration { + verificationRequests = append(verificationRequests, verificationRequest.AsAPIVerificationRequest()) + } + } + err = iter.Err() + if err != nil { + return nil, err + } + lastEval = iter.LastEvaluatedKey() + iteration += paginationClone.Limit + } + + paginationClone.Total = count + + return &model.VerificationRequests{ + VerificationRequests: verificationRequests, + Pagination: &paginationClone, + }, nil +} + +// DeleteVerificationRequest to delete verification request from database +func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error { + collection := p.db.Table(models.Collections.VerificationRequest) + + if verificationRequest.ID != "" { + err := collection.Delete("id", verificationRequest.ID).RunWithContext(ctx) + + if err != nil { + return err + } + } + return nil +} diff --git a/server/db/providers/dynamodb/webhook.go b/server/db/providers/dynamodb/webhook.go new file mode 100644 index 0000000..0883418 --- /dev/null +++ b/server/db/providers/dynamodb/webhook.go @@ -0,0 +1,147 @@ +package dynamodb + +import ( + "context" + "errors" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/google/uuid" + "github.com/guregu/dynamo" +) + +// AddWebhook to add webhook +func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) { + collection := p.db.Table(models.Collections.Webhook) + + if webhook.ID == "" { + webhook.ID = uuid.New().String() + } + + webhook.Key = webhook.ID + webhook.CreatedAt = time.Now().Unix() + webhook.UpdatedAt = time.Now().Unix() + err := collection.Put(webhook).RunWithContext(ctx) + + if err != nil { + return nil, err + } + return webhook.AsAPIWebhook(), nil +} + +// UpdateWebhook to update webhook +func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) { + collection := p.db.Table(models.Collections.Webhook) + + webhook.UpdatedAt = time.Now().Unix() + err := UpdateByHashKey(collection, "id", webhook.ID, webhook) + + if err != nil { + return nil, err + } + return webhook.AsAPIWebhook(), nil +} + +// ListWebhooks to list webhook +func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) { + webhooks := []*model.Webhook{} + var webhook models.Webhook + var lastEval dynamo.PagingKey + var iter dynamo.PagingIter + var iteration int64 = 0 + + collection := p.db.Table(models.Collections.Webhook) + paginationClone := pagination + scanner := collection.Scan() + count, err := scanner.Count() + + if err != nil { + return nil, err + } + + for (paginationClone.Offset + paginationClone.Limit) > iteration { + iter = scanner.StartFrom(lastEval).Limit(paginationClone.Limit).Iter() + for iter.NextWithContext(ctx, &webhook) { + if paginationClone.Offset == iteration { + webhooks = append(webhooks, webhook.AsAPIWebhook()) + } + } + err = iter.Err() + if err != nil { + return nil, err + } + lastEval = iter.LastEvaluatedKey() + iteration += paginationClone.Limit + } + + paginationClone.Total = count + + return &model.Webhooks{ + Pagination: &paginationClone, + Webhooks: webhooks, + }, nil +} + +// GetWebhookByID to get webhook by id +func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) { + collection := p.db.Table(models.Collections.Webhook) + var webhook models.Webhook + + err := collection.Get("id", webhookID).OneWithContext(ctx, &webhook) + + if err != nil { + return nil, err + } + + if webhook.ID == "" { + return webhook.AsAPIWebhook(), errors.New("no documets found") + } + + return webhook.AsAPIWebhook(), nil +} + +// GetWebhookByEventName to get webhook by event_name +func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) { + var webhook models.Webhook + collection := p.db.Table(models.Collections.Webhook) + + iter := collection.Scan().Filter("'event_name' = ?", eventName).Iter() + + for iter.NextWithContext(ctx, &webhook) { + return webhook.AsAPIWebhook(), nil + } + + err := iter.Err() + + if err != nil { + return webhook.AsAPIWebhook(), err + } + return webhook.AsAPIWebhook(), nil +} + +// DeleteWebhook to delete webhook +func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error { + // Also delete webhook logs for given webhook id + if webhook.ID != "" { + webhookCollection := p.db.Table(models.Collections.Webhook) + pagination := model.Pagination{} + webhookLogCollection := p.db.Table(models.Collections.WebhookLog) + err := webhookCollection.Delete("id", webhook.ID).RunWithContext(ctx) + if err != nil { + return err + } + webhookLogs, errIs := p.ListWebhookLogs(ctx, pagination, webhook.ID) + + for _, webhookLog := range webhookLogs.WebhookLogs { + err = webhookLogCollection.Delete("id", webhookLog.ID).RunWithContext(ctx) + if err != nil { + return err + } + } + if errIs != nil { + return errIs + } + } + return nil +} diff --git a/server/db/providers/dynamodb/webhook_log.go b/server/db/providers/dynamodb/webhook_log.go new file mode 100644 index 0000000..e4ca7d5 --- /dev/null +++ b/server/db/providers/dynamodb/webhook_log.go @@ -0,0 +1,78 @@ +package dynamodb + +import ( + "context" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/google/uuid" + "github.com/guregu/dynamo" +) + +// AddWebhookLog to add webhook log +func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) { + collection := p.db.Table(models.Collections.WebhookLog) + + if webhookLog.ID == "" { + webhookLog.ID = uuid.New().String() + } + + webhookLog.Key = webhookLog.ID + webhookLog.CreatedAt = time.Now().Unix() + webhookLog.UpdatedAt = time.Now().Unix() + err := collection.Put(webhookLog).RunWithContext(ctx) + + if err != nil { + return nil, err + } + return webhookLog.AsAPIWebhookLog(), nil +} + +// ListWebhookLogs to list webhook logs +func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) { + webhookLogs := []*model.WebhookLog{} + var webhookLog models.WebhookLog + var lastEval dynamo.PagingKey + var iter dynamo.PagingIter + var iteration int64 = 0 + var err error + var count int64 + + collection := p.db.Table(models.Collections.WebhookLog) + paginationClone := pagination + scanner := collection.Scan() + + if webhookID != "" { + iter = scanner.Filter("'webhook_id' = ?", webhookID).Iter() + for iter.NextWithContext(ctx, &webhookLog) { + webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog()) + } + err = iter.Err() + if err != nil { + return nil, err + } + } else { + for (paginationClone.Offset + paginationClone.Limit) > iteration { + iter = scanner.StartFrom(lastEval).Limit(paginationClone.Limit).Iter() + for iter.NextWithContext(ctx, &webhookLog) { + if paginationClone.Offset == iteration { + webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog()) + } + } + err = iter.Err() + if err != nil { + return nil, err + } + lastEval = iter.LastEvaluatedKey() + iteration += paginationClone.Limit + } + } + + paginationClone.Total = count + // paginationClone.Cursor = iter.LastEvaluatedKey() + return &model.WebhookLogs{ + Pagination: &paginationClone, + WebhookLogs: webhookLogs, + }, nil +} diff --git a/server/go.mod b/server/go.mod index 8b5cea4..aaba056 100644 --- a/server/go.mod +++ b/server/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/99designs/gqlgen v0.14.0 github.com/arangodb/go-driver v1.2.1 + github.com/aws/aws-sdk-go v1.44.109 github.com/coreos/go-oidc/v3 v3.1.0 github.com/gin-gonic/gin v1.7.2 github.com/go-playground/validator/v10 v10.8.0 // indirect @@ -13,6 +14,7 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/protobuf v1.5.2 // indirect github.com/google/uuid v1.3.0 + github.com/guregu/dynamo v1.16.0 github.com/joho/godotenv v1.3.0 github.com/json-iterator/go v1.1.11 // indirect github.com/mattn/go-isatty v0.0.13 // indirect @@ -26,7 +28,6 @@ require ( github.com/vektah/gqlparser/v2 v2.2.0 go.mongodb.org/mongo-driver v1.8.1 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 - golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect diff --git a/server/go.sum b/server/go.sum index c51da71..1cad78a 100644 --- a/server/go.sum +++ b/server/go.sum @@ -48,10 +48,15 @@ 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/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -114,6 +119,8 @@ 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.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +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/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 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= @@ -182,6 +189,8 @@ github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/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/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= @@ -243,6 +252,9 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.3 h1:PlHq1bSCSZL9K0wUhbm2pGLoTWs2GwVhsP6emvGV/ZI= github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -466,6 +478,9 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/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 h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 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= @@ -526,8 +541,11 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/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/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= diff --git a/server/test/resolvers_test.go b/server/test/resolvers_test.go index 17373fc..ce8588f 100644 --- a/server/test/resolvers_test.go +++ b/server/test/resolvers_test.go @@ -20,6 +20,7 @@ func TestResolvers(t *testing.T) { constants.DbTypeArangodb: "http://localhost:8529", constants.DbTypeMongodb: "mongodb://localhost:27017", constants.DbTypeScyllaDB: "127.0.0.1:9042", + constants.DbTypeDynamoDB: "127.0.0.1:8000", } testDBs := strings.Split(os.Getenv("TEST_DBS"), ",") From cc4eaa284794a1b1950969032fc218e3aca07240 Mon Sep 17 00:00:00 2001 From: Manoj Date: Wed, 5 Oct 2022 15:32:32 +0530 Subject: [PATCH 02/11] dynamod db index changes added to the schema --- .env.test | 7 ++- server/constants/env.go | 6 ++ server/db/models/email_templates.go | 2 +- server/db/models/otp.go | 2 +- server/db/models/session.go | 2 +- server/db/models/user.go | 2 +- server/db/models/verification_requests.go | 2 +- server/db/models/webhook.go | 2 +- server/db/models/webhook_log.go | 2 +- .../db/providers/dynamodb/email_template.go | 2 +- server/db/providers/dynamodb/otp.go | 2 +- server/db/providers/dynamodb/provider.go | 58 +++++++++---------- server/db/providers/dynamodb/user.go | 2 +- server/db/providers/dynamodb/webhook.go | 3 +- server/db/providers/dynamodb/webhook_log.go | 2 +- server/memorystore/required_env_store.go | 9 +++ 16 files changed, 60 insertions(+), 45 deletions(-) diff --git a/.env.test b/.env.test index 0df0238..89465a8 100644 --- a/.env.test +++ b/.env.test @@ -1,9 +1,10 @@ ENV=test -DATABASE_URL=test.db -DATABASE_TYPE=sqlite +DATABASE_URL=http://localhost:8000 +DATABASE_TYPE=dynamodb +REGION=us-east-1 CUSTOM_ACCESS_TOKEN_SCRIPT="function(user,tokenPayload){var data = tokenPayload;data.extra = {'x-extra-id': user.id};return data;}" SMTP_HOST=smtp.mailtrap.io SMTP_PORT=2525 SMTP_USERNAME=test SMTP_PASSWORD=test -SENDER_EMAIL="info@authorizer.dev" \ No newline at end of file +SENDER_EMAIL="info@authorizer.dev" \ No newline at end of file diff --git a/server/constants/env.go b/server/constants/env.go index a675dcc..cc1ddfc 100644 --- a/server/constants/env.go +++ b/server/constants/env.go @@ -21,6 +21,12 @@ const ( EnvKeyDatabaseType = "DATABASE_TYPE" // EnvKeyDatabaseURL key for env variable DATABASE_URL EnvKeyDatabaseURL = "DATABASE_URL" + // EnvAwsRegion key for env variable AWS REGION + EnvAwsRegion = "REGION" + // EnvAwsAccessKey key for env variable AWS_ACCESS_KEY + EnvAwsAccessKey = "AWS_ACCESS_KEY" + // EnvAwsAccessKey key for env variable AWS_SECRET_KEY + EnvAwsSecretKey = "AWS_SECRET_KEY" // EnvKeyDatabaseName key for env variable DATABASE_NAME EnvKeyDatabaseName = "DATABASE_NAME" // EnvKeyDatabaseUsername key for env variable DATABASE_USERNAME diff --git a/server/db/models/email_templates.go b/server/db/models/email_templates.go index 95e5891..6608a17 100644 --- a/server/db/models/email_templates.go +++ b/server/db/models/email_templates.go @@ -11,7 +11,7 @@ import ( type EmailTemplate 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"` - EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name"` + EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"` Subject string `gorm:"type:text" json:"subject" bson:"subject" cql:"subject" dynamo:"subject"` Template string `gorm:"type:text" json:"template" bson:"template" cql:"template" dynamo:"template"` Design string `gorm:"type:text" json:"design" bson:"design" cql:"design" dynamo:"design"` diff --git a/server/db/models/otp.go b/server/db/models/otp.go index 8898c95..ac9732b 100644 --- a/server/db/models/otp.go +++ b/server/db/models/otp.go @@ -4,7 +4,7 @@ package models 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"` + 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"` diff --git a/server/db/models/session.go b/server/db/models/session.go index cd98c85..8460c66 100644 --- a/server/db/models/session.go +++ b/server/db/models/session.go @@ -6,7 +6,7 @@ package models type Session 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"` - UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id" dynamo:"user_id"` + UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id" dynamo:"user_id" index:"user_id,hash"` UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent" dynamo:"user_agent"` IP string `json:"ip" bson:"ip" cql:"ip" dynamo:"ip"` CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"` diff --git a/server/db/models/user.go b/server/db/models/user.go index 7f6b2ae..3b533a3 100644 --- a/server/db/models/user.go +++ b/server/db/models/user.go @@ -15,7 +15,7 @@ type User 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"` + Email string `gorm:"unique" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"` EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at" cql:"email_verified_at" dynamo:"email_verified_at"` Password *string `gorm:"type:text" json:"password" bson:"password" cql:"password" dynamo:"password"` SignupMethods string `json:"signup_methods" bson:"signup_methods" cql:"signup_methods" dynamo:"signup_methods"` diff --git a/server/db/models/verification_requests.go b/server/db/models/verification_requests.go index a625ab3..5d4179d 100644 --- a/server/db/models/verification_requests.go +++ b/server/db/models/verification_requests.go @@ -13,7 +13,7 @@ import ( type VerificationRequest struct { Key string `json:"_key,omitempty" bson:"_key" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"` - Token string `gorm:"type:text" json:"token" bson:"token" cql:"jwt_token" dynamo:"token"` // token is reserved keyword in cassandra + Token string `gorm:"type:text" json:"token" bson:"token" cql:"jwt_token" dynamo:"token" index:"token,hash"` Identifier string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(64)" json:"identifier" bson:"identifier" cql:"identifier" dynamo:"identifier"` ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"` Email string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email" cql:"email" dynamo:"email"` diff --git a/server/db/models/webhook.go b/server/db/models/webhook.go index a2f8cfb..5f9f8f7 100644 --- a/server/db/models/webhook.go +++ b/server/db/models/webhook.go @@ -14,7 +14,7 @@ import ( type Webhook 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"` - EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name"` + EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"` EndPoint string `gorm:"type:text" json:"endpoint" bson:"endpoint" cql:"endpoint" dynamo:"endpoint"` Headers string `gorm:"type:text" json:"headers" bson:"headers" cql:"headers" dynamo:"headers"` Enabled bool `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"` diff --git a/server/db/models/webhook_log.go b/server/db/models/webhook_log.go index a74426e..6dda00f 100644 --- a/server/db/models/webhook_log.go +++ b/server/db/models/webhook_log.go @@ -16,7 +16,7 @@ type WebhookLog struct { HttpStatus int64 `json:"http_status" bson:"http_status" cql:"http_status" dynamo:"http_status"` Response string `gorm:"type:text" json:"response" bson:"response" cql:"response" dynamo:"response"` Request string `gorm:"type:text" json:"request" bson:"request" cql:"request" dynamo:"request"` - WebhookID string `gorm:"type:char(36)" json:"webhook_id" bson:"webhook_id" cql:"webhook_id" dynamo:"webhook_id"` + WebhookID string `gorm:"type:char(36)" json:"webhook_id" bson:"webhook_id" cql:"webhook_id" dynamo:"webhook_id" index:"webhook_id,hash"` 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"` } diff --git a/server/db/providers/dynamodb/email_template.go b/server/db/providers/dynamodb/email_template.go index ae07b00..08745cf 100644 --- a/server/db/providers/dynamodb/email_template.go +++ b/server/db/providers/dynamodb/email_template.go @@ -95,7 +95,7 @@ func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName st var emailTemplates []models.EmailTemplate var emailTemplate models.EmailTemplate - err := collection.Scan().Filter("'event_name' = ?", eventName).Limit(1).AllWithContext(ctx, &emailTemplates) + err := collection.Scan().Index("event_name").Filter("'event_name' = ?", eventName).Limit(1).AllWithContext(ctx, &emailTemplates) if err != nil { return nil, err } diff --git a/server/db/providers/dynamodb/otp.go b/server/db/providers/dynamodb/otp.go index 42e10ab..063f634 100644 --- a/server/db/providers/dynamodb/otp.go +++ b/server/db/providers/dynamodb/otp.go @@ -52,7 +52,7 @@ func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*mod collection := p.db.Table(models.Collections.OTP) - err := collection.Scan().Filter("'email' = ?", emailAddress).Limit(1).AllWithContext(ctx, &otps) + err := collection.Scan().Index("email").Filter("'email' = ?", emailAddress).Limit(1).AllWithContext(ctx, &otps) if err != nil { return nil, err diff --git a/server/db/providers/dynamodb/provider.go b/server/db/providers/dynamodb/provider.go index 9b5efc8..962a222 100644 --- a/server/db/providers/dynamodb/provider.go +++ b/server/db/providers/dynamodb/provider.go @@ -2,51 +2,49 @@ package dynamodb import ( "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/memorystore" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/guregu/dynamo" ) -// TODO change following provider to new db provider type provider struct { db *dynamo.DB } -// NewProvider returns a new SQL provider -// TODO change following provider to new db provider +// NewProvider returns a new Dynamo provider func NewProvider() (*provider, error) { + region := memorystore.RequiredEnvStoreObj.GetRequiredEnv().REGION + dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL + accessKey := memorystore.RequiredEnvStoreObj.GetRequiredEnv().AWS_ACCESS_KEY + secretKey := memorystore.RequiredEnvStoreObj.GetRequiredEnv().AWS_SECRET_KEY config := aws.Config{ - Endpoint: aws.String("http://localhost:8000"), - Region: aws.String("us-east-1"), + Region: aws.String(region), + MaxRetries: aws.Int(3), } - session := session.Must(session.NewSession()) - db := dynamo.New(session, &config) - if err := db.CreateTable(models.Collections.User, models.User{}).Wait(); err != nil { - // fmt.Println(" User", err) - } - if err := db.CreateTable(models.Collections.Session, models.Session{}).Wait(); err != nil { - // fmt.Println("Session error", err) - } - if err := db.CreateTable(models.Collections.EmailTemplate, models.EmailTemplate{}).Wait(); err != nil { - // fmt.Println(" EmailTemplate", err) - } - if err := db.CreateTable(models.Collections.Env, models.Env{}).Wait(); err != nil { - // fmt.Println(" Env", err) - } - if err := db.CreateTable(models.Collections.OTP, models.OTP{}).Wait(); err != nil { - // fmt.Println(" OTP", err) - } - if err := db.CreateTable(models.Collections.VerificationRequest, models.VerificationRequest{}).Wait(); err != nil { - // fmt.Println(" VerificationRequest", err) - } - if err := db.CreateTable(models.Collections.Webhook, models.Webhook{}).Wait(); err != nil { - // fmt.Println(" Webhook", err) - } - if err := db.CreateTable(models.Collections.WebhookLog, models.WebhookLog{}).Wait(); err != nil { - // fmt.Println(" WebhookLog", err) + // custom accessKey, secretkey took first priority, if not then fetch config from aws credentials + if accessKey != "" && secretKey != "" { + config.Credentials = credentials.NewStaticCredentials(accessKey, secretKey, "") + } else if dbURL != "" { + // static config in case of testing or local-setup + config.Credentials = credentials.NewStaticCredentials("key", "key", "") + config.Endpoint = aws.String(dbURL) } + session := session.Must(session.NewSession(&config)) + db := dynamo.New(session) + + db.CreateTable(models.Collections.User, models.User{}).Wait() + db.CreateTable(models.Collections.Session, models.Session{}).Wait() + db.CreateTable(models.Collections.EmailTemplate, models.EmailTemplate{}).Wait() + db.CreateTable(models.Collections.Env, models.Env{}).Wait() + db.CreateTable(models.Collections.OTP, models.OTP{}).Wait() + db.CreateTable(models.Collections.VerificationRequest, models.VerificationRequest{}).Wait() + db.CreateTable(models.Collections.Webhook, models.Webhook{}).Wait() + db.CreateTable(models.Collections.WebhookLog, models.WebhookLog{}).Wait() + return &provider{ db: db, }, nil diff --git a/server/db/providers/dynamodb/user.go b/server/db/providers/dynamodb/user.go index 5617065..ef780b1 100644 --- a/server/db/providers/dynamodb/user.go +++ b/server/db/providers/dynamodb/user.go @@ -131,7 +131,7 @@ func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.Use var user models.User collection := p.db.Table(models.Collections.User) - err := collection.Scan().Filter("'email' = ?", email).AllWithContext(ctx, &users) + err := collection.Scan().Index("email").Filter("'email' = ?", email).AllWithContext(ctx, &users) if err != nil { return user, nil diff --git a/server/db/providers/dynamodb/webhook.go b/server/db/providers/dynamodb/webhook.go index 0883418..9cf7ec7 100644 --- a/server/db/providers/dynamodb/webhook.go +++ b/server/db/providers/dynamodb/webhook.go @@ -22,6 +22,7 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod webhook.Key = webhook.ID webhook.CreatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix() + err := collection.Put(webhook).RunWithContext(ctx) if err != nil { @@ -106,7 +107,7 @@ func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) var webhook models.Webhook collection := p.db.Table(models.Collections.Webhook) - iter := collection.Scan().Filter("'event_name' = ?", eventName).Iter() + iter := collection.Scan().Index("event_name").Filter("'event_name' = ?", eventName).Iter() for iter.NextWithContext(ctx, &webhook) { return webhook.AsAPIWebhook(), nil diff --git a/server/db/providers/dynamodb/webhook_log.go b/server/db/providers/dynamodb/webhook_log.go index e4ca7d5..e9d1dcd 100644 --- a/server/db/providers/dynamodb/webhook_log.go +++ b/server/db/providers/dynamodb/webhook_log.go @@ -44,7 +44,7 @@ func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Paginat scanner := collection.Scan() if webhookID != "" { - iter = scanner.Filter("'webhook_id' = ?", webhookID).Iter() + iter = scanner.Index("webhook_id").Filter("'webhook_id' = ?", webhookID).Iter() for iter.NextWithContext(ctx, &webhookLog) { webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog()) } diff --git a/server/memorystore/required_env_store.go b/server/memorystore/required_env_store.go index a5f3a81..2c0139c 100644 --- a/server/memorystore/required_env_store.go +++ b/server/memorystore/required_env_store.go @@ -16,6 +16,9 @@ import ( // RequiredEnv holds information about required envs type RequiredEnv struct { EnvPath string `json:"ENV_PATH"` + REGION string `json:"REGION"` + AWS_ACCESS_KEY string `json:"AWS_ACCESS_KEY"` + AWS_SECRET_KEY string `json:"AWS_SECRET_KEY"` DatabaseURL string `json:"DATABASE_URL"` DatabaseType string `json:"DATABASE_TYPE"` DatabaseName string `json:"DATABASE_NAME"` @@ -73,6 +76,9 @@ func InitRequiredEnv() error { log.Infof("using OS env instead of %s file", envPath) } + region := os.Getenv(constants.EnvAwsRegion) + awsAccessKey := os.Getenv(constants.EnvAwsAccessKey) + awsSecretKey := os.Getenv(constants.EnvAwsSecretKey) dbURL := os.Getenv(constants.EnvKeyDatabaseURL) dbType := os.Getenv(constants.EnvKeyDatabaseType) dbName := os.Getenv(constants.EnvKeyDatabaseName) @@ -127,6 +133,9 @@ func InitRequiredEnv() error { requiredEnv := RequiredEnv{ EnvPath: envPath, + REGION: region, + AWS_ACCESS_KEY: awsAccessKey, + AWS_SECRET_KEY: awsSecretKey, DatabaseURL: dbURL, DatabaseType: dbType, DatabaseName: dbName, From a6d5d4af24b766783edf6e6f084ffc90a8ec4821 Mon Sep 17 00:00:00 2001 From: manoj Date: Wed, 5 Oct 2022 15:52:17 +0530 Subject: [PATCH 03/11] require env condition needed to be added for dynamodb --- server/memorystore/required_env_store.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/memorystore/required_env_store.go b/server/memorystore/required_env_store.go index 2c0139c..2be8559 100644 --- a/server/memorystore/required_env_store.go +++ b/server/memorystore/required_env_store.go @@ -119,7 +119,8 @@ func InitRequiredEnv() error { dbURL = strings.TrimSpace(*cli.ARG_DB_URL) } - if dbURL == "" && dbPort == "" && dbHost == "" && dbUsername == "" && dbPassword == "" { + // In dynamoDB these field are not always mandatory + if dbType != constants.DbTypeDynamoDB && dbURL == "" && dbPort == "" && dbHost == "" && dbUsername == "" && dbPassword == "" { log.Debug("DATABASE_URL is not set") return errors.New("invalid database url. DATABASE_URL is required") } From 896d8e046df204223a6ca29e5851d025a19c1a26 Mon Sep 17 00:00:00 2001 From: manoj Date: Wed, 5 Oct 2022 16:11:31 +0530 Subject: [PATCH 04/11] remove the white space --- .env.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.test b/.env.test index 89465a8..da3e4c8 100644 --- a/.env.test +++ b/.env.test @@ -7,4 +7,4 @@ SMTP_HOST=smtp.mailtrap.io SMTP_PORT=2525 SMTP_USERNAME=test SMTP_PASSWORD=test -SENDER_EMAIL="info@authorizer.dev" \ No newline at end of file +SENDER_EMAIL="info@authorizer.dev" \ No newline at end of file From e37472d49870cebe111384001331004112413d8d Mon Sep 17 00:00:00 2001 From: manoj Date: Sat, 8 Oct 2022 16:20:16 +0530 Subject: [PATCH 05/11] update the local test url for dynamodb --- server/test/resolvers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/test/resolvers_test.go b/server/test/resolvers_test.go index ce8588f..1fc6060 100644 --- a/server/test/resolvers_test.go +++ b/server/test/resolvers_test.go @@ -20,7 +20,7 @@ func TestResolvers(t *testing.T) { constants.DbTypeArangodb: "http://localhost:8529", constants.DbTypeMongodb: "mongodb://localhost:27017", constants.DbTypeScyllaDB: "127.0.0.1:9042", - constants.DbTypeDynamoDB: "127.0.0.1:8000", + constants.DbTypeDynamoDB: "http://127.0.0.1:8000", } testDBs := strings.Split(os.Getenv("TEST_DBS"), ",") From 820d29413058a6ad444ed1cd742a09d3219ff5fa Mon Sep 17 00:00:00 2001 From: manoj Date: Sun, 9 Oct 2022 00:49:31 +0530 Subject: [PATCH 06/11] comments resolved for requireEnv, Provider and test env --- .env.test | 5 ++--- server/db/providers/dynamodb/provider.go | 21 ++++++++++++++++----- server/env/env.go | 24 ++++++++++++++++++++++++ server/memorystore/required_env_store.go | 9 --------- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/.env.test b/.env.test index da3e4c8..0df0238 100644 --- a/.env.test +++ b/.env.test @@ -1,7 +1,6 @@ ENV=test -DATABASE_URL=http://localhost:8000 -DATABASE_TYPE=dynamodb -REGION=us-east-1 +DATABASE_URL=test.db +DATABASE_TYPE=sqlite CUSTOM_ACCESS_TOKEN_SCRIPT="function(user,tokenPayload){var data = tokenPayload;data.extra = {'x-extra-id': user.id};return data;}" SMTP_HOST=smtp.mailtrap.io SMTP_PORT=2525 diff --git a/server/db/providers/dynamodb/provider.go b/server/db/providers/dynamodb/provider.go index 962a222..7457613 100644 --- a/server/db/providers/dynamodb/provider.go +++ b/server/db/providers/dynamodb/provider.go @@ -1,12 +1,16 @@ package dynamodb import ( + "os" + + "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/memorystore" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/guregu/dynamo" + log "github.com/sirupsen/logrus" ) type provider struct { @@ -15,13 +19,18 @@ type provider struct { // NewProvider returns a new Dynamo provider func NewProvider() (*provider, error) { - region := memorystore.RequiredEnvStoreObj.GetRequiredEnv().REGION dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL - accessKey := memorystore.RequiredEnvStoreObj.GetRequiredEnv().AWS_ACCESS_KEY - secretKey := memorystore.RequiredEnvStoreObj.GetRequiredEnv().AWS_SECRET_KEY + awsRegion := os.Getenv(constants.EnvAwsRegion) + accessKey := os.Getenv(constants.EnvAwsAccessKey) + secretKey := os.Getenv(constants.EnvAwsSecretKey) + config := aws.Config{ - Region: aws.String(region), - MaxRetries: aws.Int(3), + MaxRetries: aws.Int(3), + CredentialsChainVerboseErrors: aws.Bool(true), // for full error logs + } + + if awsRegion != "" { + config.Region = aws.String(awsRegion) } // custom accessKey, secretkey took first priority, if not then fetch config from aws credentials @@ -31,6 +40,8 @@ func NewProvider() (*provider, error) { // static config in case of testing or local-setup config.Credentials = credentials.NewStaticCredentials("key", "key", "") config.Endpoint = aws.String(dbURL) + } else { + log.Info("REGION, AWS_ACCESS_KEY and AWS_SECRET_KEY not found in .env, trying to load default profile from aws credentials") } session := session.Must(session.NewSession(&config)) diff --git a/server/env/env.go b/server/env/env.go index 62b572f..491e4ee 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -77,6 +77,9 @@ func InitAllEnv() error { osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL) osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName) osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo) + osAwsRegion := os.Getenv(constants.EnvAwsRegion) + osAwsAccessKey := os.Getenv(constants.EnvAwsAccessKey) + osAwsSecretKey := os.Getenv(constants.EnvAwsSecretKey) // os bool vars osAppCookieSecure := os.Getenv(constants.EnvKeyAppCookieSecure) @@ -119,6 +122,27 @@ 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 + } + + if val, ok := envData[constants.EnvAwsAccessKey]; !ok || val == "" { + envData[constants.EnvAwsAccessKey] = osAwsAccessKey + } + if osAwsAccessKey != "" && envData[constants.EnvAwsAccessKey] != osAwsRegion { + envData[constants.EnvAwsAccessKey] = osAwsAccessKey + } + + if val, ok := envData[constants.EnvAwsSecretKey]; !ok || val == "" { + envData[constants.EnvAwsSecretKey] = osAwsSecretKey + } + if osAwsSecretKey != "" && envData[constants.EnvAwsSecretKey] != osAwsRegion { + envData[constants.EnvAwsSecretKey] = osAwsSecretKey + } + if val, ok := envData[constants.EnvKeyAppURL]; !ok || val == "" { envData[constants.EnvKeyAppURL] = osAppURL } diff --git a/server/memorystore/required_env_store.go b/server/memorystore/required_env_store.go index 2be8559..13166ac 100644 --- a/server/memorystore/required_env_store.go +++ b/server/memorystore/required_env_store.go @@ -16,9 +16,6 @@ import ( // RequiredEnv holds information about required envs type RequiredEnv struct { EnvPath string `json:"ENV_PATH"` - REGION string `json:"REGION"` - AWS_ACCESS_KEY string `json:"AWS_ACCESS_KEY"` - AWS_SECRET_KEY string `json:"AWS_SECRET_KEY"` DatabaseURL string `json:"DATABASE_URL"` DatabaseType string `json:"DATABASE_TYPE"` DatabaseName string `json:"DATABASE_NAME"` @@ -76,9 +73,6 @@ func InitRequiredEnv() error { log.Infof("using OS env instead of %s file", envPath) } - region := os.Getenv(constants.EnvAwsRegion) - awsAccessKey := os.Getenv(constants.EnvAwsAccessKey) - awsSecretKey := os.Getenv(constants.EnvAwsSecretKey) dbURL := os.Getenv(constants.EnvKeyDatabaseURL) dbType := os.Getenv(constants.EnvKeyDatabaseType) dbName := os.Getenv(constants.EnvKeyDatabaseName) @@ -134,9 +128,6 @@ func InitRequiredEnv() error { requiredEnv := RequiredEnv{ EnvPath: envPath, - REGION: region, - AWS_ACCESS_KEY: awsAccessKey, - AWS_SECRET_KEY: awsSecretKey, DatabaseURL: dbURL, DatabaseType: dbType, DatabaseName: dbName, From 1ac060136aac5836d4eb2a94c8dacb6ff160b6e9 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Thu, 20 Oct 2022 16:27:00 +0530 Subject: [PATCH 07/11] fix: make env vars name more persistent --- server/constants/env.go | 10 +++++----- server/db/providers/dynamodb/provider.go | 24 +++++++++++++++++------- server/env/env.go | 20 ++++++++++---------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/server/constants/env.go b/server/constants/env.go index cc1ddfc..c0af6a3 100644 --- a/server/constants/env.go +++ b/server/constants/env.go @@ -22,11 +22,11 @@ const ( // EnvKeyDatabaseURL key for env variable DATABASE_URL EnvKeyDatabaseURL = "DATABASE_URL" // EnvAwsRegion key for env variable AWS REGION - EnvAwsRegion = "REGION" - // EnvAwsAccessKey key for env variable AWS_ACCESS_KEY - EnvAwsAccessKey = "AWS_ACCESS_KEY" - // EnvAwsAccessKey key for env variable AWS_SECRET_KEY - EnvAwsSecretKey = "AWS_SECRET_KEY" + EnvAwsRegion = "AWS_REGION" + // EnvAwsAccessKeyID key for env variable AWS_ACCESS_KEY_ID + EnvAwsAccessKeyID = "AWS_ACCESS_KEY_ID" + // EnvAwsAccessKey key for env variable AWS_SECRET_ACCESS_KEY + EnvAwsSecretAccessKey = "AWS_SECRET_ACCESS_KEY" // EnvKeyDatabaseName key for env variable DATABASE_NAME EnvKeyDatabaseName = "DATABASE_NAME" // EnvKeyDatabaseUsername key for env variable DATABASE_USERNAME diff --git a/server/db/providers/dynamodb/provider.go b/server/db/providers/dynamodb/provider.go index 7457613..0caa6ea 100644 --- a/server/db/providers/dynamodb/provider.go +++ b/server/db/providers/dynamodb/provider.go @@ -1,16 +1,18 @@ package dynamodb import ( + "fmt" "os" - "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/db/models" - "github.com/authorizerdev/authorizer/server/memorystore" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/guregu/dynamo" log "github.com/sirupsen/logrus" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/memorystore" ) type provider struct { @@ -21,8 +23,8 @@ type provider struct { func NewProvider() (*provider, error) { dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL awsRegion := os.Getenv(constants.EnvAwsRegion) - accessKey := os.Getenv(constants.EnvAwsAccessKey) - secretKey := os.Getenv(constants.EnvAwsSecretKey) + accessKey := os.Getenv(constants.EnvAwsAccessKeyID) + secretKey := os.Getenv(constants.EnvAwsSecretAccessKey) config := aws.Config{ MaxRetries: aws.Int(3), @@ -33,6 +35,16 @@ func NewProvider() (*provider, error) { config.Region = aws.String(awsRegion) } + if accessKey == "" { + log.Debugf("%s not found", constants.EnvAwsAccessKeyID) + return nil, fmt.Errorf("invalid aws credentials. %s not found", constants.EnvAwsAccessKeyID) + } + + if secretKey == "" { + log.Debugf("%s not found", constants.EnvAwsSecretAccessKey) + return nil, fmt.Errorf("invalid aws credentials. %s not found", constants.EnvAwsSecretAccessKey) + } + // custom accessKey, secretkey took first priority, if not then fetch config from aws credentials if accessKey != "" && secretKey != "" { config.Credentials = credentials.NewStaticCredentials(accessKey, secretKey, "") @@ -40,8 +52,6 @@ func NewProvider() (*provider, error) { // static config in case of testing or local-setup config.Credentials = credentials.NewStaticCredentials("key", "key", "") config.Endpoint = aws.String(dbURL) - } else { - log.Info("REGION, AWS_ACCESS_KEY and AWS_SECRET_KEY not found in .env, trying to load default profile from aws credentials") } session := session.Must(session.NewSession(&config)) diff --git a/server/env/env.go b/server/env/env.go index 491e4ee..79777f0 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -78,8 +78,8 @@ func InitAllEnv() error { osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName) osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo) osAwsRegion := os.Getenv(constants.EnvAwsRegion) - osAwsAccessKey := os.Getenv(constants.EnvAwsAccessKey) - osAwsSecretKey := os.Getenv(constants.EnvAwsSecretKey) + osAwsAccessKey := os.Getenv(constants.EnvAwsAccessKeyID) + osAwsSecretKey := os.Getenv(constants.EnvAwsSecretAccessKey) // os bool vars osAppCookieSecure := os.Getenv(constants.EnvKeyAppCookieSecure) @@ -129,18 +129,18 @@ func InitAllEnv() error { envData[constants.EnvAwsRegion] = osAwsRegion } - if val, ok := envData[constants.EnvAwsAccessKey]; !ok || val == "" { - envData[constants.EnvAwsAccessKey] = osAwsAccessKey + if val, ok := envData[constants.EnvAwsAccessKeyID]; !ok || val == "" { + envData[constants.EnvAwsAccessKeyID] = osAwsAccessKey } - if osAwsAccessKey != "" && envData[constants.EnvAwsAccessKey] != osAwsRegion { - envData[constants.EnvAwsAccessKey] = osAwsAccessKey + if osAwsAccessKey != "" && envData[constants.EnvAwsAccessKeyID] != osAwsRegion { + envData[constants.EnvAwsAccessKeyID] = osAwsAccessKey } - if val, ok := envData[constants.EnvAwsSecretKey]; !ok || val == "" { - envData[constants.EnvAwsSecretKey] = osAwsSecretKey + if val, ok := envData[constants.EnvAwsSecretAccessKey]; !ok || val == "" { + envData[constants.EnvAwsSecretAccessKey] = osAwsSecretKey } - if osAwsSecretKey != "" && envData[constants.EnvAwsSecretKey] != osAwsRegion { - envData[constants.EnvAwsSecretKey] = osAwsSecretKey + if osAwsSecretKey != "" && envData[constants.EnvAwsSecretAccessKey] != osAwsRegion { + envData[constants.EnvAwsSecretAccessKey] = osAwsSecretKey } if val, ok := envData[constants.EnvKeyAppURL]; !ok || val == "" { From 094782aeca22e5424c714e91fb532b88c94e4c56 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 21 Oct 2022 11:19:32 +0530 Subject: [PATCH 08/11] fix(server): linting issues --- server/handlers/app.go | 10 +++++----- server/main.go | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/server/handlers/app.go b/server/handlers/app.go index 5b34fb6..6c5d541 100644 --- a/server/handlers/app.go +++ b/server/handlers/app.go @@ -30,7 +30,7 @@ func AppHandler() gin.HandlerFunc { return } - redirect_uri := strings.TrimSpace(c.Query("redirect_uri")) + redirectURI := strings.TrimSpace(c.Query("redirect_uri")) state := strings.TrimSpace(c.Query("state")) scopeString := strings.TrimSpace(c.Query("scope")) @@ -41,11 +41,11 @@ func AppHandler() gin.HandlerFunc { scope = strings.Split(scopeString, " ") } - if redirect_uri == "" { - redirect_uri = hostname + "/app" + if redirectURI == "" { + redirectURI = hostname + "/app" } else { // validate redirect url with allowed origins - if !validators.IsValidOrigin(redirect_uri) { + if !validators.IsValidOrigin(redirectURI) { log.Debug("Invalid redirect_uri") c.JSON(400, gin.H{"error": "invalid redirect url"}) return @@ -75,7 +75,7 @@ func AppHandler() gin.HandlerFunc { c.HTML(http.StatusOK, "app.tmpl", gin.H{ "data": map[string]interface{}{ "authorizerURL": hostname, - "redirectURL": redirect_uri, + "redirectURL": redirectURI, "scope": scope, "state": state, "organizationName": orgName, diff --git a/server/main.go b/server/main.go index f0b544a..fa397c8 100644 --- a/server/main.go +++ b/server/main.go @@ -15,12 +15,15 @@ import ( "github.com/authorizerdev/authorizer/server/routes" ) +// VERSION is used to define the version of authorizer from build tags var VERSION string +// LogUTCFormatter hels in setting UTC time format for the logs type LogUTCFormatter struct { log.Formatter } +// Format helps fomratting time to UTC func (u LogUTCFormatter) Format(e *log.Entry) ([]byte, error) { e.Time = e.Time.UTC() return u.Formatter.Format(e) From 476bdf00fc942b08338f311db6b4ca821afad3b6 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 21 Oct 2022 11:21:21 +0530 Subject: [PATCH 09/11] fix(server): open_id config --- server/handlers/openid_config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/handlers/openid_config.go b/server/handlers/openid_config.go index 781caf1..33ad090 100644 --- a/server/handlers/openid_config.go +++ b/server/handlers/openid_config.go @@ -17,14 +17,14 @@ func OpenIDConfigurationHandler() gin.HandlerFunc { c.JSON(200, gin.H{ "issuer": issuer, "authorization_endpoint": issuer + "/authorize", - "token_endpoint": issuer + "/token", + "token_endpoint": issuer + "/oauth/token", "userinfo_endpoint": issuer + "/userinfo", "jwks_uri": issuer + "/.well-known/jwks.json", - "response_types_supported": []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token"}, + "response_types_supported": []string{"code", "token", "id_token"}, "scopes_supported": []string{"openid", "email", "profile", "email_verified", "given_name", "family_name", "nick_name", "picture"}, - "response_modes_supported": []string{"query", "fragment", "form_post"}, + "response_modes_supported": []string{"query", "fragment", "form_post", "web_message"}, "id_token_signing_alg_values_supported": []string{jwtType}, - "claims_supported": []string{"aud", "exp", "iss", "iat", "sub", "given_name", "family_name", "middle_name", "nickname", "preferred_username", "picture", "email", "email_verified", "roles", "gender", "birthdate", "phone_number", "phone_number_verified"}, + "claims_supported": []string{"aud", "exp", "iss", "iat", "sub", "given_name", "family_name", "middle_name", "nickname", "preferred_username", "picture", "email", "email_verified", "roles", "role", "gender", "birthdate", "phone_number", "phone_number_verified", "nonce", "updated_at", "created_at", "revoked_timestamp", "login_method", "signup_methods", "token_type"}, }) } } From 8449821d1bf0f1e40fe1678bf4d4ce563ef3aab9 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 21 Oct 2022 15:55:54 +0530 Subject: [PATCH 10/11] fix(server): dynamodb tests + provider config --- .env.test | 3 ++- server/db/providers/dynamodb/provider.go | 28 ++++++++---------------- server/db/providers/dynamodb/user.go | 2 +- server/memorystore/memory_store.go | 2 +- server/memorystore/required_env_store.go | 17 +++++++++++--- server/test/resolvers_test.go | 14 ++++++++++-- server/test/test.go | 2 +- 7 files changed, 40 insertions(+), 28 deletions(-) diff --git a/.env.test b/.env.test index 0df0238..7db07e8 100644 --- a/.env.test +++ b/.env.test @@ -6,4 +6,5 @@ SMTP_HOST=smtp.mailtrap.io SMTP_PORT=2525 SMTP_USERNAME=test SMTP_PASSWORD=test -SENDER_EMAIL="info@authorizer.dev" \ No newline at end of file +SENDER_EMAIL="info@authorizer.dev" +AWS_REGION=ap-south-1 \ No newline at end of file diff --git a/server/db/providers/dynamodb/provider.go b/server/db/providers/dynamodb/provider.go index 0caa6ea..81aa8ce 100644 --- a/server/db/providers/dynamodb/provider.go +++ b/server/db/providers/dynamodb/provider.go @@ -1,9 +1,6 @@ package dynamodb import ( - "fmt" - "os" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" @@ -22,36 +19,29 @@ type provider struct { // NewProvider returns a new Dynamo provider func NewProvider() (*provider, error) { dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL - awsRegion := os.Getenv(constants.EnvAwsRegion) - accessKey := os.Getenv(constants.EnvAwsAccessKeyID) - secretKey := os.Getenv(constants.EnvAwsSecretAccessKey) + awsRegion := memorystore.RequiredEnvStoreObj.GetRequiredEnv().AwsRegion + awsAccessKeyID := memorystore.RequiredEnvStoreObj.GetRequiredEnv().AwsAccessKeyID + awsSecretAccessKey := memorystore.RequiredEnvStoreObj.GetRequiredEnv().AwsSecretAccessKey config := aws.Config{ MaxRetries: aws.Int(3), CredentialsChainVerboseErrors: aws.Bool(true), // for full error logs + } if awsRegion != "" { config.Region = aws.String(awsRegion) } - if accessKey == "" { - log.Debugf("%s not found", constants.EnvAwsAccessKeyID) - return nil, fmt.Errorf("invalid aws credentials. %s not found", constants.EnvAwsAccessKeyID) - } - - if secretKey == "" { - log.Debugf("%s not found", constants.EnvAwsSecretAccessKey) - return nil, fmt.Errorf("invalid aws credentials. %s not found", constants.EnvAwsSecretAccessKey) - } - - // custom accessKey, secretkey took first priority, if not then fetch config from aws credentials - if accessKey != "" && secretKey != "" { - config.Credentials = credentials.NewStaticCredentials(accessKey, secretKey, "") + // 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 != "" { // static config in case of testing or local-setup config.Credentials = credentials.NewStaticCredentials("key", "key", "") config.Endpoint = aws.String(dbURL) + } else { + log.Debugf("%s or %s or %s not found. Trying to load default credentials from aws config", constants.EnvAwsRegion, constants.EnvAwsAccessKeyID, constants.EnvAwsSecretAccessKey) } session := session.Must(session.NewSession(&config)) diff --git a/server/db/providers/dynamodb/user.go b/server/db/providers/dynamodb/user.go index ef780b1..3bd45db 100644 --- a/server/db/providers/dynamodb/user.go +++ b/server/db/providers/dynamodb/user.go @@ -180,7 +180,7 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{}, for _, user := range allUsers { err = UpdateByHashKey(userCollection, "id", user.ID, data) - if err != nil { + if err == nil { res = res + 1 } } diff --git a/server/memorystore/memory_store.go b/server/memorystore/memory_store.go index c112c01..15b7248 100644 --- a/server/memorystore/memory_store.go +++ b/server/memorystore/memory_store.go @@ -57,7 +57,7 @@ func InitMemStore() error { } redisURL := requiredEnvs.RedisURL - if redisURL != "" && !requiredEnvs.disableRedisForEnv { + if redisURL != "" && !requiredEnvs.DisableRedisForEnv { log.Info("Initializing Redis memory store") Provider, err = redis.NewRedisProvider(redisURL) if err != nil { diff --git a/server/memorystore/required_env_store.go b/server/memorystore/required_env_store.go index 13166ac..9eafe24 100644 --- a/server/memorystore/required_env_store.go +++ b/server/memorystore/required_env_store.go @@ -27,7 +27,11 @@ type RequiredEnv struct { DatabaseCertKey string `json:"DATABASE_CERT_KEY"` DatabaseCACert string `json:"DATABASE_CA_CERT"` RedisURL string `json:"REDIS_URL"` - disableRedisForEnv bool `json:"DISABLE_REDIS_FOR_ENV"` + DisableRedisForEnv bool `json:"DISABLE_REDIS_FOR_ENV"` + // AWS Related Envs + AwsRegion string `json:"AWS_REGION"` + AwsAccessKeyID string `json:"AWS_ACCESS_KEY_ID"` + AwsSecretAccessKey string `json:"AWS_SECRET_ACCESS_KEY"` } // RequiredEnvObj is a simple in-memory store for sessions. @@ -53,7 +57,8 @@ func (r *RequiredEnvStore) SetRequiredEnv(requiredEnv RequiredEnv) { var RequiredEnvStoreObj *RequiredEnvStore -// InitRequiredEnv to initialize EnvData and through error if required env are not present +// InitRequiredEnv to initialize EnvData and throw error if required env are not present +// This includes env that only configurable via env vars and not the ui func InitRequiredEnv() error { envPath := os.Getenv(constants.EnvKeyEnvPath) @@ -85,6 +90,9 @@ func InitRequiredEnv() error { dbCACert := os.Getenv(constants.EnvKeyDatabaseCACert) redisURL := os.Getenv(constants.EnvKeyRedisURL) disableRedisForEnv := os.Getenv(constants.EnvKeyDisableRedisForEnv) == "true" + awsRegion := os.Getenv(constants.EnvAwsRegion) + awsAccessKeyID := os.Getenv(constants.EnvAwsAccessKeyID) + awsSecretAccessKey := os.Getenv(constants.EnvAwsSecretAccessKey) if strings.TrimSpace(redisURL) == "" { if cli.ARG_REDIS_URL != nil && *cli.ARG_REDIS_URL != "" { @@ -139,7 +147,10 @@ func InitRequiredEnv() error { DatabaseCertKey: dbCertKey, DatabaseCACert: dbCACert, RedisURL: redisURL, - disableRedisForEnv: disableRedisForEnv, + DisableRedisForEnv: disableRedisForEnv, + AwsRegion: awsRegion, + AwsAccessKeyID: awsAccessKeyID, + AwsSecretAccessKey: awsSecretAccessKey, } RequiredEnvStoreObj = &RequiredEnvStore{ diff --git a/server/test/resolvers_test.go b/server/test/resolvers_test.go index 1fc6060..0ff918b 100644 --- a/server/test/resolvers_test.go +++ b/server/test/resolvers_test.go @@ -2,6 +2,7 @@ package test import ( "context" + "fmt" "os" "strings" "testing" @@ -20,7 +21,7 @@ func TestResolvers(t *testing.T) { constants.DbTypeArangodb: "http://localhost:8529", constants.DbTypeMongodb: "mongodb://localhost:27017", constants.DbTypeScyllaDB: "127.0.0.1:9042", - constants.DbTypeDynamoDB: "http://127.0.0.1:8000", + constants.DbTypeDynamoDB: "http://0.0.0.0:8000", } testDBs := strings.Split(os.Getenv("TEST_DBS"), ",") @@ -52,6 +53,12 @@ func TestResolvers(t *testing.T) { os.Setenv(constants.EnvKeyDatabaseURL, dbURL) os.Setenv(constants.EnvKeyDatabaseType, dbType) os.Setenv(constants.EnvKeyDatabaseName, testDb) + + if dbType == constants.DbTypeDynamoDB { + memorystore.Provider.UpdateEnvVariable(constants.EnvAwsRegion, "ap-south-1") + os.Setenv(constants.EnvAwsRegion, "ap-south-1") + } + memorystore.InitRequiredEnv() err := db.InitDB() @@ -61,12 +68,15 @@ func TestResolvers(t *testing.T) { // clean the persisted config for test to use fresh config envData, err := db.Provider.GetEnv(ctx) - if err == nil { + fmt.Println("envData", envData.ID, envData.EnvData) + if err == nil && envData.ID != "" { envData.EnvData = "" _, err = db.Provider.UpdateEnv(ctx, envData) if err != nil { t.Errorf("Error updating env: %s", err.Error()) } + } else if err != nil { + t.Errorf("Error getting env: %s", err.Error()) } err = env.PersistEnv() if err != nil { diff --git a/server/test/test.go b/server/test/test.go index 1e5105b..1fb6492 100644 --- a/server/test/test.go +++ b/server/test/test.go @@ -84,7 +84,7 @@ func testSetup() TestSetup { testData := TestData{ Email: fmt.Sprintf("%d_authorizer_tester@yopmail.com", time.Now().Unix()), Password: "Test@123", - WebhookEndpoint: "https://62cbc6738042b16aa7c22df2.mockapi.io/api/v1/webhook", + WebhookEndpoint: "https://62f93101e05644803533cf36.mockapi.io/authorizer/webhook", TestWebhookEventTypes: []string{constants.UserAccessEnabledWebhookEvent, constants.UserAccessRevokedWebhookEvent, constants.UserCreatedWebhookEvent, constants.UserDeletedWebhookEvent, constants.UserLoginWebhookEvent, constants.UserSignUpWebhookEvent}, TestEmailTemplateEventTypes: []string{constants.VerificationTypeBasicAuthSignup, constants.VerificationTypeForgotPassword, constants.VerificationTypeMagicLinkLogin, constants.VerificationTypeUpdateEmail}, } From 7fc69dcc5523347d6ff3e2b82a2bd386e07c5816 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 21 Oct 2022 21:58:56 +0530 Subject: [PATCH 11/11] fix(server): sql server not allow multiple null multiple null values for unique constrained column is not allowed on sqlserver Resolves #278 --- server/db/providers/sql/provider.go | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/server/db/providers/sql/provider.go b/server/db/providers/sql/provider.go index 712f3d1..cfe83eb 100644 --- a/server/db/providers/sql/provider.go +++ b/server/db/providers/sql/provider.go @@ -1,6 +1,7 @@ package sql import ( + "fmt" "log" "os" "time" @@ -21,6 +22,16 @@ type provider struct { db *gorm.DB } +const ( + phoneNumberIndexName = "UQ_phone_number" + phoneNumberColumnName = "phone_number" +) + +type indexInfo struct { + IndexName string `json:"index_name"` + ColumnName string `json:"column_name"` +} + // NewProvider returns a new SQL provider func NewProvider() (*provider, error) { var sqlDB *gorm.DB @@ -65,6 +76,32 @@ func NewProvider() (*provider, error) { if err != nil { return nil, err } + + // unique constraint on phone number does not work with multiple null values for sqlserver + // for more information check https://stackoverflow.com/a/767702 + if dbType == constants.DbTypeSqlserver { + var indexInfos []indexInfo + // remove index on phone number if present with different name + res := sqlDB.Raw("SELECT i.name AS index_name, i.type_desc AS index_algorithm, CASE i.is_unique WHEN 1 THEN 'TRUE' ELSE 'FALSE' END AS is_unique, ac.Name AS column_name FROM sys.tables AS t INNER JOIN sys.indexes AS i ON t.object_id = i.object_id INNER JOIN sys.index_columns AS ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id INNER JOIN sys.all_columns AS ac ON ic.object_id = ac.object_id AND ic.column_id = ac.column_id WHERE t.name = 'authorizer_users' AND SCHEMA_NAME(t.schema_id) = 'dbo';").Scan(&indexInfos) + if res.Error != nil { + return nil, res.Error + } + + for _, val := range indexInfos { + if val.ColumnName == phoneNumberColumnName && val.IndexName != phoneNumberIndexName { + // drop index & create new + if res := sqlDB.Exec(fmt.Sprintf(`ALTER TABLE authorizer_users DROP CONSTRAINT "%s";`, val.IndexName)); res.Error != nil { + return nil, res.Error + } + + // create index + if res := sqlDB.Exec(fmt.Sprintf("CREATE UNIQUE NONCLUSTERED INDEX %s ON authorizer_users(phone_number) WHERE phone_number IS NOT NULL;", phoneNumberIndexName)); res.Error != nil { + return nil, res.Error + } + } + } + } + return &provider{ db: sqlDB, }, nil