diff --git a/server/constants/oauthInfoUrls.go b/server/constants/oauthInfoUrls.go index 34756ff..220fb95 100644 --- a/server/constants/oauthInfoUrls.go +++ b/server/constants/oauthInfoUrls.go @@ -2,6 +2,7 @@ package constants var ( // Ref: https://github.com/qor/auth/blob/master/providers/google/google.go + // deprecated and not used. instead we follow open id approach for google login GoogleUserInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo" // Ref: https://github.com/qor/auth/blob/master/providers/facebook/facebook.go#L18 FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token=" diff --git a/server/db/arangodb.go b/server/db/arangodb.go index 213d3ed..80305c7 100644 --- a/server/db/arangodb.go +++ b/server/db/arangodb.go @@ -10,7 +10,11 @@ import ( "github.com/authorizerdev/authorizer/server/constants" ) -func initArangodb() (*arangoDriver.Database, error) { +// for this we need arangodb instance up and running +// for local testing we can use dockerized version of it +// docker run -p 8529:8529 -e ARANGO_ROOT_PASSWORD=root arangodb/arangodb:3.8.4 + +func initArangodb() (arangoDriver.Database, error) { ctx := context.Background() conn, err := http.NewConnection(http.ConnectionConfig{ Endpoints: []string{constants.DATABASE_URL}, @@ -19,8 +23,6 @@ func initArangodb() (*arangoDriver.Database, error) { return nil, err } - // TODO add support for authentication option in clientConfig or check if - // basic auth pattern works here in DB_URL client, err := arangoDriver.NewClient(arangoDriver.ClientConfig{ Connection: conn, }) @@ -29,11 +31,10 @@ func initArangodb() (*arangoDriver.Database, error) { } var arangodb driver.Database - var arangodb_exists bool // TODO use dynamic name based on env dbName := "authorizer" - arangodb_exists, err = client.DatabaseExists(nil, dbName) + arangodb_exists, err := client.DatabaseExists(nil, dbName) if arangodb_exists { log.Println(dbName + " db exists already") @@ -61,9 +62,18 @@ func initArangodb() (*arangoDriver.Database, error) { log.Println("error creating collection("+Collections.User+"):", err) } } + userCollection, _ := arangodb.Collection(nil, Collections.User) + userCollection.EnsureHashIndex(ctx, []string{"id"}, &arangoDriver.EnsureHashIndexOptions{ + Unique: true, + Sparse: true, + }) + userCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{ + Unique: true, + Sparse: true, + }) - verificationRequestsColumnExists, err := arangodb.CollectionExists(ctx, Collections.VerificationRequest) - if verificationRequestsColumnExists { + verificationRequestCollectionExists, err := arangodb.CollectionExists(ctx, Collections.VerificationRequest) + if verificationRequestCollectionExists { log.Println(Collections.VerificationRequest + " collection exists already") } else { _, err = arangodb.CreateCollection(ctx, Collections.VerificationRequest, nil) @@ -71,19 +81,22 @@ func initArangodb() (*arangoDriver.Database, error) { log.Println("error creating collection("+Collections.VerificationRequest+"):", err) } } + verificationRequestCollection, _ := arangodb.Collection(nil, Collections.VerificationRequest) + verificationRequestCollection.EnsureHashIndex(ctx, []string{"id"}, &arangoDriver.EnsureHashIndexOptions{ + Unique: true, + Sparse: true, + }) + verificationRequestCollection.EnsureHashIndex(ctx, []string{"email", "identifier"}, &arangoDriver.EnsureHashIndexOptions{ + Unique: true, + Sparse: true, + }) + verificationRequestCollection.EnsureHashIndex(ctx, []string{"token"}, &arangoDriver.EnsureHashIndexOptions{ + Unique: true, + Sparse: true, + }) - rolesExists, err := arangodb.CollectionExists(ctx, Collections.Role) - if rolesExists { - log.Println(Collections.Role + " collection exists already") - } else { - _, err = arangodb.CreateCollection(ctx, Collections.Role, nil) - if err != nil { - log.Println("error creating collection("+Collections.Role+"):", err) - } - } - - sessionExists, err := arangodb.CollectionExists(ctx, Collections.Session) - if sessionExists { + sessionCollectionExists, err := arangodb.CollectionExists(ctx, Collections.Session) + if sessionCollectionExists { log.Println(Collections.Session + " collection exists already") } else { _, err = arangodb.CreateCollection(ctx, Collections.Session, nil) @@ -92,5 +105,11 @@ func initArangodb() (*arangoDriver.Database, error) { } } - return &arangodb, err + sessionCollection, _ := arangodb.Collection(nil, Collections.Session) + sessionCollection.EnsureHashIndex(ctx, []string{"id"}, &arangoDriver.EnsureHashIndexOptions{ + Unique: true, + Sparse: true, + }) + + return arangodb, err } diff --git a/server/db/db.go b/server/db/db.go index 2d3d706..acdf688 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -6,7 +6,6 @@ import ( arangoDriver "github.com/arangodb/go-driver" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/enum" - "github.com/google/uuid" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" @@ -15,54 +14,51 @@ import ( ) type Manager interface { - SaveUser(user User) (User, error) + AddUser(user User) (User, error) UpdateUser(user User) (User, error) + DeleteUser(user User) error GetUsers() ([]User, error) GetUserByEmail(email string) (User, error) GetUserByID(email string) (User, error) - UpdateVerificationTime(verifiedAt int64, id uuid.UUID) error AddVerification(verification VerificationRequest) (VerificationRequest, error) GetVerificationByToken(token string) (VerificationRequest, error) - DeleteToken(email string) error + DeleteVerificationRequest(verificationRequest VerificationRequest) error GetVerificationRequests() ([]VerificationRequest, error) GetVerificationByEmail(email string) (VerificationRequest, error) - DeleteUser(email string) error - SaveRoles(roles []Role) error - SaveSession(session Session) error + AddSession(session Session) error } type manager struct { sqlDB *gorm.DB - arangodb *arangoDriver.Database + arangodb arangoDriver.Database } // mainly used by nosql dbs type CollectionList struct { User string VerificationRequest string - Role string Session string } var ( + IsSQL bool + IsArangoDB bool Mgr Manager Prefix = "authorizer_" Collections = CollectionList{ User: Prefix + "users", VerificationRequest: Prefix + "verification_requests", - Role: Prefix + "roles", Session: Prefix + "sessions", } ) -func isSQL() bool { - return constants.DATABASE_TYPE != enum.Arangodb.String() -} - func InitDB() { var sqlDB *gorm.DB var err error + IsSQL = constants.DATABASE_TYPE != enum.Arangodb.String() + IsArangoDB = constants.DATABASE_TYPE == enum.Arangodb.String() + // sql db orm config ormConfig := &gorm.Config{ NamingStrategy: schema.NamingStrategy{ @@ -70,6 +66,8 @@ func InitDB() { }, } + log.Println("db type:", constants.DATABASE_TYPE) + switch constants.DATABASE_TYPE { case enum.Postgres.String(): sqlDB, err = gorm.Open(postgres.Open(constants.DATABASE_URL), ormConfig) @@ -85,22 +83,21 @@ func InitDB() { if err != nil { log.Fatal("error initing arangodb:", err) } + Mgr = &manager{ sqlDB: nil, arangodb: arangodb, } - // check if collections exists - break } // common for all sql dbs that are configured via gorm - if isSQL() { + if IsSQL { if err != nil { log.Fatal("Failed to init sqlDB:", err) } else { - sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Role{}, &Session{}) + sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{}) } Mgr = &manager{ sqlDB: sqlDB, diff --git a/server/db/roles.go b/server/db/roles.go deleted file mode 100644 index 6fc8dbd..0000000 --- a/server/db/roles.go +++ /dev/null @@ -1,34 +0,0 @@ -package db - -import ( - "log" - - "github.com/google/uuid" - "gorm.io/gorm" - "gorm.io/gorm/clause" -) - -type Role struct { - ID uuid.UUID `gorm:"primaryKey;type:char(36)"` - Role string `gorm:"unique"` -} - -func (r *Role) BeforeCreate(tx *gorm.DB) (err error) { - r.ID = uuid.New() - - return -} - -// SaveRoles function to save roles -func (mgr *manager) SaveRoles(roles []Role) error { - res := mgr.sqlDB.Clauses( - clause.OnConflict{ - DoNothing: true, - }).Create(&roles) - if res.Error != nil { - log.Println(`Error saving roles`) - return res.Error - } - - return nil -} diff --git a/server/db/session.go b/server/db/session.go index fd72e39..cb83406 100644 --- a/server/db/session.go +++ b/server/db/session.go @@ -2,37 +2,61 @@ package db import ( "log" + "time" "github.com/google/uuid" - "gorm.io/gorm" "gorm.io/gorm/clause" ) type Session struct { - ID uuid.UUID `gorm:"primaryKey;type:char(36)"` - UserID uuid.UUID `gorm:"type:char(36)"` - User User - UserAgent string - IP string - CreatedAt int64 `gorm:"autoCreateTime"` - UpdatedAt int64 `gorm:"autoUpdateTime"` + Key string `json:"_key,omitempty"` // for arangodb + ObjectID string `json:"_id,omitempty"` // for arangodb & mongodb + ID string `gorm:"primaryKey;type:char(36)" json:"id"` + UserID string `gorm:"type:char(36)" json:"user_id"` + User User `json:"-"` + UserAgent string `json:"user_agent"` + IP string `json:"ip"` + CreatedAt int64 `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at"` } -func (r *Session) BeforeCreate(tx *gorm.DB) (err error) { - r.ID = uuid.New() +// AddSession function to save user sessiosn +func (mgr *manager) AddSession(session Session) error { + if session.ID == "" { + session.ID = uuid.New().String() + } - return -} + if session.CreatedAt == 0 { + session.CreatedAt = time.Now().Unix() + } -// SaveSession function to save user sessiosn -func (mgr *manager) SaveSession(session Session) error { - res := mgr.sqlDB.Clauses( - clause.OnConflict{ - DoNothing: true, - }).Create(&session) - if res.Error != nil { - log.Println(`Error saving session`, res.Error) - return res.Error + if session.UpdatedAt == 0 { + session.CreatedAt = time.Now().Unix() + } + + if IsSQL { + // copy id as value for fields required for mongodb & arangodb + session.Key = session.ID + session.ObjectID = session.ID + res := mgr.sqlDB.Clauses( + clause.OnConflict{ + DoNothing: true, + }).Create(&session) + if res.Error != nil { + log.Println(`Error saving session`, res.Error) + return res.Error + } + } + + if IsArangoDB { + + session.CreatedAt = time.Now().Unix() + session.UpdatedAt = time.Now().Unix() + sessionCollection, _ := mgr.arangodb.Collection(nil, Collections.Session) + _, err := sessionCollection.CreateDocument(nil, session) + if err != nil { + return err + } } return nil diff --git a/server/db/user.go b/server/db/user.go index 28140cd..b7b960d 100644 --- a/server/db/user.go +++ b/server/db/user.go @@ -1,45 +1,68 @@ package db import ( + "context" + "errors" + "fmt" "log" "time" + "github.com/arangodb/go-driver" + arangoDriver "github.com/arangodb/go-driver" "github.com/google/uuid" - "gorm.io/gorm" "gorm.io/gorm/clause" ) type User struct { - ID uuid.UUID `gorm:"primaryKey;type:char(36)"` - FirstName string - LastName string - Email string `gorm:"unique"` - Password string `gorm:"type:text"` - SignupMethod string - EmailVerifiedAt int64 - CreatedAt int64 `gorm:"autoCreateTime"` - UpdatedAt int64 `gorm:"autoUpdateTime"` - Image string `gorm:"type:text"` - Roles string + Key string `json:"_key,omitempty"` // for arangodb + ObjectID string `json:"_id,omitempty"` // for arangodb & mongodb + ID string `gorm:"primaryKey;type:char(36)" json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email string `gorm:"unique" json:"email"` + Password string `gorm:"type:text" json:"password"` + SignupMethod string `json:"signup_method"` + EmailVerifiedAt int64 `json:"email_verified_at"` + CreatedAt int64 `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at"` + Image string `gorm:"type:text" json:"image"` + Roles string `json:"roles"` } -func (u *User) BeforeCreate(tx *gorm.DB) (err error) { - u.ID = uuid.New() +// AddUser function to add user even with email conflict +func (mgr *manager) AddUser(user User) (User, error) { + if user.ID == "" { + user.ID = uuid.New().String() + } - return -} + if IsSQL { + // copy id as value for fields required for mongodb & arangodb + user.Key = user.ID + user.ObjectID = user.ID + result := mgr.sqlDB.Clauses( + clause.OnConflict{ + UpdateAll: true, + Columns: []clause.Column{{Name: "email"}}, + }).Create(&user) -// SaveUser function to add user even with email conflict -func (mgr *manager) SaveUser(user User) (User, error) { - result := mgr.sqlDB.Clauses( - clause.OnConflict{ - UpdateAll: true, - Columns: []clause.Column{{Name: "email"}}, - }).Create(&user) + if result.Error != nil { + log.Println("error adding user:", result.Error) + return user, result.Error + } + } - if result.Error != nil { - log.Println(result.Error) - return user, result.Error + if IsArangoDB { + user.CreatedAt = time.Now().Unix() + user.UpdatedAt = time.Now().Unix() + ctx := context.Background() + userCollection, _ := mgr.arangodb.Collection(nil, Collections.User) + meta, err := userCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), user) + if err != nil { + log.Println("error adding user:", err) + return user, err + } + user.Key = meta.Key + user.ObjectID = meta.ID.String() } return user, nil } @@ -47,15 +70,26 @@ func (mgr *manager) SaveUser(user User) (User, error) { // UpdateUser function to update user with ID conflict func (mgr *manager) UpdateUser(user User) (User, error) { user.UpdatedAt = time.Now().Unix() - result := mgr.sqlDB.Clauses( - clause.OnConflict{ - UpdateAll: true, - Columns: []clause.Column{{Name: "email"}}, - }).Create(&user) - if result.Error != nil { - log.Println(result.Error) - return user, result.Error + if IsSQL { + result := mgr.sqlDB.Save(&user) + + if result.Error != nil { + log.Println("error updating user:", result.Error) + return user, result.Error + } + } + + if IsArangoDB { + collection, _ := mgr.arangodb.Collection(nil, Collections.User) + meta, err := collection.UpdateDocument(nil, user.Key, user) + if err != nil { + log.Println("error updating user:", err) + return user, err + } + + user.Key = meta.Key + user.ObjectID = meta.ID.String() } return user, nil } @@ -63,20 +97,79 @@ func (mgr *manager) UpdateUser(user User) (User, error) { // GetUsers function to get all users func (mgr *manager) GetUsers() ([]User, error) { var users []User - result := mgr.sqlDB.Find(&users) - if result.Error != nil { - log.Println(result.Error) - return users, result.Error + + if IsSQL { + result := mgr.sqlDB.Find(&users) + if result.Error != nil { + log.Println("error getting users:", result.Error) + return users, result.Error + } + } + + if IsArangoDB { + query := fmt.Sprintf("FOR d in %s RETURN d", Collections.User) + + cursor, err := mgr.arangodb.Query(nil, query, nil) + if err != nil { + return users, err + } + defer cursor.Close() + + for { + var user User + meta, err := cursor.ReadDocument(nil, &user) + + if driver.IsNoMoreDocuments(err) { + break + } else if err != nil { + return users, err + } + + if meta.Key != "" { + user.Key = meta.Key + user.ObjectID = meta.ID.String() + users = append(users, user) + } + + } } return users, nil } func (mgr *manager) GetUserByEmail(email string) (User, error) { var user User - result := mgr.sqlDB.Where("email = ?", email).First(&user) - if result.Error != nil { - return user, result.Error + if IsSQL { + result := mgr.sqlDB.Where("email = ?", email).First(&user) + + if result.Error != nil { + return user, result.Error + } + } + + if IsArangoDB { + query := fmt.Sprintf("FOR d in %s FILTER d.email == @email LIMIT 1 RETURN d", Collections.User) + bindVars := map[string]interface{}{ + "email": email, + } + + cursor, err := mgr.arangodb.Query(nil, query, bindVars) + if err != nil { + return user, err + } + defer cursor.Close() + + log.Println("=> query:", query, bindVars) + log.Println("=> cursor:", cursor.Count()) + + for { + _, err := cursor.ReadDocument(nil, &user) + if driver.IsNoMoreDocuments(err) { + break + } else if err != nil { + return user, err + } + } } return user, nil @@ -84,35 +177,62 @@ func (mgr *manager) GetUserByEmail(email string) (User, error) { func (mgr *manager) GetUserByID(id string) (User, error) { var user User - result := mgr.sqlDB.Where("id = ?", id).First(&user) - if result.Error != nil { - return user, result.Error + if IsSQL { + result := mgr.sqlDB.Where("id = ?", id).First(&user) + + if result.Error != nil { + return user, result.Error + } + } + + if IsArangoDB { + query := fmt.Sprintf("FOR d in %s FILTER d.id == @id LIMIT 1 RETURN d", Collections.User) + bindVars := map[string]interface{}{ + "id": id, + } + + cursor, err := mgr.arangodb.Query(nil, query, bindVars) + if err != nil { + return user, err + } + defer cursor.Close() + + count := cursor.Count() + if count == 0 { + return user, errors.New("user not found") + } + + for { + _, err := cursor.ReadDocument(nil, &user) + if driver.IsNoMoreDocuments(err) { + break + } else if err != nil { + return user, err + } + } } return user, nil } -func (mgr *manager) UpdateVerificationTime(verifiedAt int64, id uuid.UUID) error { - user := &User{ - ID: id, - } - result := mgr.sqlDB.Model(&user).Where("id = ?", id).Update("email_verified_at", verifiedAt) +func (mgr *manager) DeleteUser(user User) error { + if IsSQL { + result := mgr.sqlDB.Delete(&user) - if result.Error != nil { - return result.Error - } - - return nil -} - -func (mgr *manager) DeleteUser(email string) error { - var user User - result := mgr.sqlDB.Where("email = ?", email).Delete(&user) - - if result.Error != nil { - log.Println(`Error deleting user:`, result.Error) - return result.Error + if result.Error != nil { + log.Println(`error deleting user:`, result.Error) + return result.Error + } + } + + if IsArangoDB { + collection, _ := mgr.arangodb.Collection(nil, Collections.User) + _, err := collection.RemoveDocument(nil, user.Key) + if err != nil { + log.Println(`error deleting user:`, err) + return err + } } return nil diff --git a/server/db/verificationRequests.go b/server/db/verificationRequests.go index 07f684a..e9c54c5 100644 --- a/server/db/verificationRequests.go +++ b/server/db/verificationRequests.go @@ -1,50 +1,135 @@ package db import ( + "fmt" "log" + "github.com/arangodb/go-driver" "github.com/google/uuid" - "gorm.io/gorm" "gorm.io/gorm/clause" ) type VerificationRequest struct { - ID uuid.UUID `gorm:"primaryKey;type:char(36)"` - Token string `gorm:"type:text"` - Identifier string - ExpiresAt int64 - CreatedAt int64 `gorm:"autoCreateTime"` - UpdatedAt int64 `gorm:"autoUpdateTime"` - Email string `gorm:"unique"` -} - -func (v *VerificationRequest) BeforeCreate(tx *gorm.DB) (err error) { - v.ID = uuid.New() - - return + Key string `json:"_key,omitempty"` // for arangodb + ObjectID string `json:"_id,omitempty"` // for arangodb & mongodb + ID string `gorm:"primaryKey;type:char(36)" json:"id"` + Token string `gorm:"type:text" json:"token"` + Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier"` + ExpiresAt int64 `json:"expires_at"` + CreatedAt int64 `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at"` + Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email"` } // AddVerification function to add verification record func (mgr *manager) AddVerification(verification VerificationRequest) (VerificationRequest, error) { - result := mgr.sqlDB.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "email"}}, - DoUpdates: clause.AssignmentColumns([]string{"token", "identifier", "expires_at"}), - }).Create(&verification) + if verification.ID == "" { + verification.ID = uuid.New().String() + } + if IsSQL { + // copy id as value for fields required for mongodb & arangodb + verification.Key = verification.ID + verification.ObjectID = verification.ID + result := mgr.sqlDB.Clauses(clause.OnConflict{ + DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at"}), + }).Create(&verification) - if result.Error != nil { - log.Println(`Error saving verification record`, result.Error) - return verification, result.Error + if result.Error != nil { + log.Println(`Error saving verification record`, result.Error) + return verification, result.Error + } + } + + if IsArangoDB { + verificationRequestCollection, _ := mgr.arangodb.Collection(nil, Collections.VerificationRequest) + meta, err := verificationRequestCollection.CreateDocument(nil, verification) + if err != nil { + log.Println("=> meta information for verification request:", meta.Key) + return verification, err + } + verification.Key = meta.Key + verification.ObjectID = meta.ID.String() } return verification, nil } +// GetVerificationRequests function to get all verification requests +func (mgr *manager) GetVerificationRequests() ([]VerificationRequest, error) { + var verificationRequests []VerificationRequest + + if IsSQL { + result := mgr.sqlDB.Find(&verificationRequests) + if result.Error != nil { + log.Println(result.Error) + return verificationRequests, result.Error + } + } + + if IsArangoDB { + query := fmt.Sprintf("FOR d in %s RETURN d", Collections.VerificationRequest) + + cursor, err := mgr.arangodb.Query(nil, query, nil) + if err != nil { + return verificationRequests, err + } + defer cursor.Close() + + for { + var verificationRequest VerificationRequest + meta, err := cursor.ReadDocument(nil, &verificationRequest) + + if driver.IsNoMoreDocuments(err) { + break + } else if err != nil { + return verificationRequests, err + } + + if meta.Key != "" { + verificationRequest.Key = meta.Key + verificationRequest.ObjectID = meta.ID.String() + verificationRequests = append(verificationRequests, verificationRequest) + } + + } + } + return verificationRequests, nil +} + func (mgr *manager) GetVerificationByToken(token string) (VerificationRequest, error) { var verification VerificationRequest - result := mgr.sqlDB.Where("token = ?", token).First(&verification) - if result.Error != nil { - log.Println(`Error getting verification token:`, result.Error) - return verification, result.Error + if IsSQL { + result := mgr.sqlDB.Where("token = ?", token).First(&verification) + + if result.Error != nil { + log.Println(`Error getting verification token:`, result.Error) + return verification, result.Error + } + } + + if IsArangoDB { + query := fmt.Sprintf("FOR d in %s FILTER d.token == @token LIMIT 1 RETURN d", Collections.VerificationRequest) + bindVars := map[string]interface{}{ + "token": token, + } + + cursor, err := mgr.arangodb.Query(nil, query, bindVars) + if err != nil { + return verification, err + } + defer cursor.Close() + + for { + meta, err := cursor.ReadDocument(nil, &verification) + + log.Println("=> arangodb verification by token:", verification, meta) + + if driver.IsNoMoreDocuments(err) { + break + } else if err != nil { + return verification, err + } + } } return verification, nil @@ -52,35 +137,63 @@ func (mgr *manager) GetVerificationByToken(token string) (VerificationRequest, e func (mgr *manager) GetVerificationByEmail(email string) (VerificationRequest, error) { var verification VerificationRequest - result := mgr.sqlDB.Where("email = ?", email).First(&verification) + if IsSQL { + result := mgr.sqlDB.Where("email = ?", email).First(&verification) - if result.Error != nil { - log.Println(`Error getting verification token:`, result.Error) - return verification, result.Error + if result.Error != nil { + log.Println(`Error getting verification token:`, result.Error) + return verification, result.Error + } + } + + if IsArangoDB { + query := fmt.Sprintf("FOR d in %s FILTER d.email == @email LIMIT 1 RETURN d", Collections.VerificationRequest) + bindVars := map[string]interface{}{ + "email": email, + } + + cursor, err := mgr.arangodb.Query(nil, query, bindVars) + if err != nil { + return verification, err + } + defer cursor.Close() + + for { + meta, err := cursor.ReadDocument(nil, &verification) + + log.Println("=> arangodb verification by token:", verification, meta) + + if driver.IsNoMoreDocuments(err) { + break + } else if err != nil { + return verification, err + } + + } } return verification, nil } -func (mgr *manager) DeleteToken(email string) error { - var verification VerificationRequest - result := mgr.sqlDB.Where("email = ?", email).Delete(&verification) +func (mgr *manager) DeleteVerificationRequest(verificationRequest VerificationRequest) error { + if IsSQL { + result := mgr.sqlDB.Delete(&verificationRequest) - if result.Error != nil { - log.Println(`Error deleting token:`, result.Error) - return result.Error + if result.Error != nil { + log.Println(`error deleting verification request:`, result.Error) + return result.Error + } + } + + if IsArangoDB { + log.Println("vkey:", verificationRequest, verificationRequest.Key) + collection, _ := mgr.arangodb.Collection(nil, Collections.VerificationRequest) + _, err := collection.RemoveDocument(nil, verificationRequest.Key) + if err != nil { + log.Println(`error deleting verification request:`, err) + return err + } } return nil } - -// GetUsers function to get all users -func (mgr *manager) GetVerificationRequests() ([]VerificationRequest, error) { - var verificationRequests []VerificationRequest - result := mgr.sqlDB.Find(&verificationRequests) - if result.Error != nil { - log.Println(result.Error) - return verificationRequests, result.Error - } - return verificationRequests, nil -} diff --git a/server/env.go b/server/env.go index f650713..f126e95 100644 --- a/server/env.go +++ b/server/env.go @@ -137,7 +137,7 @@ func InitEnv() { constants.DISABLE_EMAIL_VERIFICATION = "false" } - log.Println("=> disable email verification:", constants.DISABLE_EMAIL_VERIFICATION) + log.Println("email verification disabled:", constants.DISABLE_EMAIL_VERIFICATION) rolesSplit := strings.Split(os.Getenv("ROLES"), ",") roles := []string{} diff --git a/server/handlers/oauthCallback.go b/server/handlers/oauthCallback.go index c8f7a7a..5e79b30 100644 --- a/server/handlers/oauthCallback.go +++ b/server/handlers/oauthCallback.go @@ -129,7 +129,7 @@ func processFacebookUserInfo(code string) (db.User, error) { response, err := client.Do(req) if err != nil { - log.Println("err:", err) + log.Println("err processing facebook user info:", err) return user, err } @@ -217,10 +217,12 @@ func OAuthCallbackHandler() gin.HandlerFunc { } user.Roles = strings.Join(inputRoles, ",") + user, _ = db.Mgr.AddUser(user) } else { // user exists in db, check if method was google // if not append google to existing signup method and save it + log.Println("existing useR:", existingUser) signupMethod := existingUser.SignupMethod if !strings.Contains(signupMethod, provider) { signupMethod = signupMethod + "," + provider @@ -260,9 +262,12 @@ func OAuthCallbackHandler() gin.HandlerFunc { } else { user.Roles = existingUser.Roles } + user.Key = existingUser.Key + user.ObjectID = existingUser.ObjectID + user.ID = existingUser.ID + user, err = db.Mgr.UpdateUser(user) } - user, _ = db.Mgr.SaveUser(user) user, _ = db.Mgr.GetUserByEmail(user.Email) userIdStr := fmt.Sprintf("%v", user.ID) refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, inputRoles) @@ -277,7 +282,7 @@ func OAuthCallbackHandler() gin.HandlerFunc { IP: utils.GetIP(c.Request), } - db.Mgr.SaveSession(sessionData) + db.Mgr.AddSession(sessionData) }() c.Redirect(http.StatusTemporaryRedirect, redirectURL) diff --git a/server/handlers/verifyEmail.go b/server/handlers/verifyEmail.go index fbb080d..fecdbe3 100644 --- a/server/handlers/verifyEmail.go +++ b/server/handlers/verifyEmail.go @@ -24,7 +24,7 @@ func VerifyEmailHandler() gin.HandlerFunc { return } - _, err := db.Mgr.GetVerificationByToken(token) + verificationRequest, err := db.Mgr.GetVerificationByToken(token) if err != nil { c.JSON(400, errorRes) return @@ -47,10 +47,11 @@ func VerifyEmailHandler() gin.HandlerFunc { // update email_verified_at in users table if user.EmailVerifiedAt <= 0 { - db.Mgr.UpdateVerificationTime(time.Now().Unix(), user.ID) + user.EmailVerifiedAt = time.Now().Unix() + db.Mgr.UpdateUser(user) } // delete from verification table - db.Mgr.DeleteToken(claim.Email) + db.Mgr.DeleteVerificationRequest(verificationRequest) userIdStr := fmt.Sprintf("%v", user.ID) roles := strings.Split(user.Roles, ",") @@ -66,7 +67,7 @@ func VerifyEmailHandler() gin.HandlerFunc { IP: utils.GetIP(c.Request), } - db.Mgr.SaveSession(sessionData) + db.Mgr.AddSession(sessionData) }() utils.SetCookie(c, accessToken) c.Redirect(http.StatusTemporaryRedirect, claim.RedirectURL) diff --git a/server/resolvers/deleteUser.go b/server/resolvers/deleteUser.go index 31bb6cd..8f7f6e4 100644 --- a/server/resolvers/deleteUser.go +++ b/server/resolvers/deleteUser.go @@ -29,7 +29,7 @@ func DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Respo session.DeleteUserSession(fmt.Sprintf("%x", user.ID)) - err = db.Mgr.DeleteUser(params.Email) + err = db.Mgr.DeleteUser(user) if err != nil { log.Println("Err:", err) return res, err diff --git a/server/resolvers/login.go b/server/resolvers/login.go index 5fb4f39..ed53655 100644 --- a/server/resolvers/login.go +++ b/server/resolvers/login.go @@ -68,7 +68,7 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e IP: utils.GetIP(gc.Request), } - db.Mgr.SaveSession(sessionData) + db.Mgr.AddSession(sessionData) }() res = &model.AuthResponse{ diff --git a/server/resolvers/logout.go b/server/resolvers/logout.go index 2a1633a..5e79300 100644 --- a/server/resolvers/logout.go +++ b/server/resolvers/logout.go @@ -27,7 +27,7 @@ func Logout(ctx context.Context) (*model.Response, error) { } userId := fmt.Sprintf("%v", claim["id"]) - session.DeleteToken(userId, token) + session.DeleteVerificationRequest(userId, token) res = &model.Response{ Message: "Logged out successfully", } diff --git a/server/resolvers/magicLogin.go b/server/resolvers/magicLogin.go index 0cd1b52..a287573 100644 --- a/server/resolvers/magicLogin.go +++ b/server/resolvers/magicLogin.go @@ -35,6 +35,7 @@ func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Respo // find user with email existingUser, err := db.Mgr.GetUserByEmail(params.Email) + if err != nil { user.SignupMethod = enum.MagicLink.String() // define roles for new user @@ -50,6 +51,7 @@ func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Respo } user.Roles = strings.Join(inputRoles, ",") + user, _ = db.Mgr.AddUser(user) } else { user = existingUser // There multiple scenarios with roles here in magic link login @@ -90,10 +92,12 @@ func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Respo } user.SignupMethod = signupMethod + user, _ = db.Mgr.UpdateUser(user) + if err != nil { + log.Println("=> error updating user:", err) + } } - user, _ = db.Mgr.SaveUser(user) - if constants.DISABLE_EMAIL_VERIFICATION != "true" { // insert verification request verificationType := enum.MagicLink.String() diff --git a/server/resolvers/resetPassword.go b/server/resolvers/resetPassword.go index 17f3ea5..c4cb08c 100644 --- a/server/resolvers/resetPassword.go +++ b/server/resolvers/resetPassword.go @@ -18,7 +18,7 @@ func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model return res, fmt.Errorf(`basic authentication is disabled for this instance`) } - _, err := db.Mgr.GetVerificationByToken(params.Token) + verificationRequest, err := db.Mgr.GetVerificationByToken(params.Token) if err != nil { return res, fmt.Errorf(`invalid token`) } @@ -48,7 +48,7 @@ func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model user.SignupMethod = signupMethod // delete from verification table - db.Mgr.DeleteToken(claim.Email) + db.Mgr.DeleteVerificationRequest(verificationRequest) db.Mgr.UpdateUser(user) res = &model.Response{ diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go index 6ee7393..5bad25f 100644 --- a/server/resolvers/signup.go +++ b/server/resolvers/signup.go @@ -79,7 +79,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, if constants.DISABLE_EMAIL_VERIFICATION == "true" { user.EmailVerifiedAt = time.Now().Unix() } - user, err = db.Mgr.SaveUser(user) + user, err = db.Mgr.AddUser(user) if err != nil { return res, err } @@ -135,7 +135,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, IP: utils.GetIP(gc.Request), } - db.Mgr.SaveSession(sessionData) + db.Mgr.AddSession(sessionData) }() res = &model.AuthResponse{ Message: `Signed up successfully.`, diff --git a/server/resolvers/token.go b/server/resolvers/token.go index 6087a1e..39b9367 100644 --- a/server/resolvers/token.go +++ b/server/resolvers/token.go @@ -64,7 +64,7 @@ func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) { // if access token has expired and refresh/session token is valid // generate new accessToken currentRefreshToken := session.GetToken(userIdStr, token) - session.DeleteToken(userIdStr, token) + session.DeleteVerificationRequest(userIdStr, token) token, expiresAt, _ = utils.CreateAuthToken(user, enum.AccessToken, claimRoles) session.SetToken(userIdStr, token, currentRefreshToken) go func() { @@ -74,7 +74,7 @@ func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) { IP: utils.GetIP(gc.Request), } - db.Mgr.SaveSession(sessionData) + db.Mgr.AddSession(sessionData) }() } diff --git a/server/resolvers/verifyEmail.go b/server/resolvers/verifyEmail.go index a1f90e3..dab520b 100644 --- a/server/resolvers/verifyEmail.go +++ b/server/resolvers/verifyEmail.go @@ -3,6 +3,7 @@ package resolvers import ( "context" "fmt" + "log" "strings" "time" @@ -20,7 +21,8 @@ func VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.Aut return res, err } - _, err = db.Mgr.GetVerificationByToken(params.Token) + verificationRequest, err := db.Mgr.GetVerificationByToken(params.Token) + log.Println("=> vf req:", verificationRequest) if err != nil { return res, fmt.Errorf(`invalid token`) } @@ -37,9 +39,10 @@ func VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.Aut } // update email_verified_at in users table - db.Mgr.UpdateVerificationTime(time.Now().Unix(), user.ID) + user.EmailVerifiedAt = time.Now().Unix() + db.Mgr.UpdateUser(user) // delete from verification table - db.Mgr.DeleteToken(claim.Email) + db.Mgr.DeleteVerificationRequest(verificationRequest) userIdStr := fmt.Sprintf("%v", user.ID) roles := strings.Split(user.Roles, ",") @@ -55,7 +58,7 @@ func VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.Aut IP: utils.GetIP(gc.Request), } - db.Mgr.SaveSession(sessionData) + db.Mgr.AddSession(sessionData) }() res = &model.AuthResponse{ diff --git a/server/session/inMemoryStore.go b/server/session/inMemoryStore.go index 164b7d0..10adecd 100644 --- a/server/session/inMemoryStore.go +++ b/server/session/inMemoryStore.go @@ -41,7 +41,7 @@ func (c *InMemoryStore) DeleteUserSession(userId string) { c.mu.Unlock() } -func (c *InMemoryStore) DeleteToken(userId, accessToken string) { +func (c *InMemoryStore) DeleteVerificationRequest(userId, accessToken string) { c.mu.Lock() delete(c.store[userId], accessToken) c.mu.Unlock() diff --git a/server/session/redisStore.go b/server/session/redisStore.go index 91cb5ed..c7b99f3 100644 --- a/server/session/redisStore.go +++ b/server/session/redisStore.go @@ -29,7 +29,7 @@ func (c *RedisStore) DeleteUserSession(userId string) { } } -func (c *RedisStore) DeleteToken(userId, accessToken string) { +func (c *RedisStore) DeleteVerificationRequest(userId, accessToken string) { err := c.store.HDel(c.ctx, "authorizer_"+userId, accessToken).Err() if err != nil { log.Fatalln("Error deleting redis token:", err) diff --git a/server/session/session.go b/server/session/session.go index 15d4015..37f9cbf 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -27,12 +27,12 @@ func SetToken(userId, accessToken, refreshToken string) { } } -func DeleteToken(userId, accessToken string) { +func DeleteVerificationRequest(userId, accessToken string) { if SessionStoreObj.RedisMemoryStoreObj != nil { - SessionStoreObj.RedisMemoryStoreObj.DeleteToken(userId, accessToken) + SessionStoreObj.RedisMemoryStoreObj.DeleteVerificationRequest(userId, accessToken) } if SessionStoreObj.InMemoryStoreObj != nil { - SessionStoreObj.InMemoryStoreObj.DeleteToken(userId, accessToken) + SessionStoreObj.InMemoryStoreObj.DeleteVerificationRequest(userId, accessToken) } } diff --git a/server/utils/initServer.go b/server/utils/initServer.go index 4634c4a..9315314 100644 --- a/server/utils/initServer.go +++ b/server/utils/initServer.go @@ -1,30 +1,6 @@ package utils -import ( - "log" - - "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/db" -) - // any jobs that we want to run at start of server can be executed here -// 1. create roles table and add the roles list from env to table - func InitServer() { - roles := []db.Role{} - for _, val := range constants.ROLES { - roles = append(roles, db.Role{ - Role: val, - }) - } - for _, val := range constants.PROTECTED_ROLES { - roles = append(roles, db.Role{ - Role: val, - }) - } - err := db.Mgr.SaveRoles(roles) - if err != nil { - log.Println(`Error saving roles`, err) - } }