C++ Student Portal System Project
Logins, roles, grades, and courses — the system behind a school portal.
What you will build
A portal with three kinds of users: students see their grades and courses, lecturers enter results, admins manage everything. Role-based access control — who may do what — is the core lesson, alongside authentication done carefully enough to discuss honestly.
What you will learn
- User authentication and session state
- Role-based permissions (student/lecturer/admin)
- Relational-style data: students ↔ courses ↔ grades
- Password hashing (even a simple salted hash beats plaintext)
- Audit logging of sensitive actions
Feature checklist
- Login with username + password (hashed, salted)
- Student view: my courses, my grades, GPA
- Lecturer view: enter/edit grades for their courses
- Admin: create users and courses, enrol students
- GPA computed from grade points
- Every grade change written to an audit log
Starter code
#include <map>
#include <string>
#include <vector>
enum class Role { Student, Lecturer, Admin };
struct User { std::string name; Role role; std::string passHash; };
class Portal {
std::map<std::string, User> users_; // by username
std::map<std::string, std::map<std::string, char>> grades_; // course -> student -> grade
const User* session_ = nullptr;
public:
bool login(const std::string& u, const std::string& hash) {
auto it = users_.find(u);
if (it != users_.end() && it->second.passHash == hash) {
session_ = &it->second; return true;
}
return false;
}
bool setGrade(const std::string& course, const std::string& student,
char grade) {
if (!session_ || session_->role == Role::Student) return false;
grades_[course][student] = grade; // (audit-log this in real code)
return true;
}
char myGrade(const std::string& course) const {
if (!session_) return '?';
auto c = grades_.find(course);
if (c == grades_.end()) return '-';
auto s = c->second.find(session_->name);
return s == c->second.end() ? '-' : s->second;
}
};Full walkthrough
Step 1: Roles first: one User type, three kinds of power
struct User { username, saltedHash, role } with enum class Role { Student, Lecturer, Admin }, and class Portal owning users, courses (code, title, lecturer username), enrolments, and grades. Every menu after login is generated from the role — a student physically cannot see 'enter grades' because the option is never rendered for them, which is authorisation by construction, not by hope.
Step 2: Hash passwords with a salt — and be honest about the hash
Never store plaintext: store hash(salt + password) with a per-user random salt (so two users sharing a password store different hashes, and rainbow tables die). Use a real hash — a simple FNV or djb2 demonstrates the MECHANISM for learning, but label it clearly: production systems use bcrypt/argon2, and your code comments should say the teaching hash is not one. login() recomputes and compares.
// teaching hash — mechanism demo only, NOT production crypto
std::string Portal::login(user, pass) {
auto u = users[user];
return hash(u.salt + pass) == u.saltedHash ? sessionFor(u) : "";
}Step 3: Admin operations: users, courses, enrolment
Admin creates users (with role), creates courses assigned to a lecturer, and enrols students into courses. Each is a checked insert into the Portal's maps — duplicates refused, references validated (you cannot enrol a ghost student into a ghost course).
Step 4: Lecturer view: grades only for THEIR courses
A lecturer sees the courses where course.lecturer == their username, picks one, and setGrade(student, course, score) records it. The ownership check inside setGrade — not just in the menu — is the real boundary: menus can be bypassed, function-level checks cannot. Out-of-range scores are refused at the same gate.
Step 5: Every grade change hits the audit log
setGrade appends a line to an append-only audit file: timestamp, lecturer, student, course, old grade → new grade. 'Append-only' and 'old → new' are the whole design: grades are disputed exactly when someone changed them, and this log is the only witness. Admin can view it; nobody can edit through the portal.
Step 6: Student view and the GPA computation
A student sees their enrolled courses, their grades, and a GPA: convert each score to grade points (A=5, B=4, C=3, D=2, E=1, F=0), weight by the course's units, sum, and divide by total units. The weighting is the detail everyone forgets: an A in a 1-unit course must not cancel an F in a 4-unit course.
Step 7: Test the permissions story
Log in as a lecturer and try to grade a course that is NOT yours (refused at setGrade, and the attempt is visible behaviour, not a hidden menu). As a student, confirm only your own grades exist on screen. Change a grade as the lecturer and read the audit log line it produced. Compute a GPA by hand for a 3-course student — A(3 units), B(2), A(4) = (15 + 8 + 20) / 9 = 4.78 — and make the portal agree.
Common mistakes
- Storing passwords in plaintext 'because it is only a class project' — the habit you practise is the habit you ship.
- Checking permissions only by hiding menu items, while setGrade itself accepts any caller.
- Averaging grade points without unit weighting, so course sizes silently distort every GPA.
Compile and run
g++ -std=c++17 -o portal portal.cppSample run
$ ./portal
Username:
> drbello
Password:
> ********
Welcome, Dr. Bello (Lecturer). Your courses: CSC301, CSC305
1) Enter grades 2) View course list 0) Logout
> 1
Course, student, score:
> CSC301, adaeze, 86
Grade recorded: Adaeze Obi, CSC301 = 86 (A). Written to audit log.
(setGrade() refuses courses that are not yours — the check lives in the function, not the menu.)
> 0
Username:
> adaeze
Password:
> ********
Welcome, Adaeze Obi (Student).
CSC301 Data Structures — 86 (A) · 3 units
MTH210 Linear Algebra — 74 (B) · 2 units
PHY204 Electromagnetism — 91 (A) · 4 units
GPA: 4.78 (weighted by course units)Try it in your browser
No browser demo is included for this project. Build the C++ version below — that is the real program.