fix: dao for sql + arangodb

This commit is contained in:
Lakhan Samani
2021-12-17 21:02:02 +05:30
parent 57dd69279e
commit 537e08667a
22 changed files with 483 additions and 254 deletions

View File

@@ -2,6 +2,7 @@ package constants
var ( var (
// Ref: https://github.com/qor/auth/blob/master/providers/google/google.go // 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" GoogleUserInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo"
// Ref: https://github.com/qor/auth/blob/master/providers/facebook/facebook.go#L18 // 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=" FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token="

View File

@@ -10,7 +10,11 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "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() ctx := context.Background()
conn, err := http.NewConnection(http.ConnectionConfig{ conn, err := http.NewConnection(http.ConnectionConfig{
Endpoints: []string{constants.DATABASE_URL}, Endpoints: []string{constants.DATABASE_URL},
@@ -19,8 +23,6 @@ func initArangodb() (*arangoDriver.Database, error) {
return nil, err 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{ client, err := arangoDriver.NewClient(arangoDriver.ClientConfig{
Connection: conn, Connection: conn,
}) })
@@ -29,11 +31,10 @@ func initArangodb() (*arangoDriver.Database, error) {
} }
var arangodb driver.Database var arangodb driver.Database
var arangodb_exists bool
// TODO use dynamic name based on env // TODO use dynamic name based on env
dbName := "authorizer" dbName := "authorizer"
arangodb_exists, err = client.DatabaseExists(nil, dbName) arangodb_exists, err := client.DatabaseExists(nil, dbName)
if arangodb_exists { if arangodb_exists {
log.Println(dbName + " db exists already") log.Println(dbName + " db exists already")
@@ -61,9 +62,18 @@ func initArangodb() (*arangoDriver.Database, error) {
log.Println("error creating collection("+Collections.User+"):", err) 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) verificationRequestCollectionExists, err := arangodb.CollectionExists(ctx, Collections.VerificationRequest)
if verificationRequestsColumnExists { if verificationRequestCollectionExists {
log.Println(Collections.VerificationRequest + " collection exists already") log.Println(Collections.VerificationRequest + " collection exists already")
} else { } else {
_, err = arangodb.CreateCollection(ctx, Collections.VerificationRequest, nil) _, err = arangodb.CreateCollection(ctx, Collections.VerificationRequest, nil)
@@ -71,19 +81,22 @@ func initArangodb() (*arangoDriver.Database, error) {
log.Println("error creating collection("+Collections.VerificationRequest+"):", err) 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) sessionCollectionExists, err := arangodb.CollectionExists(ctx, Collections.Session)
if rolesExists { if sessionCollectionExists {
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 {
log.Println(Collections.Session + " collection exists already") log.Println(Collections.Session + " collection exists already")
} else { } else {
_, err = arangodb.CreateCollection(ctx, Collections.Session, nil) _, 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
} }

View File

@@ -6,7 +6,6 @@ import (
arangoDriver "github.com/arangodb/go-driver" arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/enum"
"github.com/google/uuid"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
@@ -15,54 +14,51 @@ import (
) )
type Manager interface { type Manager interface {
SaveUser(user User) (User, error) AddUser(user User) (User, error)
UpdateUser(user User) (User, error) UpdateUser(user User) (User, error)
DeleteUser(user User) error
GetUsers() ([]User, error) GetUsers() ([]User, error)
GetUserByEmail(email string) (User, error) GetUserByEmail(email string) (User, error)
GetUserByID(email string) (User, error) GetUserByID(email string) (User, error)
UpdateVerificationTime(verifiedAt int64, id uuid.UUID) error
AddVerification(verification VerificationRequest) (VerificationRequest, error) AddVerification(verification VerificationRequest) (VerificationRequest, error)
GetVerificationByToken(token string) (VerificationRequest, error) GetVerificationByToken(token string) (VerificationRequest, error)
DeleteToken(email string) error DeleteVerificationRequest(verificationRequest VerificationRequest) error
GetVerificationRequests() ([]VerificationRequest, error) GetVerificationRequests() ([]VerificationRequest, error)
GetVerificationByEmail(email string) (VerificationRequest, error) GetVerificationByEmail(email string) (VerificationRequest, error)
DeleteUser(email string) error AddSession(session Session) error
SaveRoles(roles []Role) error
SaveSession(session Session) error
} }
type manager struct { type manager struct {
sqlDB *gorm.DB sqlDB *gorm.DB
arangodb *arangoDriver.Database arangodb arangoDriver.Database
} }
// mainly used by nosql dbs // mainly used by nosql dbs
type CollectionList struct { type CollectionList struct {
User string User string
VerificationRequest string VerificationRequest string
Role string
Session string Session string
} }
var ( var (
IsSQL bool
IsArangoDB bool
Mgr Manager Mgr Manager
Prefix = "authorizer_" Prefix = "authorizer_"
Collections = CollectionList{ Collections = CollectionList{
User: Prefix + "users", User: Prefix + "users",
VerificationRequest: Prefix + "verification_requests", VerificationRequest: Prefix + "verification_requests",
Role: Prefix + "roles",
Session: Prefix + "sessions", Session: Prefix + "sessions",
} }
) )
func isSQL() bool {
return constants.DATABASE_TYPE != enum.Arangodb.String()
}
func InitDB() { func InitDB() {
var sqlDB *gorm.DB var sqlDB *gorm.DB
var err error var err error
IsSQL = constants.DATABASE_TYPE != enum.Arangodb.String()
IsArangoDB = constants.DATABASE_TYPE == enum.Arangodb.String()
// sql db orm config // sql db orm config
ormConfig := &gorm.Config{ ormConfig := &gorm.Config{
NamingStrategy: schema.NamingStrategy{ NamingStrategy: schema.NamingStrategy{
@@ -70,6 +66,8 @@ func InitDB() {
}, },
} }
log.Println("db type:", constants.DATABASE_TYPE)
switch constants.DATABASE_TYPE { switch constants.DATABASE_TYPE {
case enum.Postgres.String(): case enum.Postgres.String():
sqlDB, err = gorm.Open(postgres.Open(constants.DATABASE_URL), ormConfig) sqlDB, err = gorm.Open(postgres.Open(constants.DATABASE_URL), ormConfig)
@@ -85,22 +83,21 @@ func InitDB() {
if err != nil { if err != nil {
log.Fatal("error initing arangodb:", err) log.Fatal("error initing arangodb:", err)
} }
Mgr = &manager{ Mgr = &manager{
sqlDB: nil, sqlDB: nil,
arangodb: arangodb, arangodb: arangodb,
} }
// check if collections exists
break break
} }
// common for all sql dbs that are configured via gorm // common for all sql dbs that are configured via gorm
if isSQL() { if IsSQL {
if err != nil { if err != nil {
log.Fatal("Failed to init sqlDB:", err) log.Fatal("Failed to init sqlDB:", err)
} else { } else {
sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Role{}, &Session{}) sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{})
} }
Mgr = &manager{ Mgr = &manager{
sqlDB: sqlDB, sqlDB: sqlDB,

View File

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

View File

@@ -2,37 +2,61 @@ package db
import ( import (
"log" "log"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
type Session struct { type Session struct {
ID uuid.UUID `gorm:"primaryKey;type:char(36)"` Key string `json:"_key,omitempty"` // for arangodb
UserID uuid.UUID `gorm:"type:char(36)"` ObjectID string `json:"_id,omitempty"` // for arangodb & mongodb
User User ID string `gorm:"primaryKey;type:char(36)" json:"id"`
UserAgent string UserID string `gorm:"type:char(36)" json:"user_id"`
IP string User User `json:"-"`
CreatedAt int64 `gorm:"autoCreateTime"` UserAgent string `json:"user_agent"`
UpdatedAt int64 `gorm:"autoUpdateTime"` 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) { // AddSession function to save user sessiosn
r.ID = uuid.New() 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 if session.UpdatedAt == 0 {
func (mgr *manager) SaveSession(session Session) error { session.CreatedAt = time.Now().Unix()
res := mgr.sqlDB.Clauses( }
clause.OnConflict{
DoNothing: true, if IsSQL {
}).Create(&session) // copy id as value for fields required for mongodb & arangodb
if res.Error != nil { session.Key = session.ID
log.Println(`Error saving session`, res.Error) session.ObjectID = session.ID
return res.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 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 return nil

View File

@@ -1,45 +1,68 @@
package db package db
import ( import (
"context"
"errors"
"fmt"
"log" "log"
"time" "time"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
type User struct { type User struct {
ID uuid.UUID `gorm:"primaryKey;type:char(36)"` Key string `json:"_key,omitempty"` // for arangodb
FirstName string ObjectID string `json:"_id,omitempty"` // for arangodb & mongodb
LastName string ID string `gorm:"primaryKey;type:char(36)" json:"id"`
Email string `gorm:"unique"` FirstName string `json:"first_name"`
Password string `gorm:"type:text"` LastName string `json:"last_name"`
SignupMethod string Email string `gorm:"unique" json:"email"`
EmailVerifiedAt int64 Password string `gorm:"type:text" json:"password"`
CreatedAt int64 `gorm:"autoCreateTime"` SignupMethod string `json:"signup_method"`
UpdatedAt int64 `gorm:"autoUpdateTime"` EmailVerifiedAt int64 `json:"email_verified_at"`
Image string `gorm:"type:text"` CreatedAt int64 `gorm:"autoCreateTime" json:"created_at"`
Roles string 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) { // AddUser function to add user even with email conflict
u.ID = uuid.New() 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 if result.Error != nil {
func (mgr *manager) SaveUser(user User) (User, error) { log.Println("error adding user:", result.Error)
result := mgr.sqlDB.Clauses( return user, result.Error
clause.OnConflict{ }
UpdateAll: true, }
Columns: []clause.Column{{Name: "email"}},
}).Create(&user)
if result.Error != nil { if IsArangoDB {
log.Println(result.Error) user.CreatedAt = time.Now().Unix()
return user, result.Error 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 return user, nil
} }
@@ -47,15 +70,26 @@ func (mgr *manager) SaveUser(user User) (User, error) {
// UpdateUser function to update user with ID conflict // UpdateUser function to update user with ID conflict
func (mgr *manager) UpdateUser(user User) (User, error) { func (mgr *manager) UpdateUser(user User) (User, error) {
user.UpdatedAt = time.Now().Unix() user.UpdatedAt = time.Now().Unix()
result := mgr.sqlDB.Clauses(
clause.OnConflict{
UpdateAll: true,
Columns: []clause.Column{{Name: "email"}},
}).Create(&user)
if result.Error != nil { if IsSQL {
log.Println(result.Error) result := mgr.sqlDB.Save(&user)
return user, result.Error
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 return user, nil
} }
@@ -63,20 +97,79 @@ func (mgr *manager) UpdateUser(user User) (User, error) {
// GetUsers function to get all users // GetUsers function to get all users
func (mgr *manager) GetUsers() ([]User, error) { func (mgr *manager) GetUsers() ([]User, error) {
var users []User var users []User
result := mgr.sqlDB.Find(&users)
if result.Error != nil { if IsSQL {
log.Println(result.Error) result := mgr.sqlDB.Find(&users)
return users, result.Error 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 return users, nil
} }
func (mgr *manager) GetUserByEmail(email string) (User, error) { func (mgr *manager) GetUserByEmail(email string) (User, error) {
var user User var user User
result := mgr.sqlDB.Where("email = ?", email).First(&user)
if result.Error != nil { if IsSQL {
return user, result.Error 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 return user, nil
@@ -84,35 +177,62 @@ func (mgr *manager) GetUserByEmail(email string) (User, error) {
func (mgr *manager) GetUserByID(id string) (User, error) { func (mgr *manager) GetUserByID(id string) (User, error) {
var user User var user User
result := mgr.sqlDB.Where("id = ?", id).First(&user)
if result.Error != nil { if IsSQL {
return user, result.Error 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 return user, nil
} }
func (mgr *manager) UpdateVerificationTime(verifiedAt int64, id uuid.UUID) error { func (mgr *manager) DeleteUser(user User) error {
user := &User{ if IsSQL {
ID: id, result := mgr.sqlDB.Delete(&user)
}
result := mgr.sqlDB.Model(&user).Where("id = ?", id).Update("email_verified_at", verifiedAt)
if result.Error != nil { if result.Error != nil {
return result.Error log.Println(`error deleting user:`, result.Error)
} return result.Error
}
return nil }
}
if IsArangoDB {
func (mgr *manager) DeleteUser(email string) error { collection, _ := mgr.arangodb.Collection(nil, Collections.User)
var user User _, err := collection.RemoveDocument(nil, user.Key)
result := mgr.sqlDB.Where("email = ?", email).Delete(&user) if err != nil {
log.Println(`error deleting user:`, err)
if result.Error != nil { return err
log.Println(`Error deleting user:`, result.Error) }
return result.Error
} }
return nil return nil

View File

@@ -1,50 +1,135 @@
package db package db
import ( import (
"fmt"
"log" "log"
"github.com/arangodb/go-driver"
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
type VerificationRequest struct { type VerificationRequest struct {
ID uuid.UUID `gorm:"primaryKey;type:char(36)"` Key string `json:"_key,omitempty"` // for arangodb
Token string `gorm:"type:text"` ObjectID string `json:"_id,omitempty"` // for arangodb & mongodb
Identifier string ID string `gorm:"primaryKey;type:char(36)" json:"id"`
ExpiresAt int64 Token string `gorm:"type:text" json:"token"`
CreatedAt int64 `gorm:"autoCreateTime"` Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier"`
UpdatedAt int64 `gorm:"autoUpdateTime"` ExpiresAt int64 `json:"expires_at"`
Email string `gorm:"unique"` CreatedAt int64 `gorm:"autoCreateTime" json:"created_at"`
} UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at"`
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email"`
func (v *VerificationRequest) BeforeCreate(tx *gorm.DB) (err error) {
v.ID = uuid.New()
return
} }
// AddVerification function to add verification record // AddVerification function to add verification record
func (mgr *manager) AddVerification(verification VerificationRequest) (VerificationRequest, error) { func (mgr *manager) AddVerification(verification VerificationRequest) (VerificationRequest, error) {
result := mgr.sqlDB.Clauses(clause.OnConflict{ if verification.ID == "" {
Columns: []clause.Column{{Name: "email"}}, verification.ID = uuid.New().String()
DoUpdates: clause.AssignmentColumns([]string{"token", "identifier", "expires_at"}), }
}).Create(&verification) 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 { if result.Error != nil {
log.Println(`Error saving verification record`, result.Error) log.Println(`Error saving verification record`, result.Error)
return verification, 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 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) { func (mgr *manager) GetVerificationByToken(token string) (VerificationRequest, error) {
var verification VerificationRequest var verification VerificationRequest
result := mgr.sqlDB.Where("token = ?", token).First(&verification)
if result.Error != nil { if IsSQL {
log.Println(`Error getting verification token:`, result.Error) result := mgr.sqlDB.Where("token = ?", token).First(&verification)
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.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 return verification, nil
@@ -52,35 +137,63 @@ func (mgr *manager) GetVerificationByToken(token string) (VerificationRequest, e
func (mgr *manager) GetVerificationByEmail(email string) (VerificationRequest, error) { func (mgr *manager) GetVerificationByEmail(email string) (VerificationRequest, error) {
var verification VerificationRequest 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 { if result.Error != nil {
log.Println(`Error getting verification token:`, result.Error) log.Println(`Error getting verification token:`, result.Error)
return verification, 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 return verification, nil
} }
func (mgr *manager) DeleteToken(email string) error { func (mgr *manager) DeleteVerificationRequest(verificationRequest VerificationRequest) error {
var verification VerificationRequest if IsSQL {
result := mgr.sqlDB.Where("email = ?", email).Delete(&verification) result := mgr.sqlDB.Delete(&verificationRequest)
if result.Error != nil { if result.Error != nil {
log.Println(`Error deleting token:`, result.Error) log.Println(`error deleting verification request:`, result.Error)
return 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 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
}

View File

@@ -137,7 +137,7 @@ func InitEnv() {
constants.DISABLE_EMAIL_VERIFICATION = "false" 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"), ",") rolesSplit := strings.Split(os.Getenv("ROLES"), ",")
roles := []string{} roles := []string{}

View File

@@ -129,7 +129,7 @@ func processFacebookUserInfo(code string) (db.User, error) {
response, err := client.Do(req) response, err := client.Do(req)
if err != nil { if err != nil {
log.Println("err:", err) log.Println("err processing facebook user info:", err)
return user, err return user, err
} }
@@ -217,10 +217,12 @@ func OAuthCallbackHandler() gin.HandlerFunc {
} }
user.Roles = strings.Join(inputRoles, ",") user.Roles = strings.Join(inputRoles, ",")
user, _ = db.Mgr.AddUser(user)
} else { } else {
// user exists in db, check if method was google // user exists in db, check if method was google
// if not append google to existing signup method and save it // if not append google to existing signup method and save it
log.Println("existing useR:", existingUser)
signupMethod := existingUser.SignupMethod signupMethod := existingUser.SignupMethod
if !strings.Contains(signupMethod, provider) { if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + provider signupMethod = signupMethod + "," + provider
@@ -260,9 +262,12 @@ func OAuthCallbackHandler() gin.HandlerFunc {
} else { } else {
user.Roles = existingUser.Roles 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) user, _ = db.Mgr.GetUserByEmail(user.Email)
userIdStr := fmt.Sprintf("%v", user.ID) userIdStr := fmt.Sprintf("%v", user.ID)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, inputRoles) refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, inputRoles)
@@ -277,7 +282,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
IP: utils.GetIP(c.Request), IP: utils.GetIP(c.Request),
} }
db.Mgr.SaveSession(sessionData) db.Mgr.AddSession(sessionData)
}() }()
c.Redirect(http.StatusTemporaryRedirect, redirectURL) c.Redirect(http.StatusTemporaryRedirect, redirectURL)

View File

@@ -24,7 +24,7 @@ func VerifyEmailHandler() gin.HandlerFunc {
return return
} }
_, err := db.Mgr.GetVerificationByToken(token) verificationRequest, err := db.Mgr.GetVerificationByToken(token)
if err != nil { if err != nil {
c.JSON(400, errorRes) c.JSON(400, errorRes)
return return
@@ -47,10 +47,11 @@ func VerifyEmailHandler() gin.HandlerFunc {
// update email_verified_at in users table // update email_verified_at in users table
if user.EmailVerifiedAt <= 0 { 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 // delete from verification table
db.Mgr.DeleteToken(claim.Email) db.Mgr.DeleteVerificationRequest(verificationRequest)
userIdStr := fmt.Sprintf("%v", user.ID) userIdStr := fmt.Sprintf("%v", user.ID)
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")
@@ -66,7 +67,7 @@ func VerifyEmailHandler() gin.HandlerFunc {
IP: utils.GetIP(c.Request), IP: utils.GetIP(c.Request),
} }
db.Mgr.SaveSession(sessionData) db.Mgr.AddSession(sessionData)
}() }()
utils.SetCookie(c, accessToken) utils.SetCookie(c, accessToken)
c.Redirect(http.StatusTemporaryRedirect, claim.RedirectURL) c.Redirect(http.StatusTemporaryRedirect, claim.RedirectURL)

View File

@@ -29,7 +29,7 @@ func DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Respo
session.DeleteUserSession(fmt.Sprintf("%x", user.ID)) session.DeleteUserSession(fmt.Sprintf("%x", user.ID))
err = db.Mgr.DeleteUser(params.Email) err = db.Mgr.DeleteUser(user)
if err != nil { if err != nil {
log.Println("Err:", err) log.Println("Err:", err)
return res, err return res, err

View File

@@ -68,7 +68,7 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
IP: utils.GetIP(gc.Request), IP: utils.GetIP(gc.Request),
} }
db.Mgr.SaveSession(sessionData) db.Mgr.AddSession(sessionData)
}() }()
res = &model.AuthResponse{ res = &model.AuthResponse{

View File

@@ -27,7 +27,7 @@ func Logout(ctx context.Context) (*model.Response, error) {
} }
userId := fmt.Sprintf("%v", claim["id"]) userId := fmt.Sprintf("%v", claim["id"])
session.DeleteToken(userId, token) session.DeleteVerificationRequest(userId, token)
res = &model.Response{ res = &model.Response{
Message: "Logged out successfully", Message: "Logged out successfully",
} }

View File

@@ -35,6 +35,7 @@ func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Respo
// find user with email // find user with email
existingUser, err := db.Mgr.GetUserByEmail(params.Email) existingUser, err := db.Mgr.GetUserByEmail(params.Email)
if err != nil { if err != nil {
user.SignupMethod = enum.MagicLink.String() user.SignupMethod = enum.MagicLink.String()
// define roles for new user // 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.Roles = strings.Join(inputRoles, ",")
user, _ = db.Mgr.AddUser(user)
} else { } else {
user = existingUser user = existingUser
// There multiple scenarios with roles here in magic link login // 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.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" { if constants.DISABLE_EMAIL_VERIFICATION != "true" {
// insert verification request // insert verification request
verificationType := enum.MagicLink.String() verificationType := enum.MagicLink.String()

View File

@@ -18,7 +18,7 @@ func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model
return res, fmt.Errorf(`basic authentication is disabled for this instance`) 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 { if err != nil {
return res, fmt.Errorf(`invalid token`) return res, fmt.Errorf(`invalid token`)
} }
@@ -48,7 +48,7 @@ func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model
user.SignupMethod = signupMethod user.SignupMethod = signupMethod
// delete from verification table // delete from verification table
db.Mgr.DeleteToken(claim.Email) db.Mgr.DeleteVerificationRequest(verificationRequest)
db.Mgr.UpdateUser(user) db.Mgr.UpdateUser(user)
res = &model.Response{ res = &model.Response{

View File

@@ -79,7 +79,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
if constants.DISABLE_EMAIL_VERIFICATION == "true" { if constants.DISABLE_EMAIL_VERIFICATION == "true" {
user.EmailVerifiedAt = time.Now().Unix() user.EmailVerifiedAt = time.Now().Unix()
} }
user, err = db.Mgr.SaveUser(user) user, err = db.Mgr.AddUser(user)
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -135,7 +135,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
IP: utils.GetIP(gc.Request), IP: utils.GetIP(gc.Request),
} }
db.Mgr.SaveSession(sessionData) db.Mgr.AddSession(sessionData)
}() }()
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Signed up successfully.`, Message: `Signed up successfully.`,

View File

@@ -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 // if access token has expired and refresh/session token is valid
// generate new accessToken // generate new accessToken
currentRefreshToken := session.GetToken(userIdStr, token) currentRefreshToken := session.GetToken(userIdStr, token)
session.DeleteToken(userIdStr, token) session.DeleteVerificationRequest(userIdStr, token)
token, expiresAt, _ = utils.CreateAuthToken(user, enum.AccessToken, claimRoles) token, expiresAt, _ = utils.CreateAuthToken(user, enum.AccessToken, claimRoles)
session.SetToken(userIdStr, token, currentRefreshToken) session.SetToken(userIdStr, token, currentRefreshToken)
go func() { go func() {
@@ -74,7 +74,7 @@ func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
IP: utils.GetIP(gc.Request), IP: utils.GetIP(gc.Request),
} }
db.Mgr.SaveSession(sessionData) db.Mgr.AddSession(sessionData)
}() }()
} }

View File

@@ -3,6 +3,7 @@ package resolvers
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"strings" "strings"
"time" "time"
@@ -20,7 +21,8 @@ func VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.Aut
return res, err return res, err
} }
_, err = db.Mgr.GetVerificationByToken(params.Token) verificationRequest, err := db.Mgr.GetVerificationByToken(params.Token)
log.Println("=> vf req:", verificationRequest)
if err != nil { if err != nil {
return res, fmt.Errorf(`invalid token`) 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 // 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 // delete from verification table
db.Mgr.DeleteToken(claim.Email) db.Mgr.DeleteVerificationRequest(verificationRequest)
userIdStr := fmt.Sprintf("%v", user.ID) userIdStr := fmt.Sprintf("%v", user.ID)
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")
@@ -55,7 +58,7 @@ func VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.Aut
IP: utils.GetIP(gc.Request), IP: utils.GetIP(gc.Request),
} }
db.Mgr.SaveSession(sessionData) db.Mgr.AddSession(sessionData)
}() }()
res = &model.AuthResponse{ res = &model.AuthResponse{

View File

@@ -41,7 +41,7 @@ func (c *InMemoryStore) DeleteUserSession(userId string) {
c.mu.Unlock() c.mu.Unlock()
} }
func (c *InMemoryStore) DeleteToken(userId, accessToken string) { func (c *InMemoryStore) DeleteVerificationRequest(userId, accessToken string) {
c.mu.Lock() c.mu.Lock()
delete(c.store[userId], accessToken) delete(c.store[userId], accessToken)
c.mu.Unlock() c.mu.Unlock()

View File

@@ -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() err := c.store.HDel(c.ctx, "authorizer_"+userId, accessToken).Err()
if err != nil { if err != nil {
log.Fatalln("Error deleting redis token:", err) log.Fatalln("Error deleting redis token:", err)

View File

@@ -27,12 +27,12 @@ func SetToken(userId, accessToken, refreshToken string) {
} }
} }
func DeleteToken(userId, accessToken string) { func DeleteVerificationRequest(userId, accessToken string) {
if SessionStoreObj.RedisMemoryStoreObj != nil { if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.DeleteToken(userId, accessToken) SessionStoreObj.RedisMemoryStoreObj.DeleteVerificationRequest(userId, accessToken)
} }
if SessionStoreObj.InMemoryStoreObj != nil { if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.DeleteToken(userId, accessToken) SessionStoreObj.InMemoryStoreObj.DeleteVerificationRequest(userId, accessToken)
} }
} }

View File

@@ -1,30 +1,6 @@
package utils 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 // 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() { 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)
}
} }