projects / cib-group-11
CiB Group 11
Employee referral system prototype built in one week for a BAE Systems brief, with a cross-course team. Vue 3 and Vuetify on the front, Express, Sequelize and MySQL behind.
Overview
This was a one-week group project for the Computing in Business unit in May 2025. The university put the teams together from different computing courses, so I didn't know anyone in mine beforehand. BAE Systems supported the week and set the brief: a referral system where employees and their managers can raise cases (medical, mental health, harassment) and HR can track and act on them.
I wrote the code. Of the 54 commits in the repo, 53 are mine: the database models, the API, login and sessions, the email notifications, and the Vue frontend. The rest of the team worked on the design with me, which I needed: at the time I didn't have a good sense of how to lay out dashboards or web pages, and it's something I've got better at in later projects. They also put the presentation together and kept what I was building in line with the brief, so the demo and the presentation matched.

The brief
During the week we interviewed BAE Systems. The interview didn't give us detailed requirements, but it was enough to settle on what to build:
- four kinds of user: employees, line managers, HR, and system admins
- referrals with a type, a priority, a status, notes and attached files
- HR manages departments and user accounts
- new accounts get their temporary password by email
The deadline was the end of the week, so the goal was a working prototype we could demo, not a finished product.
How it works
Backend. An Express 5 API in TypeScript using sequelize-typescript, which defines the tables as decorated classes. On startup it syncs the schema, creates the two base departments, and creates an admin account with a temporary password. Roles are an enum: User (employee), EnhancedUser (line manager), EnhancedUserPlus (HR) and Admin. Each group of routes has its own folder of controllers and routes. Referrals use Sequelize's soft delete ("paranoid" tables), so archiving one is reversible, and the tables have a "show deleted" switch and a restore button.
Login and sessions. This was the part I spent the most care on:
const user = await UserClass.findOne({ where: { emailAddress: userEmail } }); const passwordMatch = await bcrypt.compare(password, user?.password!); if (passwordMatch) { const newSession = await UserSessionClass.create({ userId: user?.userId }) res.cookie('sessionId', jwt.sign(newSession?.sessionId.toString(), JWT_SECRET), { expires: new Date(Date.now() + 3600000), secure: false, httpOnly: true }); if (user?.tempPassword) { return res.status(200).json({ success: true, message: 'Logged In', data: { changePassword: true, goTo: (user?.role === EUserRole.Admin || user?.role === EUserRole.EnhancedUser || user?.role === EUserRole.EnhancedUserPlus) ? `dashboard` : `account` } }); }
Passwords are stored as bcrypt hashes and checked with bcrypt.compareL7. A successful login creates a session rowL10-12 in the database. The cookie only holds that session's ID, signed as a JWT and marked httpOnlyL14-18, so the server can end a session by deleting the row. If the account still has a temporary passwordL20, the response tells the frontend to make the user change it first.

Creating users. HR fills in a two-step form (details, then department and permissions). The API makes the account with a random temporary password and emails it with nodemailer. If the email fails, the account is deleted again, so nobody ends up with an account they can't get into.
Frontend. Vue 3 with file-based routing. My README says I used shadcn/vue, but the frontend is actually built with Vuetify 3 (plus Tailwind). Its data tables, steppers and dialogs got the admin pages done quickly. Each role gets its own dashboard with summary cards and a recent referrals table, and the buttons use BAE's red so it looked like it belonged to them in the demo.
Outcome
We had a working demo by the end of the week: login with forced password change, user and department management, referral tables with archive and restore, and a two-step referral form. It wasn't finished. File uploads, real referral types and priorities (new referrals are hard-coded to "Medical" and "Low"), and the deadline cards were still missing.
The presentation went well. Other teams were interested in what we'd built, and the judges gave us useful feedback on what we could have improved or done better within the week. Most of us had tired voices by the end of the day, and it finished with drinks with the other teams.
I study cyber security, so reading this back, the server-side checks are what stand out:
static async createUser(req: Request, res: Response): Promise<any> { try { const { name = '', emailAddress = '', department = 0, permission = EUserRole.User } = req.body; const lower = "abcdefghijklmnopqrstuvwxyz"; const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const numbers = "0123456789"; const symbols = "!@#$%^&*()-_=+[]{}|;:,.<>?"; const allChars = lower + upper + numbers + symbols; let password = ""; for (let i = 0; i < 5; i++) { const randomIndex = Math.floor(Math.random() * allChars.length); password += allChars[randomIndex]; } const salt = await bcrypt.genSalt(10); const hash = await bcrypt.hash(password, salt); const user = await UserClass.create({ name: name, emailAddress: emailAddress, role: permission, departmentId: department, password: hash, tempPassword: true });
- No authorisation on the HR routes.
createUsertakes the role straight from the request bodyL3 and saves itL21-28 without checking who is asking. Hiding pages from users who shouldn't see them happens only in the frontend, so anyone who can reach the API could create an admin account. The session check in the login controller should have been shared middleware in front of every/plusand/enhancedroute, with a role check. - Weak temporary passwords. Five characters from
Math.randomL13-16 is too short and not cryptographically random. The seededadmin/adminaccount andsecure: falseon the cookie were fine for a demo laptop and nowhere else. - A request that never ends. Creating a referral without a session cookie never sends a response at all, so the request just hangs.
The good parts were also mine: hashing, server-side sessions, and the email rollback. Building a whole stack in five days taught me to get the risky parts (login, data model) working first, and that the demo-only shortcuts need writing down at the time, so they don't get forgotten afterwards.
The decorated model classes are the same idea I tried to build myself in Savers a year earlier. Using sequelize-typescript here showed me what a finished version of that idea looks like.