From d5c9a821cd15e519489bc4e3f3f0c5b29da8b995 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Thu, 27 Nov 2025 23:15:35 +0900 Subject: [PATCH 01/12] refactor: flatten push/:id/authorise endpoint and improve error codes --- src/service/routes/push.ts | 118 ++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 55 deletions(-) diff --git a/src/service/routes/push.ts b/src/service/routes/push.ts index d1c2fae2c..82ed939ab 100644 --- a/src/service/routes/push.ts +++ b/src/service/routes/push.ts @@ -82,6 +82,13 @@ router.post('/:id/reject', async (req: Request, res: Response) => { }); router.post('/:id/authorise', async (req: Request, res: Response) => { + if (!req.user) { + res.status(401).send({ + message: 'Not logged in', + }); + return; + } + const questions = req.body.params?.attestation; // TODO: compare attestation to configuration and ensure all questions are answered @@ -90,72 +97,73 @@ router.post('/:id/authorise', async (req: Request, res: Response) => { (question: { checked: boolean }) => !!question.checked, ); - if (req.user && attestationComplete) { - const id = req.params.id; + if (!attestationComplete) { + res.status(400).send({ + message: 'Attestation is not complete', + }); + return; + } - const { username } = req.user as { username: string }; + const id = req.params.id; - const push = await db.getPush(id); - if (!push) { - res.status(404).send({ - message: 'Push request not found', - }); - return; - } + const { username } = req.user as { username: string }; - // Get the committer of the push via their email address - const committerEmail = push.userEmail; + const push = await db.getPush(id); + if (!push) { + res.status(404).send({ + message: 'Push request not found', + }); + return; + } - const list = await db.getUsers({ email: committerEmail }); + // Get the committer of the push via their email address + const committerEmail = push.userEmail; - if (list.length === 0) { - res.status(401).send({ - message: `There was no registered user with the committer's email address: ${committerEmail}`, - }); - return; - } + const list = await db.getUsers({ email: committerEmail }); + + if (list.length === 0) { + res.status(404).send({ + message: `No user found with the committer's email address: ${committerEmail}`, + }); + return; + } - if (list[0].username.toLowerCase() === username.toLowerCase() && !list[0].admin) { - res.status(401).send({ - message: `Cannot approve your own changes`, + if (list[0].username.toLowerCase() === username.toLowerCase() && !list[0].admin) { + res.status(403).send({ + message: `Cannot approve your own changes`, + }); + return; + } + + // If we are not the author, now check that we are allowed to authorise on this + // repo + const isAllowed = await db.canUserApproveRejectPush(id, username); + if (isAllowed) { + console.log(`User ${username} approved push request for ${id}`); + + const reviewerList = await db.getUsers({ username }); + const reviewerEmail = reviewerList[0].email; + + if (!reviewerEmail) { + res.status(404).send({ + message: `There was no registered email address for the reviewer: ${username}`, }); return; } - // If we are not the author, now check that we are allowed to authorise on this - // repo - const isAllowed = await db.canUserApproveRejectPush(id, username); - if (isAllowed) { - console.log(`user ${username} approved push request for ${id}`); - - const reviewerList = await db.getUsers({ username }); - const reviewerEmail = reviewerList[0].email; - - if (!reviewerEmail) { - res.status(401).send({ - message: `There was no registered email address for the reviewer: ${username}`, - }); - return; - } - - const attestation = { - questions, - timestamp: new Date(), - reviewer: { - username, - reviewerEmail, - }, - }; - const result = await db.authorise(id, attestation); - res.send(result); - } else { - res.status(401).send({ - message: `user ${username} not authorised to approve push's on this project`, - }); - } + const attestation = { + questions, + timestamp: new Date(), + reviewer: { + username, + reviewerEmail, + }, + }; + const result = await db.authorise(id, attestation); + res.send(result); } else { - res.status(401).send({ - message: 'You are unauthorized to perform this action...', + res.status(403).send({ + message: `User ${username} not authorised to approve pushes on this project`, }); } }); From f42734bad65b98fff78d23a67e819ee41dd80c99 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Thu, 27 Nov 2025 23:16:06 +0900 Subject: [PATCH 02/12] refactor: remaining push route error codes and messages --- src/service/routes/push.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/service/routes/push.ts b/src/service/routes/push.ts index 82ed939ab..fbce5335e 100644 --- a/src/service/routes/push.ts +++ b/src/service/routes/push.ts @@ -38,7 +38,7 @@ router.get('/:id', async (req: Request, res: Response) => { router.post('/:id/reject', async (req: Request, res: Response) => { if (!req.user) { res.status(401).send({ - message: 'not logged in', + message: 'Not logged in', }); return; } @@ -55,14 +55,14 @@ router.post('/:id/reject', async (req: Request, res: Response) => { const list = await db.getUsers({ email: committerEmail }); if (list.length === 0) { - res.status(401).send({ - message: `There was no registered user with the committer's email address: ${committerEmail}`, + res.status(404).send({ + message: `No user found with the committer's email address: ${committerEmail}`, }); return; } if (list[0].username.toLowerCase() === username.toLowerCase() && !list[0].admin) { - res.status(401).send({ + res.status(403).send({ message: `Cannot reject your own changes`, }); return; @@ -72,11 +72,11 @@ router.post('/:id/reject', async (req: Request, res: Response) => { if (isAllowed) { const result = await db.reject(id, null); - console.log(`user ${username} rejected push request for ${id}`); + console.log(`User ${username} rejected push request for ${id}`); res.send(result); } else { - res.status(401).send({ - message: 'User is not authorised to reject changes', + res.status(403).send({ + message: `User ${username} is not authorised to reject changes on this project`, }); } }); @@ -171,7 +171,7 @@ router.post('/:id/authorise', async (req: Request, res: Response) => { router.post('/:id/cancel', async (req: Request, res: Response) => { if (!req.user) { res.status(401).send({ - message: 'not logged in', + message: 'Not logged in', }); return; } @@ -183,12 +183,12 @@ router.post('/:id/cancel', async (req: Request, res: Response) => { if (isAllowed) { const result = await db.cancel(id); - console.log(`user ${username} canceled push request for ${id}`); + console.log(`User ${username} canceled push request for ${id}`); res.send(result); } else { - console.log(`user ${username} not authorised to cancel push request for ${id}`); - res.status(401).send({ - message: 'User ${req.user.username)} not authorised to cancel push requests on this project.', + console.log(`User ${username} not authorised to cancel push request for ${id}`); + res.status(403).send({ + message: `User ${username} not authorised to cancel push requests on this project`, }); } }); From e7d96611632ef1a7963813c88b6ffc5deb660654 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Thu, 27 Nov 2025 23:16:27 +0900 Subject: [PATCH 03/12] test: fix push route tests and add check for error messages --- test/testPush.test.ts | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/test/testPush.test.ts b/test/testPush.test.ts index 8e605ac60..cd74479a5 100644 --- a/test/testPush.test.ts +++ b/test/testPush.test.ts @@ -181,7 +181,8 @@ describe('Push API', () => { ], }, }); - expect(res.status).toBe(401); + expect(res.status).toBe(400); + expect(res.body.message).toBe('Attestation is not complete'); }); it('should NOT allow an authorizer to approve if committer is unknown', async () => { @@ -207,7 +208,10 @@ describe('Push API', () => { ], }, }); - expect(res.status).toBe(401); + expect(res.status).toBe(404); + expect(res.body.message).toBe( + "No user found with the committer's email address: push-test-3@test.com", + ); }); }); @@ -236,7 +240,8 @@ describe('Push API', () => { ], }, }); - expect(res.status).toBe(401); + expect(res.status).toBe(403); + expect(res.body.message).toBe('Cannot approve your own changes'); }); it('should NOT allow a non-authorizer to approve a push', async () => { @@ -260,7 +265,8 @@ describe('Push API', () => { ], }, }); - expect(res.status).toBe(401); + expect(res.status).toBe(403); + expect(res.body.message).toBe('Cannot approve your own changes'); }); it('should allow an authorizer to reject a push', async () => { @@ -282,16 +288,24 @@ describe('Push API', () => { const res = await request(app) .post(`/api/v1/push/${TEST_PUSH.id}/reject`) .set('Cookie', `${cookie}`); - expect(res.status).toBe(401); + expect(res.status).toBe(403); + expect(res.body.message).toBe('Cannot reject your own changes'); }); it('should NOT allow a non-authorizer to reject a push', async () => { - await db.writeAudit(TEST_PUSH as any); + const pushWithOtherUser = { ...TEST_PUSH }; + pushWithOtherUser.user = TEST_USERNAME_1; + pushWithOtherUser.userEmail = TEST_EMAIL_1; + + await db.writeAudit(pushWithOtherUser as any); await loginAsCommitter(); const res = await request(app) - .post(`/api/v1/push/${TEST_PUSH.id}/reject`) + .post(`/api/v1/push/${pushWithOtherUser.id}/reject`) .set('Cookie', `${cookie}`); - expect(res.status).toBe(401); + expect(res.status).toBe(403); + expect(res.body.message).toBe( + 'User push-test-2 is not authorised to reject changes on this project', + ); }); it('should fetch all pushes', async () => { @@ -328,7 +342,10 @@ describe('Push API', () => { const res = await request(app) .post(`/api/v1/push/${TEST_PUSH.id}/cancel`) .set('Cookie', `${cookie}`); - expect(res.status).toBe(401); + expect(res.status).toBe(403); + expect(res.body.message).toBe( + 'User admin not authorised to cancel push requests on this project', + ); const pushes = await request(app).get('/api/v1/push').set('Cookie', `${cookie}`); const push = pushes.body.find((p: any) => p.id === TEST_PUSH.id); From 6db32984e57f915a5de813b26979215174ee8d1c Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 28 Nov 2025 12:00:22 +0900 Subject: [PATCH 04/12] refactor: unify auth/me and auth/profile endpoints --- cypress/support/commands.js | 2 +- src/service/routes/auth.ts | 13 ------------ src/ui/services/auth.ts | 2 +- test/services/routes/auth.test.ts | 31 ---------------------------- test/testLogin.test.ts | 9 ++------ website/docs/development/testing.mdx | 2 +- 6 files changed, 5 insertions(+), 54 deletions(-) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index a0a3f620d..5117d6cfc 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -29,7 +29,7 @@ Cypress.Commands.add('login', (username, password) => { cy.session([username, password], () => { cy.visit('/login'); - cy.intercept('GET', '**/api/auth/me').as('getUser'); + cy.intercept('GET', '**/api/auth/profile').as('getUser'); cy.get('[data-test=username]').type(username); cy.get('[data-test=password]').type(password); diff --git a/src/service/routes/auth.ts b/src/service/routes/auth.ts index f6347eb4f..eea0167a7 100644 --- a/src/service/routes/auth.ts +++ b/src/service/routes/auth.ts @@ -188,19 +188,6 @@ router.post('/gitAccount', async (req: Request, res: Response) => { } }); -router.get('/me', async (req: Request, res: Response) => { - if (req.user) { - const userVal = await db.findUser((req.user as User).username); - if (!userVal) { - res.status(400).send('Error: Logged in user not found').end(); - return; - } - res.send(toPublicUser(userVal)); - } else { - res.status(401).end(); - } -}); - router.post('/create-user', async (req: Request, res: Response) => { if (!isAdminUser(req.user)) { res.status(401).send({ diff --git a/src/ui/services/auth.ts b/src/ui/services/auth.ts index 81acd399e..dae452b28 100644 --- a/src/ui/services/auth.ts +++ b/src/ui/services/auth.ts @@ -16,7 +16,7 @@ interface AxiosConfig { */ export const getUserInfo = async (): Promise => { try { - const response = await fetch(`${API_BASE}/api/auth/me`, { + const response = await fetch(`${API_BASE}/api/auth/profile`, { credentials: 'include', // Sends cookies }); if (!response.ok) throw new Error(`Failed to fetch user info: ${response.statusText}`); diff --git a/test/services/routes/auth.test.ts b/test/services/routes/auth.test.ts index 65152f576..2307e09c3 100644 --- a/test/services/routes/auth.test.ts +++ b/test/services/routes/auth.test.ts @@ -219,37 +219,6 @@ describe('Auth API', () => { }); }); - describe('GET /me', () => { - it('should return 401 Unauthorized if user is not logged in', async () => { - const res = await request(newApp()).get('/auth/me'); - - expect(res.status).toBe(401); - }); - - it('should return 200 OK and serialize public data representation of current logged in user', async () => { - vi.spyOn(db, 'findUser').mockResolvedValue({ - username: 'alice', - password: 'secret-hashed-password', - email: 'alice@example.com', - displayName: 'Alice Walker', - admin: false, - gitAccount: '', - title: '', - }); - - const res = await request(newApp('alice')).get('/auth/me'); - expect(res.status).toBe(200); - expect(res.body).toEqual({ - username: 'alice', - displayName: 'Alice Walker', - email: 'alice@example.com', - title: '', - gitAccount: '', - admin: false, - }); - }); - }); - describe('GET /profile', () => { it('should return 401 Unauthorized if user is not logged in', async () => { const res = await request(newApp()).get('/auth/profile'); diff --git a/test/testLogin.test.ts b/test/testLogin.test.ts index 4f9093b3d..b4715baeb 100644 --- a/test/testLogin.test.ts +++ b/test/testLogin.test.ts @@ -36,12 +36,7 @@ describe('login', () => { }); }); - it('should now be able to access the user login metadata', async () => { - const res = await request(app).get('/api/auth/me').set('Cookie', cookie); - expect(res.status).toBe(200); - }); - - it('should now be able to access the profile', async () => { + it('should now be able to access the user metadata', async () => { const res = await request(app).get('/api/auth/profile').set('Cookie', cookie); expect(res.status).toBe(200); }); @@ -96,7 +91,7 @@ describe('login', () => { }); it('should fail to get the current user metadata if not logged in', async () => { - const res = await request(app).get('/api/auth/me'); + const res = await request(app).get('/api/auth/profile'); expect(res.status).toBe(401); }); diff --git a/website/docs/development/testing.mdx b/website/docs/development/testing.mdx index 81c20b007..2741c003f 100644 --- a/website/docs/development/testing.mdx +++ b/website/docs/development/testing.mdx @@ -295,7 +295,7 @@ In the above example, `cy.login('admin', 'admin')` is actually a custom command Cypress.Commands.add('login', (username, password) => { cy.session([username, password], () => { cy.visit('/login'); - cy.intercept('GET', '**/api/auth/me').as('getUser'); + cy.intercept('GET', '**/api/auth/profile').as('getUser'); cy.get('[data-test=username]').type(username); cy.get('[data-test=password]').type(password); From 539ce14a1a8b90ede46c661428da982272acdd41 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 28 Nov 2025 20:38:03 +0900 Subject: [PATCH 05/12] refactor: flatten auth profile and gitaccount endpoints, improve codes and responses --- src/service/routes/auth.ts | 107 +++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 40 deletions(-) diff --git a/src/service/routes/auth.ts b/src/service/routes/auth.ts index eea0167a7..e9d83c3c6 100644 --- a/src/service/routes/auth.ts +++ b/src/service/routes/auth.ts @@ -133,58 +133,85 @@ router.post('/logout', (req: Request, res: Response, next: NextFunction) => { }); router.get('/profile', async (req: Request, res: Response) => { - if (req.user) { - const userVal = await db.findUser((req.user as User).username); - if (!userVal) { - res.status(400).send('Error: Logged in user not found').end(); - return; - } - res.send(toPublicUser(userVal)); - } else { - res.status(401).end(); + if (!req.user) { + res + .status(401) + .send({ + message: 'Not logged in', + }) + .end(); + return; + } + + const userVal = await db.findUser((req.user as User).username); + if (!userVal) { + res.status(404).send('User not found').end(); + return; } + + res.send(toPublicUser(userVal)); }); router.post('/gitAccount', async (req: Request, res: Response) => { - if (req.user) { - try { - let username = - req.body.username == null || req.body.username === 'undefined' - ? req.body.id - : req.body.username; - username = username?.split('@')[0]; - - if (!username) { - res.status(400).send('Error: Missing username. Git account not updated').end(); - return; - } + if (!req.user) { + res + .status(401) + .send({ + message: 'Not logged in', + }) + .end(); + return; + } - const reqUser = await db.findUser((req.user as User).username); - if (username !== reqUser?.username && !reqUser?.admin) { - res.status(403).send('Error: You must be an admin to update a different account').end(); - return; - } + try { + let username = + req.body.username == null || req.body.username === 'undefined' + ? req.body.id + : req.body.username; + username = username?.split('@')[0]; - const user = await db.findUser(username); - if (!user) { - res.status(400).send('Error: User not found').end(); - return; - } + if (!username) { + res + .status(400) + .send({ + message: 'Missing username. Git account not updated', + }) + .end(); + return; + } + + const reqUser = await db.findUser((req.user as User).username); + if (username !== reqUser?.username && !reqUser?.admin) { + res + .status(403) + .send({ + message: 'Must be an admin to update a different account', + }) + .end(); + return; + } - console.log('Adding gitAccount' + req.body.gitAccount); - user.gitAccount = req.body.gitAccount; - db.updateUser(user); - res.status(200).end(); - } catch (e: any) { + const user = await db.findUser(username); + if (!user) { res - .status(500) + .status(404) .send({ - message: `Error updating git account: ${e.message}`, + message: 'User not found', }) .end(); + return; } - } else { - res.status(401).end(); + + user.gitAccount = req.body.gitAccount; + db.updateUser(user); + res.status(200).end(); + } catch (e: any) { + res + .status(500) + .send({ + message: `Failed to update git account: ${e.message}`, + }) + .end(); } }); From 78ed7ccdf16c4296983847ecc6f8ff16417d1c8f Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 28 Nov 2025 20:41:24 +0900 Subject: [PATCH 06/12] refactor: remaining auth endpoints error codes and messages --- src/service/routes/auth.ts | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/service/routes/auth.ts b/src/service/routes/auth.ts index e9d83c3c6..1d36ff2a9 100644 --- a/src/service/routes/auth.ts +++ b/src/service/routes/auth.ts @@ -107,7 +107,7 @@ router.get('/openidconnect/callback', (req: Request, res: Response, next: NextFu passport.authenticate(authStrategies['openidconnect'].type, (err: any, user: any, info: any) => { if (err) { console.error('Authentication error:', err); - return res.status(401).end(); + return res.status(500).end(); } if (!user) { console.error('No user found:', info); @@ -116,7 +116,7 @@ router.get('/openidconnect/callback', (req: Request, res: Response, next: NextFu req.logIn(user, (err) => { if (err) { console.error('Login error:', err); - return res.status(401).end(); + return res.status(500).end(); } console.log('Logged in successfully. User:', user); return res.redirect(`${uiHost}:${uiPort}/dashboard/profile`); @@ -217,9 +217,12 @@ router.post('/gitAccount', async (req: Request, res: Response) => { router.post('/create-user', async (req: Request, res: Response) => { if (!isAdminUser(req.user)) { - res.status(401).send({ - message: 'You are not authorized to perform this action...', - }); + res + .status(403) + .send({ + message: 'Not authorized to create users', + }) + .end(); return; } @@ -227,20 +230,27 @@ router.post('/create-user', async (req: Request, res: Response) => { const { username, password, email, gitAccount, admin: isAdmin = false } = req.body; if (!username || !password || !email || !gitAccount) { - res.status(400).send({ - message: 'Missing required fields: username, password, email, and gitAccount are required', - }); + res + .status(400) + .send({ + message: + 'Missing required fields: username, password, email, and gitAccount are required', + }) + .end(); return; } await db.createUser(username, password, email, gitAccount, isAdmin); - res.status(201).send({ - message: 'User created successfully', - username, - }); + res + .status(201) + .send({ + message: 'User created successfully', + username, + }) + .end(); } catch (error: any) { console.error('Error creating user:', error); - res.status(400).send({ + res.status(500).send({ message: error.message || 'Failed to create user', }); } From dbc545761900f33c6e905302187d505db5afbc9e Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 28 Nov 2025 20:42:09 +0900 Subject: [PATCH 07/12] test: update auth endpoint tests --- test/testLogin.test.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/testLogin.test.ts b/test/testLogin.test.ts index b4715baeb..91d8b4f58 100644 --- a/test/testLogin.test.ts +++ b/test/testLogin.test.ts @@ -118,8 +118,8 @@ describe('login', () => { gitAccount: 'newgit', }); - expect(res.status).toBe(401); - expect(res.body.message).toBe('You are not authorized to perform this action...'); + expect(res.status).toBe(403); + expect(res.body.message).toBe('Not authorized to create users'); }); it('should fail to create user when not admin', async () => { @@ -150,8 +150,8 @@ describe('login', () => { gitAccount: 'newgit', }); - expect(res.status).toBe(401); - expect(res.body.message).toBe('You are not authorized to perform this action...'); + expect(res.status).toBe(403); + expect(res.body.message).toBe('Not authorized to create users'); }); it('should fail to create user with missing required fields', async () => { @@ -231,7 +231,8 @@ describe('login', () => { admin: false, }); - expect(failCreateRes.status).toBe(400); + expect(failCreateRes.status).toBe(500); + expect(failCreateRes.body.message).toBe('user newuser already exists'); }); }); From 1ed0f312b92028b00f6299344f5758b1ccedd061 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 28 Nov 2025 20:55:29 +0900 Subject: [PATCH 08/12] chore: move publicApi.ts contents to utils.ts --- src/service/routes/auth.ts | 3 +-- src/service/routes/publicApi.ts | 12 ------------ src/service/routes/users.ts | 2 +- src/service/routes/utils.ts | 13 +++++++++++++ 4 files changed, 15 insertions(+), 15 deletions(-) delete mode 100644 src/service/routes/publicApi.ts diff --git a/src/service/routes/auth.ts b/src/service/routes/auth.ts index 1d36ff2a9..9835af3c8 100644 --- a/src/service/routes/auth.ts +++ b/src/service/routes/auth.ts @@ -9,8 +9,7 @@ import * as passportAD from '../passport/activeDirectory'; import { User } from '../../db/types'; import { AuthenticationElement } from '../../config/generated/config'; -import { toPublicUser } from './publicApi'; -import { isAdminUser } from './utils'; +import { isAdminUser, toPublicUser } from './utils'; const router = express.Router(); const passport = getPassport(); diff --git a/src/service/routes/publicApi.ts b/src/service/routes/publicApi.ts deleted file mode 100644 index 1b408a562..000000000 --- a/src/service/routes/publicApi.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { PublicUser, User } from '../../db/types'; - -export const toPublicUser = (user: User): PublicUser => { - return { - username: user.username || '', - displayName: user.displayName || '', - email: user.email || '', - title: user.title || '', - gitAccount: user.gitAccount || '', - admin: user.admin || false, - }; -}; diff --git a/src/service/routes/users.ts b/src/service/routes/users.ts index 2e689817e..78f826365 100644 --- a/src/service/routes/users.ts +++ b/src/service/routes/users.ts @@ -2,7 +2,7 @@ import express, { Request, Response } from 'express'; const router = express.Router(); import * as db from '../../db'; -import { toPublicUser } from './publicApi'; +import { toPublicUser } from './utils'; router.get('/', async (req: Request, res: Response) => { console.log('fetching users'); diff --git a/src/service/routes/utils.ts b/src/service/routes/utils.ts index a9c501801..694732a5d 100644 --- a/src/service/routes/utils.ts +++ b/src/service/routes/utils.ts @@ -1,3 +1,5 @@ +import { PublicUser, User as DbUser } from '../../db/types'; + interface User extends Express.User { username: string; admin?: boolean; @@ -6,3 +8,14 @@ interface User extends Express.User { export function isAdminUser(user?: Express.User): user is User & { admin: true } { return user !== null && user !== undefined && (user as User).admin === true; } + +export const toPublicUser = (user: DbUser): PublicUser => { + return { + username: user.username || '', + displayName: user.displayName || '', + email: user.email || '', + title: user.title || '', + gitAccount: user.gitAccount || '', + admin: user.admin || false, + }; +}; From 7b2f1786f8c0a9f72dda6adc55e3aa9dccc28bbd Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 29 Nov 2025 15:27:22 +0900 Subject: [PATCH 09/12] chore: fix failing cypress test --- cypress/e2e/login.cy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/e2e/login.cy.js b/cypress/e2e/login.cy.js index 40ce83a75..418109b5b 100644 --- a/cypress/e2e/login.cy.js +++ b/cypress/e2e/login.cy.js @@ -20,7 +20,7 @@ describe('Login page', () => { }); it('should redirect to repo list on valid login', () => { - cy.intercept('GET', '**/api/auth/me').as('getUser'); + cy.intercept('GET', '**/api/auth/profile').as('getUser'); cy.get('[data-test="username"]').type('admin'); cy.get('[data-test="password"]').type('admin'); From 498d3cb85b5d9cef5e20989c747ac6485ee7f3f6 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sun, 30 Nov 2025 12:01:36 +0900 Subject: [PATCH 10/12] chore: improve user endpoint error handling in UI --- src/service/routes/users.ts | 7 +++- .../Navbars/DashboardNavbarLinks.tsx | 5 ++- src/ui/services/user.ts | 40 ++++++++++--------- src/ui/views/User/UserProfile.tsx | 24 +++++------ 4 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/service/routes/users.ts b/src/service/routes/users.ts index 78f826365..8701223c5 100644 --- a/src/service/routes/users.ts +++ b/src/service/routes/users.ts @@ -15,7 +15,12 @@ router.get('/:id', async (req: Request, res: Response) => { console.log(`Retrieving details for user: ${username}`); const user = await db.findUser(username); if (!user) { - res.status(404).send('Error: User not found').end(); + res + .status(404) + .send({ + message: `User ${username} not found`, + }) + .end(); return; } res.send(toPublicUser(user)); diff --git a/src/ui/components/Navbars/DashboardNavbarLinks.tsx b/src/ui/components/Navbars/DashboardNavbarLinks.tsx index 2ed5c3d8f..d23d3b65a 100644 --- a/src/ui/components/Navbars/DashboardNavbarLinks.tsx +++ b/src/ui/components/Navbars/DashboardNavbarLinks.tsx @@ -28,11 +28,11 @@ const DashboardNavbarLinks: React.FC = () => { const [openProfile, setOpenProfile] = useState(null); const [, setAuth] = useState(true); const [, setIsLoading] = useState(true); - const [, setIsError] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); const [user, setUser] = useState(null); useEffect(() => { - getUser(setIsLoading, setUser, setAuth, setIsError); + getUser(setIsLoading, setUser, setAuth, setErrorMessage); }, []); const handleClickProfile = (event: React.MouseEvent) => { @@ -66,6 +66,7 @@ const DashboardNavbarLinks: React.FC = () => { return (
+ {errorMessage &&
{errorMessage}
}