fix(api): byte-safe bearer auth, hide classroom flag

This commit is contained in:
Mrugesh Mohapatra
2026-05-29 09:39:01 +05:30
parent 9f6c6d11f0
commit 5b1daeceb8
5 changed files with 58 additions and 6 deletions

View File

@@ -88,4 +88,56 @@ describe('service-bearer-auth plugin', () => {
expect(res.statusCode).toEqual(401);
expect(res.json()).toEqual({ error: 'Invalid bearer token' });
});
test('should return 401 when bearer token is the same length but wrong', async () => {
const sameLengthWrongToken = 'x'.repeat('test-api-secret-key'.length);
const res = await fastify.inject({
method: 'GET',
url: '/test',
headers: {
authorization: `Bearer ${sameLengthWrongToken}`
}
});
expect(res.statusCode).toEqual(401);
expect(res.json()).toEqual({ error: 'Invalid bearer token' });
});
});
describe('service-bearer-auth plugin without a configured token', () => {
afterEach(() => {
vi.doUnmock('../utils/env');
vi.resetModules();
});
test('should return 500 when TPA_API_BEARER_TOKEN is not configured', async () => {
vi.resetModules();
vi.doMock('../utils/env', async importOriginal => {
const actual = await importOriginal<typeof import('../utils/env.js')>();
return { ...actual, TPA_API_BEARER_TOKEN: '' };
});
const { default: plugin } = await import('./service-bearer-auth.js');
const fastify = Fastify();
await fastify.register(plugin);
fastify.addHook('onRequest', fastify.validateBearerToken);
fastify.get('/test', (_req, reply) => {
void reply.send({ ok: true });
});
const res = await fastify.inject({
method: 'GET',
url: '/test',
headers: {
authorization: 'Bearer anything'
}
});
expect(res.statusCode).toEqual(500);
expect(res.json()).toEqual({
error: 'Service authentication not configured'
});
await fastify.close();
});
});

View File

@@ -38,9 +38,11 @@ const plugin: FastifyPluginCallback = (fastify, _options, done) => {
}
const token = authHeader.slice(7);
const tokenBuf = Buffer.from(token);
const secretBuf = Buffer.from(secret);
if (
token.length !== secret.length ||
!crypto.timingSafeEqual(Buffer.from(token), Buffer.from(secret))
tokenBuf.length !== secretBuf.length ||
!crypto.timingSafeEqual(tokenBuf, secretBuf)
) {
await reply.status(401).send({ error: 'Invalid bearer token' });
return;

View File

@@ -210,7 +210,8 @@ const sessionOnlyData = {
theme: testUserData.theme,
keyboardShortcuts: testUserData.keyboardShortcuts,
completedChallengeCount: 3,
acceptedPrivacyTerms: testUserData.acceptedPrivacyTerms
acceptedPrivacyTerms: testUserData.acceptedPrivacyTerms,
isClassroomAccount: testUserData.isClassroomAccount ?? false
};
const publicUserData = {
@@ -281,7 +282,6 @@ const publicUserData = {
isApisMicroservicesCert: testUserData.isApisMicroservicesCert,
isBackEndCert: testUserData.isBackEndCert,
isCheater: testUserData.isCheater,
isClassroomAccount: testUserData.isClassroomAccount ?? false,
isCollegeAlgebraPyCertV8: testUserData.isCollegeAlgebraPyCertV8,
isDataAnalysisPyCertV7: testUserData.isDataAnalysisPyCertV7,
isDataVisCert: testUserData.isDataVisCert,

View File

@@ -204,7 +204,6 @@ const publicUserData = {
isApisMicroservicesCert: testUserData.isApisMicroservicesCert,
isBackEndCert: testUserData.isBackEndCert,
isCheater: testUserData.isCheater,
isClassroomAccount: testUserData.isClassroomAccount ?? false,
isCollegeAlgebraPyCertV8: testUserData.isCollegeAlgebraPyCertV8,
isDataAnalysisPyCertV7: testUserData.isDataAnalysisPyCertV7,
isDataVisCert: testUserData.isDataVisCert,

View File

@@ -68,7 +68,6 @@ export const getPublicProfile = {
isFrontEndLibsCert: Type.Boolean(),
isFullStackCert: Type.Boolean(),
isJavascriptCertV9: Type.Boolean(),
isClassroomAccount: Type.Boolean(),
isHonest: Type.Boolean(),
isInfosecCertV7: Type.Boolean(),
isInfosecQaCert: Type.Boolean(),