Skip to content

Commit

Permalink
add checking issuer for awarding badge
Browse files Browse the repository at this point in the history
  • Loading branch information
jhk482001 committed Jan 20, 2025
1 parent 227aaeb commit 8431d58
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 95 deletions.
68 changes: 34 additions & 34 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,46 @@ services:
db:
image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.3.2
ports:
- "62223:9200"
- "62222:9200"
volumes:
- "./esdata:/usr/share/elasticsearch/data"
environment:
- "path.repo=/usr/share/elasticsearch/data"
- "ES_JAVA_OPTS=-Xms512m -Xmx512m" # Prevent elasticsearch eating up too much memory

# kibana:
# image: docker.elastic.co/kibana/kibana-oss:6.3.2
# depends_on:
# - db
# environment:
# ELASTICSEARCH_URL: http://db:9200 # Through docker network, not exposed port
# ports:
# - "6222:5601"
kibana:
image: docker.elastic.co/kibana/kibana-oss:6.3.2
depends_on:
- db
environment:
ELASTICSEARCH_URL: http://db:9200 # Through docker network, not exposed port
ports:
- "6222:5601"

# url-resolver:
# image: cofacts/url-resolver
# ports:
# - "4000:4000"
url-resolver:
image: cofacts/url-resolver
ports:
- "4000:4000"

# api:
# image: node:18
# container_name: rumors-api
# depends_on:
# - db
# working_dir: "/srv/www"
# entrypoint: npm run dev
# volumes:
# - ".:/srv/www"
# environment:
# ELASTICSEARCH_URL: "http://db:9200"
# URL_RESOLVER_URL: "url-resolver:4000"
# ports:
# - "6000:5000"
# - "5500:5500"
api:
image: node:18
container_name: rumors-api
depends_on:
- db
working_dir: "/srv/www"
entrypoint: npm run dev
volumes:
- ".:/srv/www"
environment:
ELASTICSEARCH_URL: "http://db:9200"
URL_RESOLVER_URL: "url-resolver:4000"
ports:
- "6000:5000"
- "5500:5500"

# site:
# image: cofacts/rumors-site:latest-en
# ports:
# - "3000:3000"
# environment:
# PUBLIC_API_URL: http://localhost:5000
site:
image: cofacts/rumors-site:latest-en
ports:
- "3000:3000"
environment:
PUBLIC_API_URL: http://localhost:5000
15 changes: 14 additions & 1 deletion src/adm/handlers/moderation/__fixtures__/awardBadge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { User } from 'rumors-db/schema/users';
import type { Badge } from 'rumors-db/schema/badges';

export default {
'/users/doc/user-to-award-badge': {
Expand All @@ -15,11 +16,23 @@ export default {
badges: [
{
badgeId: 'test-certification-001',
badgeMetaData: '{"from":"some-orgnization}',
badgeMetaData: '{"from":"some-orgnization"}',
isDisplayed: false,
createdAt: '2020-01-01T00:00:00.000Z',
updatedAt: '2020-01-01T00:00:00.000Z',
},
],
} satisfies User,

'/badges/doc/test-certification-001': {
name: 'Test Certification',
displayName: 'Test Certification',
description: 'A test certification badge',
link: 'https://badge.source.com',
icon: 'https://badge.source.com/icon.png',
borderImage: 'https://badge.source.com/border.png',
issuers: ['[email protected]', 'service-token-123'],
createdAt: '2020-01-01T00:00:00.000Z',
updatedAt: '2020-01-01T00:00:00.000Z',
} satisfies Badge,
};
148 changes: 92 additions & 56 deletions src/adm/handlers/moderation/__tests__/awardBadge.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,68 +7,104 @@ import fixtures from '../__fixtures__/awardBadge';

const FIXED_DATE = 612921600000;

beforeEach(() => loadFixtures(fixtures));
afterEach(() => unloadFixtures(fixtures));

it('fails if userId is not valid', async () => {
await expect(
awardBadge({
userId: 'not-exist',
badgeId: 'badge id',
badgeMetaData: '{}',
})
).rejects.toMatchInlineSnapshot(
`[HTTPError: User with ID=not-exist does not exist]`
);
beforeEach(async () => {
await loadFixtures(fixtures);
MockDate.set(FIXED_DATE);
});

/**
* Asserts the document in database is the same as in the fixture,
* i.e. the document is not modified
*
* @param {string} fixtureKey
* @param {{index: string; id: string;}} clientGetArgs - Arguments for client.get()
*/
afterEach(async () => {
await unloadFixtures(fixtures);
MockDate.reset();
});

it('correctly sets the awarded badge id and updates status of their works', async () => {
MockDate.set(FIXED_DATE);
const result = await awardBadge({
userId: 'user-to-award-badge',
badgeId: 'test-certification-001',
badgeMetaData: '{"from":"some-orgnization}',
describe('awardBadge', () => {
it('fails if userId is not valid', async () => {
await expect(
awardBadge({
userId: 'not-exist',
badgeId: 'badge id',
badgeMetaData: '{}',
request: { userId: '[email protected]' },
})
).rejects.toMatchInlineSnapshot(
`[HTTPError: User with ID=not-exist does not exist]`
);
});
MockDate.reset();

expect(result).toMatchInlineSnapshot(`
Object {
"badgeId": "test-certification-001",
"badgeMetaData": "{\\"from\\":\\"some-orgnization}",
}
`);
/**
* Asserts the document in database is the same as in the fixture,
* i.e. the document is not modified
*
* @param {string} fixtureKey
* @param {{index: string; id: string;}} clientGetArgs - Arguments for client.get()
*/

it('correctly sets the awarded badge id when authorized', async () => {
const result = await awardBadge({
userId: 'user-to-award-badge',
badgeId: 'test-certification-001',
badgeMetaData: '{"from":"some-orgnization"}',
request: { userId: '[email protected]' },
});

const {
body: { _source: userWithBadge },
} = await client.get({
index: 'users',
type: 'doc',
id: 'user-to-award-badge',
expect(result).toMatchInlineSnapshot(`
Object {
"badgeId": "test-certification-001",
"badgeMetaData": "{\\"from\\":\\"some-orgnization\\"}",
}
`);

const {
body: { _source: userWithBadge },
} = await client.get({
index: 'users',
type: 'doc',
id: 'user-to-award-badge',
});

// Assert that badgeId is written on the user
expect(userWithBadge).toMatchInlineSnapshot(`
Object {
"badges": Array [
Object {
"badgeId": "test-certification-001",
"badgeMetaData": "{\\"from\\":\\"some-orgnization\\"}",
"createdAt": "1989-06-04T00:00:00.000Z",
"isDisplayed": true,
"updatedAt": "1989-06-04T00:00:00.000Z",
},
],
"createdAt": "2020-01-01T00:00:00.000Z",
"googleId": "some-google-id",
"name": "user-to-award-badge",
}
`);
});

// Assert that badgeId is written on the user
expect(userWithBadge).toMatchInlineSnapshot(`
Object {
"badges": Array [
Object {
"badgeId": "test-certification-001",
"badgeMetaData": "{\\"from\\":\\"some-orgnization}",
"createdAt": "1989-06-04T00:00:00.000Z",
"isDisplayed": true,
"updatedAt": "1989-06-04T00:00:00.000Z",
},
],
"createdAt": "2020-01-01T00:00:00.000Z",
"googleId": "some-google-id",
"name": "user-to-award-badge",
}
`);
it('allows service token to award badge', async () => {
const result = await awardBadge({
userId: 'user-to-award-badge',
badgeId: 'test-certification-001',
badgeMetaData: '{"from":"service"}',
request: { userId: 'service-token-123' },
});

expect(result).toMatchInlineSnapshot(`
Object {
"badgeId": "test-certification-001",
"badgeMetaData": "{\\"from\\":\\"service\\"}",
}
`);

const {
body: { _source: userWithBadge },
} = await client.get({
index: 'users',
type: 'doc',
id: 'user-to-award-badge',
});

expect(userWithBadge.badges).toHaveLength(1);
expect(userWithBadge.badges[0].badgeId).toBe('test-certification-001');
});
});
53 changes: 53 additions & 0 deletions src/adm/handlers/moderation/awardBadge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,39 @@ async function appendBadgeToList(
}
}

/**
* Verify if the badge exists and if the current user is authorized to issue it
*
* @param badgeId - ID of the badge to verify
* @param requestUserId - ID of the user making the request
* @throws {HTTPError} if badge doesn't exist or user is not authorized
*/
async function verifyBadgeIssuer(badgeId: string, requestUserId: string) {
try {
const {
body: { _source: badge },
} = await client.get({
index: 'badges',
type: 'doc',
id: badgeId,
});

if (!badge) {
throw new HTTPError(404, `Badge with ID=${badgeId} does not exist`);
}

if (!badge.issuers?.includes(requestUserId)) {
throw new HTTPError(
403,
`User ${requestUserId} is not authorized to issue badge ${badgeId}`
);
}
} catch (e) {
if (e instanceof HTTPError) throw e;
throw new HTTPError(404, `Badge with ID=${badgeId} does not exist`);
}
}

type awardBadgeReturnValue = {
badgeId: string;
badgeMetaData: string;
Expand All @@ -77,11 +110,31 @@ async function main({
userId,
badgeId,
badgeMetaData,
request,
}: {
userId: string;
badgeId: string;
badgeMetaData: string;
request: { userId: string };
}): Promise<awardBadgeReturnValue> {
// Check if user exists first
try {
const { body } = await client.get({
index: 'users',
type: 'doc',
id: userId,
});
if (!body._source) {
throw new HTTPError(400, `User with ID=${userId} does not exist`);
}
} catch (e) {
if (e instanceof HTTPError) throw e;
throw new HTTPError(400, `User with ID=${userId} does not exist`);
}

// Verify if the current user/service is authorized to issue this badge
await verifyBadgeIssuer(badgeId, request.userId);

await appendBadgeToList(userId, badgeId, badgeMetaData);

return {
Expand Down
15 changes: 11 additions & 4 deletions src/adm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const router = createRouter({
),
},
},
handler: async (request) =>
handler: async (request: Request) =>
Response.json(pingHandler(await request.json())),
})
.route({
Expand Down Expand Up @@ -81,7 +81,7 @@ const router = createRouter({
}),
},
},
handler: async (request) =>
handler: async (request: Request) =>
Response.json(await blockUser(await request.json())),
})
.route({
Expand Down Expand Up @@ -112,8 +112,15 @@ const router = createRouter({
}),
},
},
handler: async (request) =>
Response.json(await awardBadge(await request.json())),
handler: async (request: Request) => {
const body = await request.json();
return Response.json(
await awardBadge({
...body,
request, // Pass the entire request object from feTS
})
);
},
});

createServer(router).listen(process.env.ADM_PORT, () => {
Expand Down

0 comments on commit 8431d58

Please sign in to comment.