change password program

Have to create a database that contains names, usernames and previous passwords, and hardcode that in my program. Then log into the program using one of the users and passwords and change the current password to a different one meeting requirements.

My plan is to use a txt.file for the database to contain the users and passwords.
Then create a function to log in comparing the stored data against user input. A function to change the password making sure it meets the requirements, and a function to exit the program.

Before I get deep in coding I wanted to know if my approach is sound, or if there is a more effiecient, and or easier way to complete the program.
Hello Felipe08,

Sounds like a good start.

I would suggest posting a sample of the input file, so everyone will have an idea of what you are working with.

It sounds like the input file has 3 fields to work with.

One approach would be to create a struct and then a vector of structs, if you have covered vectors, otherwise an array will work.

You might also use a "std::map" or "std::list".

After you decide how to deal with the data consider reading the entire file into the program so you do not have to read the file each time that you need it. I find that reading the file into something to be easier to work with.

You can create a "bool" variable and set it to true if a change is made then check it before the program ends and write everything to the old file.

Andy
Andy

I actually created a vector to store the name, username, and previous passwords.

example. vector<vector<string>> database = {mike, user01, Mj23%, BigBang01@, ATL2008&}

My question is would that conflict with writing the new password to the database once the user creates a new one that fits the requirements?

I understand this is an assignment and the requirements are contrived, but in real life your code must never store passwords.
Hello Felipe08,

I take it that 1 line in the file would look like:

mike,user01,Mj23%,BigBang01@,ATL2008&


Creating a 2D vector would work. To add a new password and keep the rest insert the new password at the beginning of the passwords. Then when you write the vector to the file it will have all the passwords.

Not knowing what you have done, post what code you do have, I have been working with a struct and a vector of those structs. The problem is that I did not understand about the previous passwords.

Using the 2D vector and inserting the new password in the right place should not be a problem writing to a new file.

Reading the file would be some extra work since the previous passwords would contain different quantities. You could a string stream for this part.

Andy
A possible data struct could be:

1
2
3
4
5
6
7
struct UserData {
	std::string name;
	std::string curpass;
	std::set<std::string> prevpass;
};

std::map<std::string, UserData> Users;	// Key is userid 


This would allow easy checking for valid/invalid userid, duplicate userid and duplicate psswords.
Consider for a starter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#include <iostream>
#include <string>
#include <fstream>
#include <set>
#include <map>
#include <sstream>
#include <iomanip>

struct UserData {
	std::string name;
	std::string curpass;
	std::set<std::string> prevpass;
};

using Users = std::map<std::string, UserData>;

const std::string userfile {"users.txt"};

// Format of file is
//userid,name,curpass,oldpass,...

// true OK, false bad
bool readUsers(Users& users)
{
	std::ifstream ifs(userfile);

	if (!ifs)
		return false;

	for (std::string line; std::getline(ifs, line); ) {
		UserData user;
		std::string id;
		std::istringstream iss(line);

		std::getline(iss, id, ',');
		std::getline(iss, user.name, ',');
		std::getline(iss, user.curpass, ',');

		for (std::string pass; std::getline(iss, pass, ','); user.prevpass.insert(pass));

		// Assume data on file is correct - no duplicates etc
		users[id] = user;
	}

	return true;
}

// true OK, false bad
bool writeUsers(const Users& users)
{
	std::ofstream ofs(userfile);

	if (!ofs)
		return false;

	for (const auto& user : users) {
		ofs << user.first << ',' << user.second.name << ',' << user.second.curpass << ',';

		for (size_t pasno = 0; const auto& pass : user.second.prevpass) {
			if (pasno++)
				ofs << ',';

			ofs << pass;
		}

		ofs << '\n';
	}
        return true;
}

//true OK, false bad
bool checkPass(const UserData& user, const std::string& pass)
{
	if (pass.size() < 6)
		return false;

	// INCLUDE HERE OTHER REQUIRED TESTS FOR VALID PASSWORD

	return (pass != user.curpass) && !user.prevpass.count(pass);
}

void addUser(Users& users)
{
	std::string id, pass;

	UserData data;

	std::cout << "Enter userid: ";
	std::getline(std::cin >> std::ws, id);

	if (users.count(id))
		std::cout << "Id already exists.\n";
	else {
		std::cout << "Enter name: ";
		std::getline(std::cin, data.name);

		do {
			std::cout << "Enter password: ";
			std::getline(std::cin >> std::ws, pass);
		} while (!checkPass(data, pass) && (std::cout << "Invalid password\n"));

		data.curpass = pass;
		users[id] = data;
	}
}

void changePass(Users& users)
{
	std::string id, pass;

	std::cout << "Enter userid: ";
	std::getline(std::cin >> std::ws, id);

	if (auto iditr {users.find(id)}; iditr == users.end())
		std::cout << "Id does not exist\n";
	else {
		do {
			std::cout << "Enter new password: ";
			std::getline(std::cin >> std::ws, pass);
		} while (!checkPass(iditr->second, pass) && (std::cout << "Invalid password\n"));

		iditr->second.prevpass.insert(iditr->second.curpass);
		iditr->second.curpass = pass;
	}
}

// true OK, false bad
bool check(const Users& users, const std::string& id, const std::string& pass)
{
	if (const auto itr {users.find(id)}; itr != users.end())
		if (itr->second.curpass == pass)
			return true;

	return false;
}

int main() {
	Users users;

	unsigned opt {};

	do {
		std::cout << "\n1. Read users\n";
		std::cout << "2. Write users\n";
		std::cout << "3. Add user\n";
		std::cout << "4. Delete user\n";
		std::cout << "5. Change password\n";
		std::cout << "6. Check userid/password\n";
		std::cout << "0. Exit\n";

		std::cout << "\nEnter options: ";
		std::cin >> opt;

		switch (opt) {
			case 1:
				if (!readUsers(users))
					std::cout << "Problem reading users\n";

				break;

			case 2:
				if (!writeUsers(users))
					std::cout << "Problem writing users\n";

				break;

			case 3:
				addUser(users);
				break;

			case 4:
				std::cout << "To do!\n";
				break;

			case 5:
				changePass(users);
				break;

			case 6:
			{
				std::string id, pass;

				std::cout << "Enter id: ";
				std::getline(std::cin >> std::ws, id);

				std::cout << "Password: ";
				std::getline(std::cin >> std::ws, pass);
				if (check(users, id, pass))
					std::cout << "OK\n";
				else
					std::cout << "Invalid\n";
			}
				break;

			case 0:
				break;

			default:
				std::cout << "Invalid option\n";
		}
	} while (opt);
}


Last edited on
Hello Felipe08,

Using your 2D vector I did manage to come with something that produces this output:

                    USER      PASS
 NAME               NAME      WORD       PREVIOUS PASS WORDS
----------------------------------------------------------------------
Etienne Navarre     Wolf      Hawk      Mj23% BigBang01@ ATL2008&
Isabeau d'Anjou     Hawk      Wolf      BigBang01@
Father Imperius     Biship    New#      temp
Saphira Dragon      temp      Eragon    ATL2008& Mj23% BigBang01@ ATL2008&
Arya Dröttningu     Arya      Horse

 Please enter your user name (case sensistive): wolf

 Please enter your password (case sensistive): hawk

     User name and/or password did not match.


 Press Enter to continue:


 
                    USER      PASS
 NAME               NAME      WORD       PREVIOUS PASS WORDS
----------------------------------------------------------------------
Etienne Navarre     Wolf      Hawk      Mj23% BigBang01@ ATL2008&
Isabeau d'Anjou     Hawk      Wolf      BigBang01@
Father Imperius     Biship    New#      temp
Saphira Dragon      temp      Eragon    ATL2008& Mj23% BigBang01@ ATL2008&
Arya Dröttningu     Arya      Horse

 Please enter your user name (case sensistive): Wolf

 Please enter your password (case sensistive): Hawk

     Login sucessful


 Press Enter to continue:


The print function for the first part is optional. I just used it to print out the vector for testing.

Andy
Here is an example of the code so far.
Still a work in progress


<vector<string>> database = {
{"Etienne Navarre", "Wolf" , "Hawk", "Mj23%", "BigBang01@", "ATL2008&"},
{"Isabeau d'Anjou", "Hawk", "Wolf", "BigBang01@"},
{"Father Imperius", "Biship", "New#", "temp"},
{"Saphira Dragon", "temp", "Eragon", "ATL2008&", "Mj23%", "BigBang01@", "ATL2008&"}.
{"Arya Dröttningu", "Arya", "Horse"}};

int getMenuChoice()
{
int choice;
cout << "\n-------MENU-------\n";
cout << "1. Log in\n";
cout << "2. Change Password\n";
cout << "3. Exit\n";
cout << "\nYour choice -> ";
cin >> choice;
return choice;
}

void login()
{
string username, password;
cout << "Enter the username: ";
cin >> username;
cout << "Enter the password: ";
cin >> password;

int userIndex = -1;
for (int i = 0; i < database.size(); i++)
{
if (username == database[i][1] && password == database[i][4])
{
userIndex = i;
break;
}
}
if (userIndex != -1)
cout << "\nLOGIN SUCCESSFUL\n";
else
cout << "\nINVALID USERNAME OR PASSWORD\n";
}

bool checkPassValidity(string p)
{
int l = p.length();
int lowerCount = 0, upperCount = 0;
for (int i = 0; i < l; i++)
{
if (isupper(p[i]))
upperCount++;
if (islower(p[i]))
lowerCount++;
}
if (l >= 6 && lowerCount >= 1 && upperCount >= 1)
return true;
if (l < 6)
cout << "\nThe password length must be atleast 6 characters!\n";
if (upperCount < 1)
cout << "\nThe password must contain at least 1 uppercase character!\n";
if (lowerCount < 1)
cout << "\nThe password must contain at least 1 lowercase character!\n";

return false;
}

void changePassword()
{
string username, password;
cout << "Enter the username: ";
cin >> username;
cout << "Enter the password: ";
cin >> password;

int userIndex = -1;
for (int i = 0; i < database.size(); i++)
{
if (username == database[i][1] && password == database[i][4])
{
userIndex = i;
break;
}
}
if (userIndex != -1)
{
string newPassword;
cout << "\nLOGIN SUCCESSFUL\n";
while (true)
{
cout << "\nEnter new password:";
cin >> newPassword;
if (checkPassValidity(newPassword))
break;
}
database[userIndex][4] = newPassword;
}
else
cout << "\nINVALID USERNAME OR PASSWORD\n";
}
// driver function
int main()
{
while (true)
{
int choice = getMenuChoice();
switch (choice)
{
case 1:
login();
break;
case 2:
changePassword();
break;
case 3:
cout << "\nThank You!\n";
exit(1);
break;
default:
cout << "\nINVALID CHOICE!\n";
break;
}
}
return 0;
}
Hello Felipe08,


PLEASE ALWAYS USE CODE TAGS (the <> formatting button), to the right of this box, when posting code.

Along with the proper indenting it makes it easier to read your code and also easier to respond to your post.

http://www.cplusplus.com/articles/jEywvCM9/
http://www.cplusplus.com/articles/z13hAqkS/

Hint: You can edit your post, highlight your code and press the <> formatting button. This will not automatically indent your code. That part is up to you.

You can use the preview button at the bottom to see how it looks.

I found the second link to be the most help.


Maybe I misunderstood, but I had the impression that you wanted to read from a file.

When posting code it is best to post a complete program that can be compiled and tested. It does not always have to be the whole program, but at least enough to demonstrate what you did.

I will take a look at your code and see what I can find.

Andy
[When posting code, use code tags so that the code is formatted and easier to read]

It is easier if you have the userid first then the name in the database. My code above will allow admin database maintenance and also login.

An updated version to also add additional password checks and to display the userid info:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#include <iostream>
#include <string>
#include <fstream>
#include <set>
#include <map>
#include <sstream>
#include <iomanip>

struct UserData {
	std::string name;
	std::string curpass;
	std::set<std::string> prevpass;
};

using Users = std::map<std::string, UserData>;

const std::string userfile {"users.txt"};

// Format of file is
//userid,name,curpass,oldpass,...

// true OK, false bad
bool readUsers(Users& users)
{
	std::ifstream ifs(userfile);

	if (!ifs)
		return false;

	for (std::string line; std::getline(ifs, line); ) {
		UserData user;
		std::string id;
		std::istringstream iss(line);

		std::getline(iss, id, ',');
		std::getline(iss, user.name, ',');
		std::getline(iss, user.curpass, ',');

		for (std::string pass; std::getline(iss, pass, ','); user.prevpass.insert(pass));

		// Assume data on file is correct - no duplicates etc
		users[id] = user;
	}

	return true;
}

// true OK, false bad
bool writeUsers(const Users& users)
{
	std::ofstream ofs(userfile);

	if (!ofs)
		return false;

	for (const auto& user : users) {
		ofs << user.first << ',' << user.second.name << ',' << user.second.curpass << ',';

		for (size_t pasno = 0; const auto & pass : user.second.prevpass) {
			if (pasno++)
				ofs << ',';

			ofs << pass;
		}

		ofs << '\n';
	}
        return true;
}

//true OK, false bad
bool checkPass(const UserData& user, const std::string& pass)
{
	// INCLUDE HERE OTHER REQUIRED TESTS FOR VALID PASSWORD

	size_t lowerCount {}, upperCount {};

	for (const auto& ch : pass) {
		if (isupper(static_cast<unsigned char>(ch)))
			upperCount++;

		if (islower(static_cast<unsigned char>(ch)))
			lowerCount++;
	}

	if (pass.size() < 6 || lowerCount == 0 || upperCount == 0)
		return false;

	return (pass != user.curpass) && !user.prevpass.count(pass);
}

void addUser(Users& users)
{
	std::string id, pass;

	UserData data;

	std::cout << "Enter userid: ";
	std::getline(std::cin >> std::ws, id);

	if (users.count(id))
		std::cout << "Id already exists.\n";
	else {
		std::cout << "Enter name: ";
		std::getline(std::cin, data.name);

		do {
			std::cout << "Enter password: ";
			std::getline(std::cin >> std::ws, pass);
		} while (!checkPass(data, pass) && (std::cout << "Invalid password\n"));

		data.curpass = pass;
		users[id] = data;
	}
}

void changePass(Users& users)
{
	std::string id, pass;

	std::cout << "Enter userid: ";
	std::getline(std::cin >> std::ws, id);

	if (auto iditr {users.find(id)}; iditr == users.end())
		std::cout << "Id does not exist\n";
	else {
		do {
			std::cout << "Enter new password: ";
			std::getline(std::cin >> std::ws, pass);
		} while (!checkPass(iditr->second, pass) && (std::cout << "Invalid password\n"));

		iditr->second.prevpass.insert(iditr->second.curpass);
		iditr->second.curpass = pass;
	}
}

// true OK, false bad
bool check(const Users& users, const std::string& id, const std::string& pass)
{
	if (const auto itr {users.find(id)}; itr != users.end())
		if (itr->second.curpass == pass)
			return true;

	return false;
}

void displayUsers(const Users& users)
{
	std::cout << std::right << std::setw(24) << "USER" << std::setw(16) << "PASS\n";
	std::cout << std::left << std::setw(20) << "NAME" << std::setw(15) << "NAME" << std::setw(10) << "WORD" << " PREVIOUS PASSWORDS\n";
	std::cout << std::setw(75) << std::setfill('-') << '-' << std::setfill(' ') << '\n';

	for (const auto& user : users) {
		std::cout << std::setw(20) << user.second.name << std::setw(15) << user.first << std::setw(10) << user.second.curpass;
		for (const auto& old : user.second.prevpass)
			std::cout << " " << old;

		std::cout << '\n';
	}

	std::cout << '\n';
}

int main() {
	Users users;

	unsigned opt {};

	do {
		std::cout << "\n1. Read users\n";
		std::cout << "2. Write users\n";
		std::cout << "3. Add user\n";
		std::cout << "4. Delete user\n";
		std::cout << "5. Change password\n";
		std::cout << "6. Login\n";
		std::cout << "7. Display all users\n";
		std::cout << "0. Exit\n";

		std::cout << "\nEnter options: ";
		std::cin >> opt;

		switch (opt) {
			case 1:
				if (!readUsers(users))
					std::cout << "Problem reading users\n";

				break;

			case 2:
				if (!writeUsers(users))
					std::cout << "Problem writing users\n";

				break;

			case 3:
				addUser(users);
				break;

			case 4:
				std::cout << "To do!\n";
				break;

			case 5:
				changePass(users);
				break;

			case 6:
			{
				std::string id, pass;

				std::cout << "Enter id: ";
				std::getline(std::cin >> std::ws, id);

				std::cout << "Password: ";
				std::getline(std::cin >> std::ws, pass);
				if (check(users, id, pass))
					std::cout << "OK\n";
				else
					std::cout << "Invalid\n";
			}
			break;

			case 7:
				displayUsers(users);
				break;

			case 0:
				break;

			default:
				std::cout << "Invalid option\n";
		}
	} while (opt);
}


Given the users.txt file:


id1,qwerty,lkjhgf,asdfgh,poiuyt,qwerty,zxcvbn
Wolf,Etienne Navarre,Hawk,Mj23%,BigBang01@,ATL2008&
Hawk,Isabeau d'Anjou,Wolf,BigBang01@
Biship,Father Imperius,New#,temp
temp,Saphira Dragon,Eragon,ATL2008&,Mj23%,BigBang01@,ATL2008&
Arya,Arya Dröttningu,Horse


Option 7 displays:


Enter options: 7
                    USER           PASS
NAME                NAME           WORD       PREVIOUS PASSWORDS
---------------------------------------------------------------------------
Arya Dr÷ttningu     Arya           Horse
Father Imperius     Biship         New#       temp
Isabeau d'Anjou     Hawk           Wolf       BigBang01@
Etienne Navarre     Wolf           Hawk       ATL2008& BigBang01@ Mj23%
qwerty              id1            lkjhgf     asdfgh poiuyt qwerty zxcvbn
Saphira Dragon      temp           Eragon     ATL2008& BigBang01@ Mj23%


Last edited on
Hello Felipe08,

A quick rework of your code I have this. The comments in the code should help:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// <--- Most comon includes.
#include <iostream>
#include <iomanip>
#include <limits>
#include <string>
#include <vector>
#include <cctype>

//<vector<string>> database = 
std::vector<std::vector<std::string>> database
{
    {"Etienne Navarre", "Wolf" , "Hawk", "Mj23%", "BigBang01@", "ATL2008&"},
    {"Isabeau d'Anjou", "Hawk", "Wolf", "BigBang01@"},
    {"Father Imperius", "Biship", "New#", "temp"},
    {"Saphira Dragon", "temp", "Eragon", "ATL2008&", "Mj23%", "BigBang01@", "ATL2008&"},
    {"Arya Dröttningu", "Arya", "Horse"}
};

int getMenuChoice()
{
    int choice;

    std::cout <<
        "\n-------MENU-------\n"
        "1. Log in\n"
        "2. Change Password\n"
        "3. Exit\n"
        "\n  Your choice -> ";
    std::cin >> choice;

    return choice;
}

void login()
{
    std::string username, password;

    std::cout << "Enter the username: ";
    std::cin >> username;

    std::cout << "Enter the password: ";
    std::cin >> password;

    int userIndex = -1;

    for (int i = 0; i < database.size(); i++)
    {
        if (username == database[i][1] && password == database[i][4])  // <--- Accessing wrong element of the vector.
        {
            userIndex = i;
            break;
        }
    }

    if (userIndex != -1)
        std::cout << "\nLOGIN SUCCESSFUL\n";
    else
        std::cout << "\nINVALID USERNAME OR PASSWORD\n";
}

bool checkPassValidity(std::string p)
{
    int l = p.length();
    int lowerCount = 0, upperCount = 0;

    for (int i = 0; i < l; i++)
    {
        if (std::isupper(p[i]))
            upperCount++;

        if (std::islower(p[i]))
            lowerCount++;
    }

    if (l >= 6 && lowerCount >= 1 && upperCount >= 1)
        return true;

    if (l < 6)
        std::cout << "\nThe password length must be atleast 6 characters!\n";

    if (upperCount < 1)
        std::cout << "\nThe password must contain at least 1 uppercase character!\n";

    if (lowerCount < 1)
        std::cout << "\nThe password must contain at least 1 lowercase character!\n";

    return false;
}

void changePassword()
{
    std::string username, password;

    std::cout << "Enter the username: ";
    std::cin >> username;

    std::cout << "Enter the password: ";
    std::cin >> password;

    int userIndex = -1;

    for (int i = 0; i < database.size(); i++)
    {
        if (username == database[i][1] && password == database[i][4])
        {
            userIndex = i;

            break;
        }
    }

    if (userIndex != -1)
    {
        std::string newPassword;

        std::cout << "\nLOGIN SUCCESSFUL\n";

        while (true)
        {
            std::cout << "\nEnter new password:";
            std::cin >> newPassword;

            if (checkPassValidity(newPassword))
                break;
        }

        database[userIndex][4] = newPassword;
    }
    else
        std::cout << "\nINVALID USERNAME OR PASSWORD\n";
}

// driver function
int main()
{
    while (true)
    {
        int choice = getMenuChoice();

        switch (choice)
        {
            case 1:
                login();
                break;
            case 2:
                changePassword();
                break;
            case 3:
                std::cout << "\nThank You!\n";
                exit(1);  // <--- Do not use (exit). Use "return 0;"
                break;    // <--- Never reached.
            default:
                std::cout << "\nINVALID CHOICE!\n";
                break;
        }
    }

    return 0;  // <--- Not required, but makes a good break point for testing.
}

Line 9 may just be a highlight and copy problem. Not a big deal.

Lines 10 - 17 I find much easier to work with when using a 2D array of any type. Also you do not need the (=). The {}s is sufficient.

You should defining your vector as a global variable. Something that begins with "const" or "constexpr" is OK as it can not be changed. It is to easy for any line of code that follows to change something then it is hard to track down.

This vector should be defined in "main" and passed to the functions that need it.

In "getMenuChoice" you do not need a "cout" for every line. In the end each line in double quotes will be considered just 1 string. Otherwise you can use the insertion operator (<<) to chain several things together.

In line 48 "password == database[i][4]" is comparing the wrong element. C and C++ is a zero based language, so element 4 is either into "previous passwords" or beyond the end of the vector.

In the "checkPassValidity" function: first use some better names "p" may be quick and esay, but has no meaning. And the lower case "L" I thought it was a (1)one when I first say it And the uppercase "O" can sometimes be mistaken for a zero and vice versa.

You really do not need line 63. you could start the function with:
1
2
3
4
5
6
7
8
if (p.size() < 6)
{
    std::cout << "\nThe password length must be at least 6 characters!\n";

    return false;
}

int lowerCount{}, upperCount{};  // <--- do not need the (= 0). The empty {}s will set the variable to the proper type of (0)zero by the compiler. 

The ".size()" and ".length()" function both return the same number of type "size_t", which is an "unsigned integral" number which is implementation-defined. Have a look at https://en.cppreference.com/w/c/types/size_t

By doing the if statement first you can avoid unnecessarily checking something that you do not need to. It would also eliminate the 1st if statement.

Line 104 has the same problem as line 48.

Line 127 I believe you want to do an insert here and not a replace as written. Also you are accessing the wrong element.

In "main" in "case 3" "exit" is a C function and knows nothing about C++ classes or how to clean up a C++ program before it exits. It is an unconditional exit.

The while loop will work, but you should have a better way to exit the loop. May be something like:
1
2
3
bool done{};  // <--- initializes to false.

while (!done)

Then in "case 3" set "done" to true and exit the while loop and let the program end properly.

Or you could do the same with a do/while loop.

Andy
After further work (auto read database at start, write on exit, additional password checks, function split etc), consider as C++20:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#include <iostream>
#include <string>
#include <fstream>
#include <set>
#include <map>
#include <sstream>
#include <iomanip>
#include <limits>

struct UserData {
	std::string name;
	std::string curpass;
	std::set<std::string> prevpass;
};

using Users = std::map<std::string, UserData>;

enum Passerr {OK, UPPER, LOWER, LENGTH, DUPLICATE, LAST};

const std::string PassErrs[LAST] {"", "Insufficient upper case chars", "Insufficient lower case chars", "Not enough chars", "Duplicate"};
const std::string userfile {"users.txt"};

// Format of file is
//userid,name,curpass,oldpass,...

int getInt(const std::string& prm)
{
	int i {};

	while ((std::cout << prm) && (!(std::cin >> i) || std::cin.peek() != '\n')) {
		std::cout << "Not an integer\n";
		std::cin.clear();
		std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
	}

	std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
	return i;
}

// true OK, false bad
bool readUsers(Users& users)
{
	std::ifstream ifs(userfile);

	if (!ifs)
		return false;

	for (std::string line; std::getline(ifs, line); ) {
		UserData user;
		std::string id;
		std::istringstream iss(line);

		std::getline(iss, id, ',');
		std::getline(iss, user.name, ',');
		std::getline(iss, user.curpass, ',');

		const bool ok {iss};

		for (std::string pass; std::getline(iss, pass, ','); user.prevpass.insert(pass));

		// Assume data on file is correct - no duplicates etc
		if (ok)
			users[id] = user;
	}

	return true;
}

// true OK, false bad
bool writeUsers(const Users& users)
{
	std::ofstream ofs(userfile);

	if (!ofs)
		return false;

	for (const auto& user : users) {
		ofs << user.first << ',' << user.second.name << ',' << user.second.curpass << ',';

		for (size_t pasno = 0; const auto & pass : user.second.prevpass)
			ofs << (pasno++ ? "," : "") << pass;

		ofs << '\n';
	}

	return true;
}

//true OK, false bad
Passerr checkPass(const UserData& user, const std::string& pass)
{
	// INCLUDE HERE OTHER REQUIRED TESTS FOR VALID PASSWORD

	size_t lowerCount {}, upperCount {};

	for (const auto& ch : pass) {
		upperCount += isupper(static_cast<unsigned char>(ch));
		lowerCount += islower(static_cast<unsigned char>(ch));
	}

	if (pass.size() < 6)
		return LENGTH;

	if (lowerCount == 0)
		return LOWER;

	if (upperCount == 0)
		return UPPER;

	if ((pass == user.curpass) || user.prevpass.count(pass))
		return DUPLICATE;

	return OK;
}

auto getUser(Users& users, std::string& id)
{
	std::cout << "\nEnter userid: ";
	std::getline(std::cin, id);

	return users.find(id);
}

auto getUser(Users& users)
{
	std::string id;

	return getUser(users, id);
}

std::string getPass(const UserData& data)
{
	std::string pass;
	Passerr passerr;

	do {
		std::cout << "Enter password (CR to terminate): ";
		std::getline(std::cin, pass);
	} while (!pass.empty() && ((passerr = checkPass(data, pass)) != OK) && (std::cout << "Invalid password - " << PassErrs[passerr] << '\n'));

	return pass;
}

// true OK, false bad
bool addUser(Users& users)
{
	std::string id;

	if (const auto itr {getUser(users, id)}; itr != users.end()) {
		std::cout << "Id already exists.\n";
	} else {
		UserData data;

		std::cout << "Enter name: ";
		std::getline(std::cin, data.name);

		if (const auto pass {getPass(data)}; !pass.empty()) {
			data.curpass = pass;
			users[id] = data;
			return true;
		}
	}

	return false;
}

// true OK, false bad
bool changePass(Users& users)
{
	if (const auto iditr {getUser(users)}; iditr == users.end()) {
		std::cout << "Id does not exist\n";
	} else {
		if (const auto pass {getPass(iditr->second)}; !pass.empty()) {
			iditr->second.prevpass.insert(iditr->second.curpass);
			iditr->second.curpass = pass;
			return true;
		}
	}

	return false;
}

// true OK, false bad
bool login(const Users& users)
{
	const auto itr {getUser(const_cast<Users&>(users))};
	std::string pass;

	std::cout << "Password: ";
	std::getline(std::cin, pass);

	if (itr != users.end())
		if (itr->second.curpass == pass)
			return true;

	return false;
}

void displayUsers(const Users& users)
{
	std::cout << '\n' << std::right << std::setw(24) << "USER" << std::setw(16) << "PASS\n";
	std::cout << std::left << std::setw(20) << "NAME" << std::setw(15) << "NAME" << std::setw(10) << "WORD" << " PREVIOUS PASSWORDS\n";
	std::cout << std::setw(75) << std::setfill('-') << '-' << std::setfill(' ') << '\n';

	for (const auto& user : users) {
		std::cout << std::setw(20) << user.second.name << std::setw(15) << user.first << std::setw(10) << user.second.curpass;
		for (const auto& old : user.second.prevpass)
			std::cout << " " << old;

		std::cout << '\n';
	}

	std::cout << '\n';
}

// true OK, false bad
bool deleteUser(Users& users)
{
	if (const auto iditr {getUser(users)}; iditr == users.end()) {
		std::cout << "Id does not exist\n";
		return false;
	} else
		users.erase(iditr);

	return true;
}

int main() {
	Users users;
	unsigned opt {};

	if (!readUsers(users))
		std::cout << "Problem reading users\n";

	do {
		std::cout << "\n1. Login\n";
		std::cout << "2. Change password\n";
		std::cout << "\n3. Add user\n";
		std::cout << "4. Delete user\n";
		std::cout << "5. Display all users\n";
		std::cout << "0. Exit\n";

		switch (opt = getInt("\nEnter options: ")) {
			case 3:
				addUser(users);
				break;

			case 4:
				deleteUser(users);
				break;

			case 2:
				changePass(users);
				break;

			case 1:
				if (login(users))
					std::cout << "OK\n";
				else
					std::cout << "Invalid\n";

				break;

			case 5:
				displayUsers(users);
				break;

			case 0:
				if (!writeUsers(users))
					std::cout << "Problem writing users\n";

				break;

			default:
				std::cout << "Invalid option\n";
		}
	} while (opt);
}

Last edited on
What compiler are you using? I use onlinegdb and got multiple errors for that sample.








I use MS VS2019. You need to compile as C++20 for L80. As a minimum you need to compile as C++17 and move the pasno initialisation from L80 to another line just before as:

1
2
3
4
size_t pasno {};

for (const auto& pass : user.second.prevpass)
    ofs << (pasno++ ? "," : "") << pass;


If you can't compile as at least C++17, you'll need to update the compiler - or use another one.
Registered users can post here. Sign in or register to post.