This commit is contained in:
2023-04-30 13:54:49 +02:00
49 changed files with 989 additions and 27 deletions

2
storage/accounts.go Normal file → Executable file
View File

@@ -23,6 +23,6 @@ type LocalAuth struct {
}
type Validation struct {
Validated bool
Validated bool `json:"validated" bson:"validated"`
ValidationCode string `json:"validation_code" bson:"validation_code"`
}

0
storage/etcd.go Normal file → Executable file
View File

0
storage/mongodb.go Normal file → Executable file
View File

434
storage/postgresql.go Normal file
View File

@@ -0,0 +1,434 @@
/*
___ ___ ___ _ __ __ _ ___
/ __/ _ \ / _ \| '_ \ / _` |/ _ \
| (_| (_) | (_) | |_) | (_| | (_) |
\___\___/ \___/| .__/ \__, |\___/
| | __/ |
|_| |___/
*/
package storage
import (
"database/sql"
"encoding/json"
"fmt"
_ "github.com/lib/pq"
"github.com/spf13/viper"
"strconv"
"strings"
)
type PostgresqlStorage struct {
DbConnection *sql.DB
}
func NewPostgresqlStorage(cfg *viper.Viper) (PostgresqlStorage, error) {
var (
host = cfg.GetString("storage.db.psql.host")
port = cfg.GetString("storage.db.psql.port")
user = cfg.GetString("storage.db.psql.user")
password = cfg.GetString("storage.db.psql.password")
dbname = cfg.GetString("storage.db.psql.dbname")
)
portInt, _ := strconv.Atoi(port)
psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, portInt,
user, password, dbname)
db, err := sql.Open("postgres", psqlconn)
if err != nil {
fmt.Println("error", err)
return PostgresqlStorage{}, fmt.Errorf("connection to postgresql failed")
}
err = db.Ping()
if err != nil {
return PostgresqlStorage{}, fmt.Errorf("connection to postgresql database failed")
}
return PostgresqlStorage{
DbConnection: db,
}, nil
}
func (psql PostgresqlStorage) GetAccount(id string) (*Account, error) {
var (
data, metadata, emailValidation, phoneValidation []byte
)
account := &Account{}
stmtAccounts, err := psql.DbConnection.Prepare("" +
"SELECT id, namespace, data, " +
"metadata, username, password, email, email_validation, " +
"phone_number, phone_number_validation FROM accounts a JOIN " +
"account_auth auth ON id = account_id WHERE id = $1")
if err != nil {
return nil, fmt.Errorf("psql connection failed")
}
defer stmtAccounts.Close()
err = stmtAccounts.QueryRow(id).Scan(&account.ID,
&account.Namespace,
&data,
&metadata,
&account.Authentication.Local.Username,
&account.Authentication.Local.Password,
&account.Authentication.Local.Email,
&emailValidation,
&account.Authentication.Local.PhoneNumber,
&phoneValidation)
if err != nil {
return nil, fmt.Errorf("psql select account query failed")
}
err = json.Unmarshal(data, &account.Data)
if err != nil {
return nil, err
}
err = json.Unmarshal(metadata, &account.Metadata)
if err != nil {
return nil, err
}
err = json.Unmarshal(emailValidation, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneValidation, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
}
return account, nil
}
func (psql PostgresqlStorage) LocalAuthentication(namespace string, username string, email string,
phone_number string) (*Account, error) {
account := &Account{}
var (
data, metadata, emailValidation, phoneValidation []byte
)
if username != "" {
usernameStmt, err := psql.DbConnection.Prepare("SELECT id, namespace, data, metadata, username, " +
"password, email, email_validation, phone_number, phone_number_validation " +
"FROM accounts INNER JOIN account_auth ON accounts.id = account_auth.account_id WHERE " +
"namespace = $1 AND username = $2;")
if err != nil {
return nil, err
}
err = usernameStmt.QueryRow(namespace, username).Scan(
&account.ID,
&account.Namespace, &data, &metadata,
&account.Authentication.Local.Username,
&account.Authentication.Local.Password,
&account.Authentication.Local.Email,
&emailValidation, &account.Authentication.Local.PhoneNumber,
&phoneValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(metadata, &account.Metadata)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &account.Data)
if err != nil {
return nil, err
}
err = json.Unmarshal(emailValidation, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneValidation, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
}
return account, nil
} else if email != "" {
account.Authentication.Local.Email = email
emailStmt, err := psql.DbConnection.Prepare("SELECT id, namespace, data, metadata, username, " +
"password, email_validation, phone_number, phone_number_validation " +
"FROM accounts INNER JOIN account_auth ON " +
"accounts.id = account_auth.account_id WHERE namespace = $1 AND email = $2;")
if err != nil {
return nil, err
}
err = emailStmt.QueryRow(namespace, email).Scan(
&account.ID,
&account.Namespace,
&data, &metadata,
&account.Authentication.Local.Username,
&account.Authentication.Local.Password,
&emailValidation,
&account.Authentication.Local.PhoneNumber,
&phoneValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(metadata, &account.Metadata)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &account.Data)
if err != nil {
return nil, err
}
err = json.Unmarshal(emailValidation, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneValidation, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
}
return account, nil
} else if phone_number != "" {
account.Authentication.Local.PhoneNumber = phone_number
phoneStmt, err := psql.DbConnection.Prepare("SELECT id, namespace, " +
"data, metadata, username, password, email, " +
"email_validation, phone_number_validation FROM accounts " +
"INNER JOIN account_auth ON accounts.id = account_auth.account_id WHERE " +
"namespace = $1 AND phone_number = $2;")
if err != nil {
return nil, err
}
err = phoneStmt.QueryRow(namespace, phone_number).Scan(&account.ID,
&account.Namespace,
&data,
&metadata,
&account.Authentication.Local.Username,
&account.Authentication.Local.Password,
&account.Authentication.Local.Email,
&emailValidation,
&phoneValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(metadata, &account.Metadata)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &account.Data)
if err != nil {
return nil, err
}
err = json.Unmarshal(emailValidation, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneValidation, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
}
return account, nil
}
return nil, fmt.Errorf("localauthentication func error PSQL")
}
func (psql PostgresqlStorage) GetAccounts(namespaces []string) ([]Account, error) {
var accounts []Account
namespacesStr := "'" + strings.Join(namespaces, "', '") + "'"
query := `
SELECT accounts.id, accounts.namespace, accounts.data, accounts.metadata,
account_auth.username, account_auth.password,
account_auth.email, account_auth.email_validation,
account_auth.phone_number, account_auth.phone_number_validation
FROM accounts
INNER JOIN account_auth ON accounts.id = account_auth.account_id
WHERE accounts.namespace IN (%s)
`
query = fmt.Sprintf(query, namespacesStr)
rows, err := psql.DbConnection.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var account Account
var dataBytes []byte
var metadataBytes []byte
var emailValidationBytes []byte
var phoneNumberValidationBytes []byte
err := rows.Scan(
&account.ID,
&account.Namespace,
&dataBytes,
&metadataBytes,
&account.Authentication.Local.Username,
&account.Authentication.Local.Password,
&account.Authentication.Local.Email,
&emailValidationBytes,
&account.Authentication.Local.PhoneNumber,
&phoneNumberValidationBytes,
)
if err != nil {
return nil, err
}
err = json.Unmarshal(dataBytes, &account.Data)
if err != nil {
return nil, err
}
err = json.Unmarshal(metadataBytes, &account.Metadata)
if err != nil {
return nil, err
}
err = json.Unmarshal(emailValidationBytes, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneNumberValidationBytes, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
}
accounts = append(accounts, account)
}
return accounts, nil
}
func (psql PostgresqlStorage) GetAccountsByIds(accountids []string) ([]Account, error) {
var accounts []Account
accountIdsStr := "'" + strings.Join(accountids, "', '") + "'"
query := `
SELECT accounts.id, accounts.namespace, accounts.data, accounts.metadata,
account_auth.username, account_auth.password,
account_auth.email, account_auth.email_validation,
account_auth.phone_number, account_auth.phone_number_validation
FROM accounts
INNER JOIN account_auth ON accounts.id = account_auth.account_id
WHERE accounts.id IN (%s)
`
query = fmt.Sprintf(query, accountIdsStr)
rows, err := psql.DbConnection.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var account Account
var dataBytes []byte
var metadataBytes []byte
var emailValidationBytes []byte
var phoneNumberValidationBytes []byte
err := rows.Scan(
&account.ID,
&account.Namespace,
&dataBytes,
&metadataBytes,
&account.Authentication.Local.Username,
&account.Authentication.Local.Password,
&account.Authentication.Local.Email,
&emailValidationBytes,
&account.Authentication.Local.PhoneNumber,
&phoneNumberValidationBytes)
if err != nil {
return nil, err
}
err = json.Unmarshal(dataBytes, &account.Data)
if err != nil {
return nil, err
}
err = json.Unmarshal(metadataBytes, &account.Metadata)
if err != nil {
return nil, err
}
err = json.Unmarshal(emailValidationBytes, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneNumberValidationBytes, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
}
accounts = append(accounts, account)
}
return accounts, nil
}
func (psql PostgresqlStorage) CreateAccount(account Account) error {
insertAccountStmt, err := psql.DbConnection.Prepare("INSERT INTO accounts (id, namespace, data, metadata)" +
" VALUES ($1, $2, $3, $4)")
if err != nil {
return err
}
dataAccountJson, err := json.Marshal(account.Data)
if err != nil {
return err
}
metadataAccountJson, err := json.Marshal(account.Metadata)
if err != nil {
return err
}
_, err = insertAccountStmt.Exec(account.ID, account.Namespace, dataAccountJson, metadataAccountJson)
if err != nil {
return err
}
insertAccountAuthStmt, err := psql.DbConnection.Prepare("INSERT INTO account_auth (account_id, username," +
" password, email, email_validation,phone_number,phone_number_validation) " +
"values($1, $2, $3, $4, $5, $6, $7)")
if err != nil {
return err
}
emailValidationJson, err := json.Marshal(account.Authentication.Local.EmailValidation)
if err != nil {
return err
}
phoneValidationJson, err := json.Marshal(account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return err
}
_, err = insertAccountAuthStmt.Exec(account.ID,
account.Authentication.Local.Username,
account.Authentication.Local.Password,
account.Authentication.Local.Email,
emailValidationJson,
account.Authentication.Local.PhoneNumber,
phoneValidationJson)
if err != nil {
return err
}
return nil
}
func (psql PostgresqlStorage) UpdateAccount(account Account) error {
updateAccountStmt, err := psql.DbConnection.Prepare("update accounts set namespace=$1, data=$2, " +
" metadata=$3 where id= $4")
if err != nil {
return err
}
dataAccountJson, err := json.Marshal(account.Data)
if err != nil {
return err
}
metadataAccountJson, err := json.Marshal(account.Metadata)
if err != nil {
return err
}
_, err = updateAccountStmt.Exec(account.Namespace, dataAccountJson, metadataAccountJson, account.ID)
if err != nil {
return err
}
updateAccountAuthStmt, err := psql.DbConnection.Prepare("update account_auth set username = $1," +
" password = $2," +
" email = $3, email_validation = $4 ," +
"phone_number = $5,phone_number_validation = $6 where account_id = $7")
if err != nil {
return err
}
emailValidationJson, err := json.Marshal(account.Authentication.Local.EmailValidation)
if err != nil {
return err
}
phoneValidationJson, err := json.Marshal(account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return err
}
_, err = updateAccountAuthStmt.Exec(account.Authentication.Local.Username,
account.Authentication.Local.Password,
account.Authentication.Local.Email,
emailValidationJson,
account.Authentication.Local.PhoneNumber,
phoneValidationJson, account.ID)
if err != nil {
return err
}
return nil
}

369
storage/postgresql_test.go Normal file
View File

@@ -0,0 +1,369 @@
package storage
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/spf13/viper"
"reflect"
"testing"
)
// Tests must be run in order
// Table creation:
/* #####################################################################################################################
CREATE TABLE accounts (
id TEXT PRIMARY KEY,
namespace TEXT,
data JSONB,
metadata JSONB
);
CREATE TABLE account_auth (
account_id TEXT PRIMARY KEY,
username TEXT,
password TEXT,
email TEXT,
email_validation JSONB,
phone_number TEXT,
phone_number_validation JSONB,
FOREIGN KEY (account_id) REFERENCES accounts(id)
);
#######################################################################################################################
*/
/* Inserting an Account: (Password = test)
#######################################################################################################################
INSERT INTO accounts (id, namespace, data, metadata)
VALUES ('2faa137b-27be-476f-b98c-8b7eed6f1f3a', 'parcoursmob', '{"email": "salimbouaram12@gmail.com", "gender": "9",
"groups": ["483280d0-db2d-4f06-b361-02e4be5012d2", "483280d0-db2d-4f06-b361-02e4be5012d2:admin"], "last_name": "salim",
"first_name": "salim", "display_name": "salim salim", "phone_number": ""}', '{"created": "2023-04-24T09:29:18.262+00:00"}');
#######################################################################################################################
INSERT INTO account_auth (account_id, username, password, email, email_validation, phone_number, phone_number_validation)
VALUES ('2faa137b-27be-476f-b98c-8b7eed6f1f3a', 'salim-amine.bou-aram@coopgo.fr',
'$2a$10$j9LwkGYT6HhLpWxUvpEniOJ3nBKEhwAn52G.t4QYMgje4HnJuWqHK', 'salim-amine.bou-aram@coopgo.fr',
'{"validated": false, "validation_code": ""}', '0749331953', '{"validated": false, "validation_code": ""}');
#######################################################################################################################
*/
var cfg *viper.Viper
func init() {
cfg = viper.New()
cfg.Set("storage.db.psql.host", "localhost")
cfg.Set("storage.db.psql.port", "5432")
cfg.Set("storage.db.psql.user", "postgres")
cfg.Set("storage.db.psql.password", "postgres")
cfg.Set("storage.db.psql.dbname", "mobilityaccounts")
}
func TestNewPostgresqlStorage(t *testing.T) {
storage, err := NewPostgresqlStorage(cfg)
storage.DbConnection.Exec("Delete from account_auth ;")
storage.DbConnection.Exec("Delete from accounts ;")
if err != nil {
t.Errorf("error creating new PostgreSQL storage: %v", err)
}
defer storage.DbConnection.Close()
}
func generateUUIDv4() string {
uuid := make([]byte, 16)
_, err := rand.Read(uuid)
if err != nil {
panic(err)
}
uuid[6] = (uuid[6] & 0x0f) | 0x40
uuid[8] = (uuid[8] & 0xbf) | 0x80
return hex.EncodeToString(uuid[:4]) + "-" + hex.EncodeToString(uuid[4:6]) + "-" +
hex.EncodeToString(uuid[6:8]) + "-" + hex.EncodeToString(uuid[8:10]) + "-" +
hex.EncodeToString(uuid[10:])
}
func TestGetAccount(t *testing.T) {
// Open a database connection
db, err := NewPostgresqlStorage(cfg)
Id := generateUUIDv4()
if err != nil {
t.Errorf("failed to create new psql connection")
}
// Insert data into accounts table
accountData := map[string]any{
"key1": "value1",
"key2": "value2",
}
accountMetadata := map[string]any{
"key1": "value1",
"key2": "value2",
}
account := Account{
ID: Id,
Namespace: "test_namespace",
Data: accountData,
Metadata: accountMetadata,
}
dataJSON, err := json.Marshal(map[string]any{
"key1": "value1",
"key2": "value2",
})
if err != nil {
t.Errorf("error account data and metdata")
}
_, err = db.DbConnection.Exec("INSERT INTO accounts (id, namespace, data, metadata) "+
"VALUES ($1, $2, $3, $4)", account.ID, account.Namespace, dataJSON, dataJSON)
if err != nil {
t.Errorf("error in inserting a new account")
}
// Insert data into account_auth table
emailValidation := Validation{
Validated: true,
ValidationCode: "code",
}
localAuth := LocalAuth{
Username: "testuser",
Password: "testpassword",
Email: "test@test.com",
EmailValidation: emailValidation,
PhoneNumber: "1234567890",
PhoneNumberValidation: emailValidation,
}
localAuthJSON, err := json.Marshal(emailValidation)
if err != nil {
t.Errorf("error account_auth localAuth")
}
_, err = db.DbConnection.Exec("INSERT INTO account_auth (account_id, username, password, "+
"email, email_validation, "+
"phone_number,phone_number_validation) VALUES ($1, $2, $3, $4, $5, $6, $7)", account.ID,
localAuth.Username, localAuth.Password, localAuth.Email, localAuthJSON, localAuth.PhoneNumber, localAuthJSON)
if err != nil {
fmt.Println(err)
t.Errorf("error in iserting a new account in account_auth")
}
account_, err := db.GetAccount(Id)
if err != nil {
t.Errorf("failed")
fmt.Println(err)
}
expectedAccount := &Account{
ID: Id,
Namespace: "test_namespace",
Data: accountData,
Metadata: accountMetadata,
Authentication: AccountAuth{
Local: localAuth,
},
}
if reflect.DeepEqual(account_, expectedAccount) {
fmt.Println("PASS")
} else {
t.Errorf("The received account is not the same as expected")
}
}
func TestPostgresqlStorage_CreateAccount(t *testing.T) {
db, err := NewPostgresqlStorage(cfg)
Id := generateUUIDv4()
if err != nil {
t.Errorf("failed to create new psql connection")
}
emailValidation := Validation{
Validated: true,
ValidationCode: "code",
}
localAuth := LocalAuth{
Username: "salim",
Password: "testpassword",
Email: "test@test.com",
EmailValidation: emailValidation,
PhoneNumber: "1234567890",
PhoneNumberValidation: emailValidation,
}
accountData := map[string]any{
"key1": "value1",
"key2": "value2",
}
accountMetadata := map[string]any{
"key1": "value1",
"key2": "value2",
}
account := Account{
ID: Id,
Namespace: "namespace",
Authentication: AccountAuth{
Local: localAuth,
},
Data: accountData,
Metadata: accountMetadata,
}
err = db.CreateAccount(account)
if err != nil {
fmt.Println(err)
t.Errorf("Failed to create account")
}
}
func TestPostgresqlStorage_UpdateAccount(t *testing.T) {
db, err := NewPostgresqlStorage(cfg)
Id := generateUUIDv4()
if err != nil {
t.Errorf("failed to create new psql connection")
}
emailValidation := Validation{
Validated: true,
ValidationCode: "code",
}
localAuth := LocalAuth{
Username: "salim",
Password: "testpassword",
Email: "test@test.com",
EmailValidation: emailValidation,
PhoneNumber: "1234567890",
PhoneNumberValidation: emailValidation,
}
accountData := map[string]any{
"key1": "value1",
"key2": "value2",
}
accountMetadata := map[string]any{
"key1": "value1",
"key2": "value2",
}
account := Account{
ID: Id,
Namespace: "test_namespace",
Authentication: AccountAuth{
Local: localAuth,
},
Data: accountData,
Metadata: accountMetadata,
}
err = db.CreateAccount(account)
if err != nil {
fmt.Println(err)
t.Errorf("Failed to create account")
}
account2 := Account{
ID: Id,
Namespace: "salim",
Authentication: AccountAuth{
Local: LocalAuth{
Username: "salim",
Password: "salim",
Email: "salim@test.com",
EmailValidation: Validation{
Validated: false,
ValidationCode: "123",
},
PhoneNumber: "12345",
PhoneNumberValidation: Validation{
Validated: true,
ValidationCode: "1233",
},
},
},
Data: map[string]any{
"key1": "salim1",
"key2": "salim2",
},
Metadata: map[string]any{
"key1": "salim1",
"key2": "salim2",
},
}
err = db.UpdateAccount(account2)
if err != nil {
fmt.Println(err)
t.Errorf("failed")
}
}
func TestPostgresqlStorage_LocalAuthentication(t *testing.T) {
db, err := NewPostgresqlStorage(cfg)
if err != nil {
t.Errorf("failed to create new psql connection")
}
accountByUsername, err := db.LocalAuthentication("test_namespace", "testuser",
"", "")
if err != nil {
t.Errorf("Failed LocalAuthentication based on username and namespace")
}
fmt.Println(accountByUsername)
accountByEmail, err := db.LocalAuthentication("test_namespace", "",
"test@test.com", "")
if err != nil {
t.Errorf("Failed LocalAuthentication based on username and namespace")
}
fmt.Println(accountByEmail)
accountByPhone, err := db.LocalAuthentication("test_namespace", "",
"", "1234567890")
if err != nil {
t.Errorf("Failed LocalAuthentication based on username and namespace")
}
fmt.Println(accountByPhone)
accountByAll, err := db.LocalAuthentication("test_namespace", "testuser",
"test@test.com", "1234567890")
if err != nil {
t.Errorf("Failed LocalAuthentication based on username and namespace")
}
fmt.Println(accountByAll)
}
func TestPostgresqlStorage_GetAccounts(t *testing.T) {
db, err := NewPostgresqlStorage(cfg)
if err != nil {
t.Errorf("failed to create new psql connection")
}
accounts, err := db.GetAccounts([]string{"test_namespace", "salim", "namespace"})
if err != nil {
t.Errorf("Failed")
}
for _, account := range accounts {
fmt.Println(account)
}
}
func TestPostgresqlStorage_GetAccountsByIds(t *testing.T) {
db, err := NewPostgresqlStorage(cfg)
if err != nil {
t.Errorf("failed to create new psql connection")
}
account := Account{
ID: "772315f1-8113-486a-90c7-9073410065bd",
Namespace: "oo",
Authentication: AccountAuth{
Local: LocalAuth{
Username: "username",
Password: "password",
Email: "salim@test.com",
EmailValidation: Validation{
Validated: true,
ValidationCode: "123",
},
PhoneNumber: "12345",
PhoneNumberValidation: Validation{
Validated: true,
ValidationCode: "1233",
},
},
},
Data: map[string]any{
"key1": "salim1",
"key2": "salim2",
},
Metadata: map[string]any{
"key1": "salim1",
"key2": "salim2",
},
}
err = db.CreateAccount(account)
if err != nil {
t.Errorf("Failed to create account")
}
accounts, err := db.GetAccountsByIds([]string{"772315f1-8113-486a-90c7-9073410065bd"})
if err != nil {
t.Errorf("Failed to get account by ID")
}
for _, acc := range accounts {
fmt.Println(acc)
}
}

3
storage/storage.go Normal file → Executable file
View File

@@ -46,6 +46,9 @@ func NewDBStorage(cfg *viper.Viper) (DBStorage, error) {
case "mongodb":
s, err := NewMongoDBStorage(cfg)
return s, err
case "psql":
s, err := NewPostgresqlStorage(cfg)
return s, err
default:
return nil, fmt.Errorf("storage type %v is not supported", storage_type)
}