2022-04-21 07:06:22 +00:00
package cassandradb
import (
2022-04-23 12:22:02 +00:00
"crypto/tls"
"crypto/x509"
2022-04-21 07:06:22 +00:00
"fmt"
2022-04-22 15:54:39 +00:00
"strings"
2022-07-04 16:27:14 +00:00
"time"
2022-04-21 07:06:22 +00:00
"github.com/authorizerdev/authorizer/server/constants"
2022-04-23 12:22:02 +00:00
"github.com/authorizerdev/authorizer/server/crypto"
2022-04-21 07:06:22 +00:00
"github.com/authorizerdev/authorizer/server/db/models"
2022-05-29 11:52:46 +00:00
"github.com/authorizerdev/authorizer/server/memorystore"
2022-04-23 12:22:02 +00:00
"github.com/gocql/gocql"
2022-04-21 07:06:22 +00:00
cansandraDriver "github.com/gocql/gocql"
2022-08-02 08:42:36 +00:00
log "github.com/sirupsen/logrus"
2022-04-21 07:06:22 +00:00
)
type provider struct {
db * cansandraDriver . Session
}
2022-04-21 12:24:33 +00:00
// KeySpace for the cassandra database
var KeySpace string
2022-04-21 07:06:22 +00:00
// NewProvider to initialize arangodb connection
func NewProvider ( ) ( * provider , error ) {
2022-05-31 02:44:03 +00:00
dbURL := memorystore . RequiredEnvStoreObj . GetRequiredEnv ( ) . DatabaseURL
2022-04-23 12:22:02 +00:00
if dbURL == "" {
2022-05-31 02:44:03 +00:00
dbHost := memorystore . RequiredEnvStoreObj . GetRequiredEnv ( ) . DatabaseHost
dbPort := memorystore . RequiredEnvStoreObj . GetRequiredEnv ( ) . DatabasePort
if dbPort != "" && dbHost != "" {
dbURL = fmt . Sprintf ( "%s:%s" , dbHost , dbPort )
2022-06-05 06:43:55 +00:00
} else if dbHost != "" {
dbURL = dbHost
2022-04-23 12:22:02 +00:00
}
}
2022-05-31 02:44:03 +00:00
KeySpace = memorystore . RequiredEnvStoreObj . GetRequiredEnv ( ) . DatabaseName
if KeySpace == "" {
2022-05-29 11:52:46 +00:00
KeySpace = constants . EnvKeyDatabaseName
}
2022-04-22 15:54:39 +00:00
clusterURL := [ ] string { }
if strings . Contains ( dbURL , "," ) {
clusterURL = strings . Split ( dbURL , "," )
} else {
clusterURL = append ( clusterURL , dbURL )
}
cassandraClient := cansandraDriver . NewCluster ( clusterURL ... )
2022-05-31 02:44:03 +00:00
dbUsername := memorystore . RequiredEnvStoreObj . GetRequiredEnv ( ) . DatabaseUsername
dbPassword := memorystore . RequiredEnvStoreObj . GetRequiredEnv ( ) . DatabasePassword
2022-05-29 11:52:46 +00:00
if dbUsername != "" && dbPassword != "" {
2022-04-22 15:54:39 +00:00
cassandraClient . Authenticator = & cansandraDriver . PasswordAuthenticator {
2022-05-29 11:52:46 +00:00
Username : dbUsername ,
Password : dbPassword ,
2022-04-22 15:54:39 +00:00
}
}
2022-04-21 12:24:33 +00:00
2022-05-31 02:44:03 +00:00
dbCert := memorystore . RequiredEnvStoreObj . GetRequiredEnv ( ) . DatabaseCert
dbCACert := memorystore . RequiredEnvStoreObj . GetRequiredEnv ( ) . DatabaseCACert
dbCertKey := memorystore . RequiredEnvStoreObj . GetRequiredEnv ( ) . DatabaseCertKey
2022-05-29 11:52:46 +00:00
if dbCert != "" && dbCACert != "" && dbCertKey != "" {
certString , err := crypto . DecryptB64 ( dbCert )
2022-04-23 12:22:02 +00:00
if err != nil {
return nil , err
}
2022-05-29 11:52:46 +00:00
keyString , err := crypto . DecryptB64 ( dbCertKey )
2022-04-23 12:22:02 +00:00
if err != nil {
return nil , err
}
2022-05-29 11:52:46 +00:00
caString , err := crypto . DecryptB64 ( dbCACert )
2022-04-23 12:22:02 +00:00
if err != nil {
return nil , err
}
cert , err := tls . X509KeyPair ( [ ] byte ( certString ) , [ ] byte ( keyString ) )
if err != nil {
return nil , err
}
caCertPool := x509 . NewCertPool ( )
caCertPool . AppendCertsFromPEM ( [ ] byte ( caString ) )
cassandraClient . SslOpts = & cansandraDriver . SslOptions {
Config : & tls . Config {
Certificates : [ ] tls . Certificate { cert } ,
RootCAs : caCertPool ,
InsecureSkipVerify : true ,
} ,
EnableHostVerification : false ,
}
}
2022-04-21 07:06:22 +00:00
cassandraClient . RetryPolicy = & cansandraDriver . SimpleRetryPolicy {
NumRetries : 3 ,
}
2022-04-23 12:22:02 +00:00
cassandraClient . Consistency = gocql . LocalQuorum
2022-07-04 16:27:14 +00:00
cassandraClient . ConnectTimeout = 10 * time . Second
cassandraClient . ProtoVersion = 4
2022-08-02 08:42:36 +00:00
cassandraClient . Timeout = 30 * time . Minute // for large data
2022-04-21 07:06:22 +00:00
session , err := cassandraClient . CreateSession ( )
if err != nil {
return nil , err
}
2022-04-23 12:22:02 +00:00
// Note for astra keyspaces can only be created from there console
// https://docs.datastax.com/en/astra/docs/datastax-astra-faq.html#_i_am_trying_to_create_a_keyspace_in_the_cql_shell_and_i_am_running_into_an_error_how_do_i_fix_this
getKeyspaceQuery := fmt . Sprintf ( "SELECT keyspace_name FROM system_schema.keyspaces;" )
scanner := session . Query ( getKeyspaceQuery ) . Iter ( ) . Scanner ( )
hasAuthorizerKeySpace := false
for scanner . Next ( ) {
var keySpace string
err := scanner . Scan ( & keySpace )
if err != nil {
return nil , err
}
if keySpace == KeySpace {
hasAuthorizerKeySpace = true
break
}
}
if ! hasAuthorizerKeySpace {
createKeySpaceQuery := fmt . Sprintf ( "CREATE KEYSPACE %s WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1};" , KeySpace )
err = session . Query ( createKeySpaceQuery ) . Exec ( )
if err != nil {
return nil , err
}
2022-04-21 07:06:22 +00:00
}
// make sure collections are present
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))" ,
2022-04-21 12:24:33 +00:00
KeySpace , models . Collections . Env )
2022-04-21 07:06:22 +00:00
err = session . Query ( envCollectionQuery ) . Exec ( )
if err != nil {
return nil , err
}
2022-04-21 12:24:33 +00:00
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 )
2022-04-21 07:06:22 +00:00
err = session . Query ( sessionCollectionQuery ) . Exec ( )
if err != nil {
return nil , err
}
2022-07-12 06:18:42 +00:00
sessionIndexQuery := fmt . Sprintf ( "CREATE INDEX IF NOT EXISTS authorizer_session_user_id ON %s.%s (user_id)" , KeySpace , models . Collections . Session )
err = session . Query ( sessionIndexQuery ) . Exec ( )
if err != nil {
return nil , err
}
2022-04-21 07:06:22 +00:00
2022-04-22 14:26:55 +00:00
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 )
2022-04-21 07:06:22 +00:00
err = session . Query ( userCollectionQuery ) . Exec ( )
if err != nil {
return nil , err
}
2022-04-22 14:26:55 +00:00
userIndexQuery := fmt . Sprintf ( "CREATE INDEX IF NOT EXISTS authorizer_user_email ON %s.%s (email)" , KeySpace , models . Collections . User )
err = session . Query ( userIndexQuery ) . Exec ( )
if err != nil {
return nil , err
}
2022-12-21 17:44:24 +00:00
userPhoneNumberIndexQuery := fmt . Sprintf ( "CREATE INDEX IF NOT EXISTS authorizer_user_phone_number ON %s.%s (phone_number)" , KeySpace , models . Collections . User )
err = session . Query ( userPhoneNumberIndexQuery ) . Exec ( )
if err != nil {
return nil , err
}
2022-07-23 09:56:44 +00:00
// add is_multi_factor_auth_enabled on users table
2022-08-02 08:42:36 +00:00
userTableAlterQuery := fmt . Sprintf ( ` ALTER TABLE %s.%s ADD is_multi_factor_auth_enabled boolean ` , KeySpace , models . Collections . User )
2022-07-23 09:56:44 +00:00
err = session . Query ( userTableAlterQuery ) . Exec ( )
if err != nil {
2022-08-02 08:42:36 +00:00
log . Debug ( "Failed to alter table as column exists: " , err )
// return nil, err
2022-07-23 09:56:44 +00:00
}
2022-04-21 07:06:22 +00:00
// token is reserved keyword in cassandra, hence we need to use jwt_token
2022-04-22 14:26:55 +00:00
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 )
2022-04-21 07:06:22 +00:00
err = session . Query ( verificationRequestCollectionQuery ) . Exec ( )
if err != nil {
return nil , err
}
2022-04-22 14:26:55 +00:00
verificationRequestIndexQuery := fmt . Sprintf ( "CREATE INDEX IF NOT EXISTS authorizer_verification_request_email ON %s.%s (email)" , KeySpace , models . Collections . VerificationRequest )
err = session . Query ( verificationRequestIndexQuery ) . Exec ( )
if err != nil {
return nil , err
}
verificationRequestIndexQuery = fmt . Sprintf ( "CREATE INDEX IF NOT EXISTS authorizer_verification_request_identifier ON %s.%s (identifier)" , KeySpace , models . Collections . VerificationRequest )
err = session . Query ( verificationRequestIndexQuery ) . Exec ( )
if err != nil {
return nil , err
}
verificationRequestIndexQuery = fmt . Sprintf ( "CREATE INDEX IF NOT EXISTS authorizer_verification_request_jwt_token ON %s.%s (jwt_token)" , KeySpace , models . Collections . VerificationRequest )
err = session . Query ( verificationRequestIndexQuery ) . Exec ( )
if err != nil {
return nil , err
}
2022-04-21 07:06:22 +00:00
2022-07-12 06:18:42 +00:00
webhookCollectionQuery := fmt . Sprintf ( "CREATE TABLE IF NOT EXISTS %s.%s (id text, event_name text, endpoint text, enabled boolean, headers text, updated_at bigint, created_at bigint, PRIMARY KEY (id))" , KeySpace , models . Collections . Webhook )
2022-07-08 13:39:23 +00:00
err = session . Query ( webhookCollectionQuery ) . Exec ( )
if err != nil {
return nil , err
}
webhookIndexQuery := fmt . Sprintf ( "CREATE INDEX IF NOT EXISTS authorizer_webhook_event_name ON %s.%s (event_name)" , KeySpace , models . Collections . Webhook )
err = session . Query ( webhookIndexQuery ) . Exec ( )
if err != nil {
return nil , err
}
2023-03-29 01:36:33 +00:00
// add event_description to webhook table
webhookAlterQuery := fmt . Sprintf ( ` ALTER TABLE %s.%s ADD (event_description text); ` , KeySpace , models . Collections . Webhook )
err = session . Query ( webhookAlterQuery ) . Exec ( )
if err != nil {
log . Debug ( "Failed to alter table as column exists: " , err )
// continue
}
2022-07-08 13:39:23 +00:00
webhookLogCollectionQuery := fmt . Sprintf ( "CREATE TABLE IF NOT EXISTS %s.%s (id text, http_status bigint, response text, request text, webhook_id text,updated_at bigint, created_at bigint, PRIMARY KEY (id))" , KeySpace , models . Collections . WebhookLog )
err = session . Query ( webhookLogCollectionQuery ) . Exec ( )
if err != nil {
return nil , err
}
webhookLogIndexQuery := fmt . Sprintf ( "CREATE INDEX IF NOT EXISTS authorizer_webhook_log_webhook_id ON %s.%s (webhook_id)" , KeySpace , models . Collections . WebhookLog )
err = session . Query ( webhookLogIndexQuery ) . Exec ( )
if err != nil {
return nil , err
}
2022-07-15 04:53:45 +00:00
emailTemplateCollectionQuery := fmt . Sprintf ( "CREATE TABLE IF NOT EXISTS %s.%s (id text, event_name text, template text, updated_at bigint, created_at bigint, PRIMARY KEY (id))" , KeySpace , models . Collections . EmailTemplate )
err = session . Query ( emailTemplateCollectionQuery ) . Exec ( )
if err != nil {
return nil , err
}
emailTemplateIndexQuery := fmt . Sprintf ( "CREATE INDEX IF NOT EXISTS authorizer_email_template_event_name ON %s.%s (event_name)" , KeySpace , models . Collections . EmailTemplate )
err = session . Query ( emailTemplateIndexQuery ) . Exec ( )
if err != nil {
return nil , err
}
2022-07-29 10:45:57 +00:00
// add subject on email_templates table
2022-08-13 06:04:24 +00:00
emailTemplateAlterQuery := fmt . Sprintf ( ` ALTER TABLE %s.%s ADD (subject text, design text); ` , KeySpace , models . Collections . EmailTemplate )
2022-07-29 10:45:57 +00:00
err = session . Query ( emailTemplateAlterQuery ) . Exec ( )
if err != nil {
2022-08-13 06:04:24 +00:00
log . Debug ( "Failed to alter table as column exists: " , err )
// continue
2022-07-29 10:45:57 +00:00
}
2022-07-15 04:53:45 +00:00
2022-07-23 11:09:35 +00:00
otpCollection := fmt . Sprintf ( "CREATE TABLE IF NOT EXISTS %s.%s (id text, email text, otp text, expires_at bigint, updated_at bigint, created_at bigint, PRIMARY KEY (id))" , KeySpace , models . Collections . OTP )
err = session . Query ( otpCollection ) . Exec ( )
if err != nil {
return nil , err
}
otpIndexQuery := fmt . Sprintf ( "CREATE INDEX IF NOT EXISTS authorizer_otp_email ON %s.%s (email)" , KeySpace , models . Collections . OTP )
err = session . Query ( otpIndexQuery ) . Exec ( )
if err != nil {
return nil , err
}
2023-07-13 06:09:22 +00:00
otpIndexQueryPhoneNumber := fmt . Sprintf ( "CREATE INDEX IF NOT EXISTS authorizer_otp_phone_number ON %s.%s (phone_number)" , KeySpace , models . Collections . OTP )
err = session . Query ( otpIndexQueryPhoneNumber ) . Exec ( )
if err != nil {
return nil , err
}
2022-04-21 07:06:22 +00:00
return & provider {
db : session ,
} , err
}