// created.. 4.26.2024 ~q
#include <Preferences.h>
#include <vector>
const char* TAG = "UserManagement";
//user data structure
typedef struct user {
char userName[20];
uint32_t hashPass;
byte userLevel;
};
//.vector of users
std::vector<user> users;
//nvm storage
Preferences prefs;
void setup() {
Serial.begin(115200);
Serial.println("User Management");
Serial.println();
Serial.println("Creating 2 users..");
//hash a password..
uint32_t ph = hash("1234");
//make a couple of new users..
user newUser = {"Joe", ph, 99};
addUser(newUser);
//gonna re-use, just change the name
strcpy(newUser.userName, "Mike");
addUser(newUser);
//should have 2..
Serial.print("User count:");
//users.size() == user count
Serial.println(users.size());
// test loading em back in..
//generally you do this once during setup..
Serial.println("Reloading..");
loadUsers();
//should have 2 again..
Serial.print("User count:");
Serial.println(users.size());
//check for unique user names..
Serial.println("Attempting to add Mike again");
if (!addUser(newUser)) {
Serial.println("Good, we can't..");
} else {
Serial.println("Wrong, should not be able to add..");
}
//should have 2..
Serial.print("User count:");
//users.size() == user count
Serial.println(users.size());
// find Mike??
int idx = findUser("Mike");
if ( idx > -1 ) {
Serial.print("Found usename:");
Serial.println(users[idx].userName);
// check password by hashing what they enter..
ph = hash("4321");// bad one
if (users[idx].hashPass != ph) {
Serial.println("Password is not: 4321");
}
ph = hash("1234");//good one
if (users[idx].hashPass == ph) {
Serial.println("Password is : 1234");
}
}
//check user level functions..
Serial.print("Mike's user level :");
byte ul = getUserLevel("Mike");
Serial.println(ul);
Serial.println("Changing Mike's level to 1");
if (setUserLevel("Mike", 1)) {
Serial.print("Level changed to :");
ul = getUserLevel("Mike");
Serial.println(ul);
}
//delete a user..
if (delUser("Mike")) {
Serial.println("Mike deleted..");
}
//should be 1 now..
Serial.print("User count:");
Serial.println(users.size());
//reload everything to make sure..
Serial.println("Reloading..");
loadUsers();
//should now be 1..
Serial.print("User count:");
Serial.println(users.size());
//delete Joe, the last user..
if (delUser("Joe")) {
Serial.println("Joe deleted..");
}
//should be 0
Serial.print("User count:");
Serial.println(users.size());
//reload to check for still 0
Serial.println("Reloading..");
loadUsers();
//should be 0
Serial.print("User count:");
Serial.println(users.size());
//have fun..
}
void loop() {
delay(1000);
}
// returns index in vector to user else -1 if not found..
int findUser(const char* userName) {
int result = -1;
if (users.size() == 0) return result;
for ( int i = 0; i < users.size(); i++) {
if (strcmp(userName, users[i].userName) == 0) {
result = i;
break;
}
}
return result;
}
//returns -1 if not found else user level
int getUserLevel(const char* userName) {
int result = -1;
if (users.size() == 0) return result;
for ( int i = 0; i < users.size(); i++) {
if (strcmp(userName, users[i].userName) == 0) {
result = users[i].userLevel;
break;
}
}
return result;
}
// changes user level for a user name, returns true if it saves..
bool setUserLevel(const char* userName, const byte newLevel) {
bool result = false;
if (users.size() == 0) return result;
for ( int i = 0; i < users.size(); i++) {
if (strcmp(userName, users[i].userName) == 0) {
users[i].userLevel = newLevel;
result = saveUsers();
break;
}
}
return result;
}
// returns true if user was added
bool addUser(user newUser) {
bool result = false;
//check for unique user name
if (findUser(newUser.userName) < 0) {
users.push_back(newUser);
//save to prefs..
result = saveUsers();
}
return result;
}
// returns true if user deleted..
bool delUser(const char* userName) {
bool result = false;
int pos = findUser(userName);
if (pos < 0) return result;
if (users.size() == 1) {
users.clear();
} else {
users.erase(users.begin() + pos);
}
//save to prefs
result = saveUsers();
return result;
}
// saves all users..
bool saveUsers() {
bool result = false;
prefs.begin(TAG);
if (users.size() < 1) {
//remove all users..
if (prefs.getBytesLength("_users_") > 0) {
result = prefs.remove("_users_");
} else result = true;//already gone..
} else {
//saving some users..
int totalSize = users.size() * sizeof(user);
int s = prefs.putBytes("_users_", users.data(), totalSize);
if (s == totalSize) result = true;
}
prefs.end();
return result;
}
// loads all users..
int loadUsers() {
int result = 0;
users.clear();
prefs.begin(TAG);
int numUsers = prefs.getBytesLength("_users_");
if (numUsers > 0) numUsers = numUsers / sizeof(user);
if (numUsers > 0) {
user tmpUsers[numUsers];
int loadedBytes = prefs.getBytes("_users_", tmpUsers, numUsers * sizeof(user));
if (loadedBytes == (numUsers * sizeof(user))) {
users.insert(users.begin(), tmpUsers, tmpUsers + numUsers);
result = users.size();
}
}
prefs.end();
return result;
}
//djb2 - Hashing function, the hash is stored instead of password..
// password entered is then hashed and resulting hash is saved/compared..
// remember we don't spefically need to know the actual password..
// we just need to authenticate it..
uint32_t hash(char *str) {
uint32_t hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return hash;
}