Skip to content

Commit

Permalink
chore(api): add ExamEnvironmentAuthorizationToken -> user relation (f…
Browse files Browse the repository at this point in the history
…reeCodeCamp#56627)

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
  • Loading branch information
ShaunSHamilton and ojeytonwilliams authored Oct 25, 2024
1 parent 90e122b commit 5180249
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 7 deletions.
5 changes: 5 additions & 0 deletions api/jest.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ export const defaultUserEmail = 'foo@bar.com';
export const defaultUsername = 'fcc-test-user';

export const resetDefaultUser = async (): Promise<void> => {
await fastifyTestInstance.prisma.examEnvironmentAuthorizationToken.deleteMany(
{
where: { userId: defaultUserId }
}
);
await fastifyTestInstance.prisma.user.deleteMany({
where: { email: defaultUserEmail }
});
Expand Down
11 changes: 5 additions & 6 deletions api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ model user {
isClassroomAccount Boolean? // Undefined
// Relations
examAttempts EnvExamAttempt[]
examAttempts EnvExamAttempt[]
examEnvironmentAuthorizationToken ExamEnvironmentAuthorizationToken?
}

// -----------------------------------
Expand Down Expand Up @@ -377,15 +378,13 @@ model UserToken {
@@index([userId], map: "userId_1")
}

/// TODO: Token has to outlive the exam attempt
/// Validation has to be taken as the attempt is requested
/// to ensure it lives long enough.
model ExamEnvironmentAuthorizationToken {
id String @id @map("_id")
createdDate DateTime @db.Date
userId String @db.ObjectId
userId String @unique @db.ObjectId
@@index([userId], map: "userId_1")
// Relations
user user @relation(fields: [userId], references: [id])
}

model sessions {
Expand Down
2 changes: 1 addition & 1 deletion api/src/exam-environment/routes/exam-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ async function tokenVerifyHandler(
const examEnvironmentAuthorizationToken =
payload.examEnvironmentAuthorizationToken;

const token = await this.prisma.examEnvironmentAuthorizationToken.findFirst({
const token = await this.prisma.examEnvironmentAuthorizationToken.findUnique({
where: {
id: examEnvironmentAuthorizationToken
}
Expand Down
72 changes: 72 additions & 0 deletions api/src/routes/protected/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
createSuperRequest
} from '../../../jest.utils';
import { JWT_SECRET } from '../../utils/env';
import { customNanoid } from '../../utils/ids';
import { getMsTranscriptApiUrl } from './user';

const mockedFetch = jest.fn();
Expand Down Expand Up @@ -564,6 +565,11 @@ describe('userRoutes', () => {
await fastifyTestInstance.prisma.userToken.deleteMany({
where: { id: userTokenId }
});
await fastifyTestInstance.prisma.examEnvironmentAuthorizationToken.deleteMany(
{
where: { userId: defaultUserId }
}
);
});

test('GET rejects with 500 status code if the username is missing', async () => {
Expand Down Expand Up @@ -1130,6 +1136,72 @@ Thanks and regards,
});
});
});

describe('/user/exam-environment/token', () => {
afterEach(async () => {
await fastifyTestInstance.prisma.examEnvironmentAuthorizationToken.deleteMany(
{
where: { userId: defaultUserId }
}
);
});

test('POST generates a new token if one does not exist', async () => {
const response = await superPost('/user/exam-environment/token');
const { examEnvironmentAuthorizationToken } = response.body.data;

const decodedToken = jwt.decode(examEnvironmentAuthorizationToken);

expect(decodedToken).toStrictEqual({
examEnvironmentAuthorizationToken:
expect.stringMatching(/^[a-zA-Z0-9]{64}$/),
iat: expect.any(Number)
});

expect(() =>
jwt.verify(examEnvironmentAuthorizationToken, 'wrong-secret')
).toThrow();
expect(() =>
jwt.verify(examEnvironmentAuthorizationToken, JWT_SECRET)
).not.toThrow();

expect(response.status).toBe(200);
});

test('POST only allows for one token per user id', async () => {
const id = customNanoid();
await fastifyTestInstance.prisma.examEnvironmentAuthorizationToken.create(
{
data: {
userId: defaultUserId,
id,
createdDate: new Date()
}
}
);

const response = await superPost('/user/exam-environment/token');

const { examEnvironmentAuthorizationToken } = response.body.data;

const decodedToken = jwt.decode(examEnvironmentAuthorizationToken);

expect(decodedToken).not.toHaveProperty(
'examEnvironmentAuthorizationToken',
id
);

expect(response.status).toBe(200);

const tokens =
await fastifyTestInstance.prisma.examEnvironmentAuthorizationToken.findMany(
{
where: { userId: defaultUserId }
}
);
expect(tokens).toHaveLength(1);
});
});
});

describe('Unauthenticated user', () => {
Expand Down

0 comments on commit 5180249

Please sign in to comment.