Skip to content

Commit 4778827

Browse files
authored
Merge pull request #144 from authorizerdev/feat/casandra-db
feat: add support for cassandra db
2 parents 75e44ff + 39c2c36 commit 4778827

33 files changed

+1015
-76
lines changed

server/constants/db_types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ const (
1717
DbTypeYugabyte = "yugabyte"
1818
// DbTypeMariaDB is the mariadb database type
1919
DbTypeMariaDB = "mariadb"
20+
// DbTypeCassandra is the cassandra database type
21+
DbTypeCassandraDB = "cassandradb"
2022
)

server/constants/env.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ const (
3030
EnvKeyDatabaseURL = "DATABASE_URL"
3131
// EnvKeyDatabaseName key for env variable DATABASE_NAME
3232
EnvKeyDatabaseName = "DATABASE_NAME"
33+
// EnvKeyDatabaseUsername key for env variable DATABASE_USERNAME
34+
EnvKeyDatabaseUsername = "DATABASE_USERNAME"
35+
// EnvKeyDatabasePassword key for env variable DATABASE_PASSWORD
36+
EnvKeyDatabasePassword = "DATABASE_PASSWORD"
37+
// EnvKeyDatabasePort key for env variable DATABASE_PORT
38+
EnvKeyDatabasePort = "DATABASE_PORT"
39+
// EnvKeyDatabaseHost key for env variable DATABASE_HOST
40+
EnvKeyDatabaseHost = "DATABASE_HOST"
3341
// EnvKeySmtpHost key for env variable SMTP_HOST
3442
EnvKeySmtpHost = "SMTP_HOST"
3543
// EnvKeySmtpPort key for env variable SMTP_PORT

server/db/db.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/authorizerdev/authorizer/server/constants"
55
"github.com/authorizerdev/authorizer/server/db/providers"
66
"github.com/authorizerdev/authorizer/server/db/providers/arangodb"
7+
"github.com/authorizerdev/authorizer/server/db/providers/cassandradb"
78
"github.com/authorizerdev/authorizer/server/db/providers/mongodb"
89
"github.com/authorizerdev/authorizer/server/db/providers/sql"
910
"github.com/authorizerdev/authorizer/server/envstore"
@@ -15,9 +16,10 @@ var Provider providers.Provider
1516
func InitDB() error {
1617
var err error
1718

18-
isSQL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb
19+
isSQL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeCassandraDB
1920
isArangoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb
2021
isMongoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb
22+
isCassandra := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeCassandraDB
2123

2224
if isSQL {
2325
Provider, err = sql.NewProvider()
@@ -40,5 +42,12 @@ func InitDB() error {
4042
}
4143
}
4244

45+
if isCassandra {
46+
Provider, err = cassandradb.NewProvider()
47+
if err != nil {
48+
return err
49+
}
50+
}
51+
4352
return nil
4453
}

server/db/models/env.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package models
22

3+
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
4+
35
// Env model for db
46
type Env struct {
5-
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
6-
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
7-
EnvData string `gorm:"type:text" json:"env" bson:"env"`
8-
Hash string `gorm:"type:text" json:"hash" bson:"hash"`
9-
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
10-
CreatedAt int64 `json:"created_at" bson:"created_at"`
7+
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
8+
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
9+
EnvData string `gorm:"type:text" json:"env" bson:"env" cql:"env"`
10+
Hash string `gorm:"type:text" json:"hash" bson:"hash" cql:"hash"`
11+
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
12+
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
1113
}

server/db/models/session.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package models
22

3+
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
4+
35
// Session model for db
46
type Session struct {
5-
Key string `json:"_key,omitempty" bson:"_key,omitempty"` // for arangodb
6-
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
7-
UserID string `gorm:"type:char(36),index:" json:"user_id" bson:"user_id"`
8-
User User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" bson:"-"`
9-
UserAgent string `json:"user_agent" bson:"user_agent"`
10-
IP string `json:"ip" bson:"ip"`
11-
CreatedAt int64 `json:"created_at" bson:"created_at"`
12-
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
7+
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
8+
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
9+
UserID string `gorm:"type:char(36),index:" json:"user_id" bson:"user_id" cql:"user_id"`
10+
User User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" bson:"-" cql:"-"`
11+
UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent"`
12+
IP string `json:"ip" bson:"ip" cql:"ip"`
13+
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
14+
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
1315
}

server/db/models/user.go

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,30 @@ import (
66
"github.com/authorizerdev/authorizer/server/graph/model"
77
)
88

9+
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
10+
911
// User model for db
1012
type User struct {
11-
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
12-
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
13+
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
14+
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
1315

14-
Email string `gorm:"unique" json:"email" bson:"email"`
15-
EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at"`
16-
Password *string `gorm:"type:text" json:"password" bson:"password"`
17-
SignupMethods string `json:"signup_methods" bson:"signup_methods"`
18-
GivenName *string `json:"given_name" bson:"given_name"`
19-
FamilyName *string `json:"family_name" bson:"family_name"`
20-
MiddleName *string `json:"middle_name" bson:"middle_name"`
21-
Nickname *string `json:"nickname" bson:"nickname"`
22-
Gender *string `json:"gender" bson:"gender"`
23-
Birthdate *string `json:"birthdate" bson:"birthdate"`
24-
PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number"`
25-
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at"`
26-
Picture *string `gorm:"type:text" json:"picture" bson:"picture"`
27-
Roles string `json:"roles" bson:"roles"`
28-
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
29-
CreatedAt int64 `json:"created_at" bson:"created_at"`
30-
RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp"`
16+
Email string `gorm:"unique" json:"email" bson:"email" cql:"email"`
17+
EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at" cql:"email_verified_at"`
18+
Password *string `gorm:"type:text" json:"password" bson:"password" cql:"password"`
19+
SignupMethods string `json:"signup_methods" bson:"signup_methods" cql:"signup_methods"`
20+
GivenName *string `json:"given_name" bson:"given_name" cql:"given_name"`
21+
FamilyName *string `json:"family_name" bson:"family_name" cql:"family_name"`
22+
MiddleName *string `json:"middle_name" bson:"middle_name" cql:"middle_name"`
23+
Nickname *string `json:"nickname" bson:"nickname" cql:"nickname"`
24+
Gender *string `json:"gender" bson:"gender" cql:"gender"`
25+
Birthdate *string `json:"birthdate" bson:"birthdate" cql:"birthdate"`
26+
PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number"`
27+
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at" cql:"phone_number_verified_at"`
28+
Picture *string `gorm:"type:text" json:"picture" bson:"picture" cql:"picture"`
29+
Roles string `json:"roles" bson:"roles" cql:"roles"`
30+
RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp" cql:"revoked_timestamp"`
31+
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
32+
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
3133
}
3234

3335
func (user *User) AsAPIUser() *model.User {
@@ -53,8 +55,8 @@ func (user *User) AsAPIUser() *model.User {
5355
PhoneNumberVerified: &isPhoneVerified,
5456
Picture: user.Picture,
5557
Roles: strings.Split(user.Roles, ","),
58+
RevokedTimestamp: revokedTimestamp,
5659
CreatedAt: &createdAt,
5760
UpdatedAt: &updatedAt,
58-
RevokedTimestamp: revokedTimestamp,
5961
}
6062
}

server/db/models/verification_requests.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@ package models
22

33
import "github.com/authorizerdev/authorizer/server/graph/model"
44

5+
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
6+
57
// VerificationRequest model for db
68
type VerificationRequest struct {
7-
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
8-
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
9-
Token string `gorm:"type:text" json:"token" bson:"token"`
10-
Identifier string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(64)" json:"identifier" bson:"identifier"`
11-
ExpiresAt int64 `json:"expires_at" bson:"expires_at"`
12-
CreatedAt int64 `json:"created_at" bson:"created_at"`
13-
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
14-
Email string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email"`
15-
Nonce string `gorm:"type:text" json:"nonce" bson:"nonce"`
16-
RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri"`
9+
Key string `json:"_key,omitempty" bson:"_key" cql:"_key,omitempty"` // for arangodb
10+
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
11+
Token string `gorm:"type:text" json:"token" bson:"token" cql:"jwt_token"` // token is reserved keyword in cassandra
12+
Identifier string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(64)" json:"identifier" bson:"identifier" cql:"identifier"`
13+
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at"`
14+
Email string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email" cql:"email"`
15+
Nonce string `gorm:"type:text" json:"nonce" bson:"nonce" cql:"nonce"`
16+
RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri" cql:"redirect_uri"`
17+
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
18+
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
1719
}
1820

1921
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
@@ -30,10 +32,10 @@ func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequ
3032
Token: &token,
3133
Identifier: &identifier,
3234
Expires: &expires,
33-
CreatedAt: &createdAt,
34-
UpdatedAt: &updatedAt,
3535
Email: &email,
3636
Nonce: &nonce,
3737
RedirectURI: &redirectURI,
38+
CreatedAt: &createdAt,
39+
UpdatedAt: &updatedAt,
3840
}
3941
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package cassandradb
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/authorizerdev/authorizer/server/db/models"
8+
"github.com/gocql/gocql"
9+
"github.com/google/uuid"
10+
)
11+
12+
// AddEnv to save environment information in database
13+
func (p *provider) AddEnv(env models.Env) (models.Env, error) {
14+
if env.ID == "" {
15+
env.ID = uuid.New().String()
16+
}
17+
18+
env.CreatedAt = time.Now().Unix()
19+
env.UpdatedAt = time.Now().Unix()
20+
insertEnvQuery := fmt.Sprintf("INSERT INTO %s (id, env, hash, created_at, updated_at) VALUES ('%s', '%s', '%s', %d, %d)", KeySpace+"."+models.Collections.Env, env.ID, env.EnvData, env.Hash, env.CreatedAt, env.UpdatedAt)
21+
err := p.db.Query(insertEnvQuery).Exec()
22+
if err != nil {
23+
return env, err
24+
}
25+
26+
return env, nil
27+
}
28+
29+
// UpdateEnv to update environment information in database
30+
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
31+
env.UpdatedAt = time.Now().Unix()
32+
33+
updateEnvQuery := fmt.Sprintf("UPDATE %s SET env = '%s', updated_at = %d WHERE id = '%s'", KeySpace+"."+models.Collections.Env, env.EnvData, env.UpdatedAt, env.ID)
34+
err := p.db.Query(updateEnvQuery).Exec()
35+
if err != nil {
36+
return env, err
37+
}
38+
return env, nil
39+
}
40+
41+
// GetEnv to get environment information from database
42+
func (p *provider) GetEnv() (models.Env, error) {
43+
var env models.Env
44+
45+
query := fmt.Sprintf("SELECT id, env, hash, created_at, updated_at FROM %s LIMIT 1", KeySpace+"."+models.Collections.Env)
46+
err := p.db.Query(query).Consistency(gocql.One).Scan(&env.ID, &env.EnvData, &env.Hash, &env.CreatedAt, &env.UpdatedAt)
47+
if err != nil {
48+
return env, err
49+
}
50+
51+
return env, nil
52+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package cassandradb
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"strings"
7+
8+
"github.com/authorizerdev/authorizer/server/constants"
9+
"github.com/authorizerdev/authorizer/server/db/models"
10+
"github.com/authorizerdev/authorizer/server/envstore"
11+
cansandraDriver "github.com/gocql/gocql"
12+
)
13+
14+
type provider struct {
15+
db *cansandraDriver.Session
16+
}
17+
18+
// KeySpace for the cassandra database
19+
var KeySpace string
20+
21+
// NewProvider to initialize arangodb connection
22+
func NewProvider() (*provider, error) {
23+
dbURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)
24+
KeySpace = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName)
25+
clusterURL := []string{}
26+
if strings.Contains(dbURL, ",") {
27+
clusterURL = strings.Split(dbURL, ",")
28+
} else {
29+
clusterURL = append(clusterURL, dbURL)
30+
}
31+
cassandraClient := cansandraDriver.NewCluster(clusterURL...)
32+
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseUsername) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabasePassword) != "" {
33+
cassandraClient.Authenticator = &cansandraDriver.PasswordAuthenticator{
34+
Username: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseUsername),
35+
Password: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabasePassword),
36+
}
37+
}
38+
39+
cassandraClient.RetryPolicy = &cansandraDriver.SimpleRetryPolicy{
40+
NumRetries: 3,
41+
}
42+
cassandraClient.Consistency = cansandraDriver.Quorum
43+
44+
session, err := cassandraClient.CreateSession()
45+
if err != nil {
46+
log.Println("Error while creating connection to cassandra db", err)
47+
return nil, err
48+
}
49+
50+
keyspaceQuery := fmt.Sprintf("CREATE KEYSPACE IF NOT EXISTS %s WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor':1}",
51+
KeySpace)
52+
err = session.Query(keyspaceQuery).Exec()
53+
if err != nil {
54+
log.Println("Unable to create keyspace:", err)
55+
return nil, err
56+
}
57+
58+
// make sure collections are present
59+
envCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, env text, hash text, updated_at bigint, created_at bigint, PRIMARY KEY (id))",
60+
KeySpace, models.Collections.Env)
61+
err = session.Query(envCollectionQuery).Exec()
62+
if err != nil {
63+
log.Println("Unable to create env collection:", err)
64+
return nil, err
65+
}
66+
67+
sessionCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, user_id text, user_agent text, ip text, updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.Session)
68+
err = session.Query(sessionCollectionQuery).Exec()
69+
if err != nil {
70+
log.Println("Unable to create session collection:", err)
71+
return nil, err
72+
}
73+
74+
userCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, email text, email_verified_at bigint, password text, signup_methods text, given_name text, family_name text, middle_name text, nickname text, gender text, birthdate text, phone_number text, phone_number_verified_at bigint, picture text, roles text, updated_at bigint, created_at bigint, revoked_timestamp bigint, PRIMARY KEY (id))", KeySpace, models.Collections.User)
75+
err = session.Query(userCollectionQuery).Exec()
76+
if err != nil {
77+
log.Println("Unable to create user collection:", err)
78+
return nil, err
79+
}
80+
userIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_user_email ON %s.%s (email)", KeySpace, models.Collections.User)
81+
err = session.Query(userIndexQuery).Exec()
82+
if err != nil {
83+
log.Println("Unable to create user index:", err)
84+
return nil, err
85+
}
86+
87+
// token is reserved keyword in cassandra, hence we need to use jwt_token
88+
verificationRequestCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, jwt_token text, identifier text, expires_at bigint, email text, nonce text, redirect_uri text, created_at bigint, updated_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.VerificationRequest)
89+
err = session.Query(verificationRequestCollectionQuery).Exec()
90+
if err != nil {
91+
log.Println("Unable to create verification request collection:", err)
92+
return nil, err
93+
}
94+
verificationRequestIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_verification_request_email ON %s.%s (email)", KeySpace, models.Collections.VerificationRequest)
95+
err = session.Query(verificationRequestIndexQuery).Exec()
96+
if err != nil {
97+
log.Println("Unable to create verification_requests index:", err)
98+
return nil, err
99+
}
100+
verificationRequestIndexQuery = fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_verification_request_identifier ON %s.%s (identifier)", KeySpace, models.Collections.VerificationRequest)
101+
err = session.Query(verificationRequestIndexQuery).Exec()
102+
if err != nil {
103+
log.Println("Unable to create verification_requests index:", err)
104+
return nil, err
105+
}
106+
verificationRequestIndexQuery = fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_verification_request_jwt_token ON %s.%s (jwt_token)", KeySpace, models.Collections.VerificationRequest)
107+
err = session.Query(verificationRequestIndexQuery).Exec()
108+
if err != nil {
109+
log.Println("Unable to create verification_requests index:", err)
110+
return nil, err
111+
}
112+
113+
return &provider{
114+
db: session,
115+
}, err
116+
}

0 commit comments

Comments
 (0)