Skip to content

Commit

Permalink
Merge pull request #15 from betagouv/fix/multiple-salts
Browse files Browse the repository at this point in the history
Fix/multiple salts
  • Loading branch information
freesteph authored May 10, 2022
2 parents 1049315 + 46e7b7a commit d368d96
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 81 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.git
.github
node_modules
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
15 changes: 6 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
FROM node:lts

WORKDIR /app
RUN npm i -g nodemon typescript

RUN chown node:node /app
USER node

COPY . .
WORKDIR /app

RUN npm install -g nodemon
RUN npm install typescript -g
RUN npm install -g
COPY --chown=node . .

USER node
RUN npm i

EXPOSE 8100
RUN npm run dev
CMD npm run migrate && npm run dev
8 changes: 2 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,13 @@ services:
- "5432:5432"
web:
build: .
command: bash -c "echo 'first npm install can be a bit long'; npm install; npm run migrate; npm run dev"
depends_on:
- db
- maildev
env_file:
- .env
environment:
DATABASE_URL: postgres://bbbanalytics:bbbanalytics@db:5432/bbbanalytics
SECURE: "false"
HOSTNAME: localhost
JWT_SECURITY_SALT: "SecretThatShouldChangedInProduction"
JWT_SECURITY_SALT: SecretThatShouldChangedInProduction
JWT_TAGS_AND_SALTS: dinum:SecretThatShouldChangedInProduction
ports:
- "8100:8100"
- "9229:9229"
Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
secret: process.env.JWT_SECURITY_SALT,
tagsAndSalts: process.env.JWT_TAGS_AND_SALTS,
host: process.env.HOSTNAME,
port: process.env.PORT || 8100,
};
4 changes: 2 additions & 2 deletions src/controllers/indexController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ module.exports.postEvents = async function (req, res) {
catch (err) {
return res.status(500).send(`${err}`);
}
return res.json({
return res.status(200).json({
status: 'ok'
}, 200)
});
};
31 changes: 30 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,41 @@ const app = express();
app.use(expressSanitizer());
app.use(bodyParser.urlencoded({ extended: false }));

const getSecretForTag = function(req, payload, done){
const { tag } = req.query;

// fallback to the default JWT_SECURITY_SALT if the new variable isn't set up
if (!config.tagsAndSalts) {
done(null, config.secret);
return;
}

const issuer = config
.tagsAndSalts
.split(",")
.map(i => i.split(":"))
.find(([t]) => t === tag);

if (!issuer) throw new Error(`No matching issuer was found for tag '${tag}'.`)

const [, secret] = issuer;

done(null, secret);
}

app.use(
expressJWT({
secret: config.secret,
secret: getSecretForTag,
algorithms: ['HS512'],
typ: "JWT",
}),
function(err, req, res, next) {
if (err) {
res.status(403).send('No matching issuer was found');
} else {
next();
}
},
);

app.use(express.json());
Expand Down
173 changes: 110 additions & 63 deletions tests/test-event.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,75 +5,122 @@ const config = require('../src/config');
const jwt = require('jsonwebtoken');

const apiResponse = {
"version": "1.0",
"meeting_id": "meeting-persistent-3--c308049",
"internal_meeting_id": "3e677e88dl7698",
"data": {
"metadata": {
"analytics_callback_url": "https://webhook.site/f519038c-b956-4fa3-9b5c-148e8df09b47",
"is_breakout": "false",
"meeting_name": "Arawa3"
version: '1.0',
meeting_id: 'meeting-persistent-c308049',
internal_meeting_id: '3e677e88dl7698',
data: {
metadata: {
analytics_callback_url:
'https://webhook.site/f519038c-b956-4fa3-9b5c-148e8df09b47',
is_breakout: 'false',
meeting_name: 'Arawa3',
},
"duration": 20,
"start": "2021-06-27 18:52:12 +0200",
"finish": "2021-06-27 18:52:32 +0200",
"attendees": [
duration: 20,
start: '2021-06-27 18:52:12 +0200',
finish: '2021-06-27 18:52:32 +0200',
attendees: [
{
"ext_user_id": "w_hll5o9o",
"name": "Lucas Pla",
"moderator": true,
"joins": [
"2021-06-23 18:52:21 +0200"
],
"leaves": [
"2021-06-23 18:52:32 +0200"
],
"duration": 11,
"recent_talking_time": "",
"engagement": {
"chats": 0,
"talks": 0,
"raisehand": 0,
"emojis": 0,
"poll_votes": 0,
"talk_time": 0
}
}
ext_user_id: 'w_hll5o9o',
name: 'Lucas Pla',
moderator: true,
joins: ['2021-06-23 18:52:21 +0200'],
leaves: ['2021-06-23 18:52:32 +0200'],
duration: 11,
recent_talking_time: '',
engagement: {
chats: 0,
talks: 0,
raisehand: 0,
emojis: 0,
poll_votes: 0,
talk_time: 0,
},
},
],
"files": [
"default.pdf"
],
"polls": [],
}}
files: ['default.pdf'],
polls: [],
},
};

const signForSecret = function (secret) {
return jwt.sign(
{
exp: Math.floor(Date.now() / 1000) + 60 * 60,
},
secret,
{
algorithm: 'HS512',
header: {
typ: 'JWT',
},
}
);
};

const sendResponseForTag = async function (tag, secret) {
return await chai
.request(app)
.post(`/v1/post_events?tag=${tag}`)
.set('content-type', 'application/json')
.set('Authorization', 'Bearer ' + signForSecret(secret))
.set('user-agent', 'BigBlueButton Analytics Callback')
.send(apiResponse);
};

describe('Meetings', async () => {
beforeEach(async () => {
config.tagsAndSalts = 'dinum:somefancysecret';

await knex.raw('truncate table meetings cascade');
});

describe('POST /v1/post_events', async () => {
describe("when the JWT_TAGS_AND_SALTS variable isn't set", () => {
beforeEach(() => {
config.tagsAndSalts = null;
});

it('defaults to JWT_SECURITY_SALT', async () => {
const res = await sendResponseForTag('some org', config.secret);

res.should.have.status(200);
});
});

describe('when the issuer is unknown', () => {
it('throws an error', async () => {
const res = await sendResponseForTag('some org', 'somefancysecret');

res.should.have.status(403);
});
});

describe('when the issuer is known but the secret is wrong', () => {
it('throws an error', async () => {
const res = await sendResponseForTag('dinum', 'some other secret');

res.should.have.status(403);
});
});

describe('when the issuer is known and the secret is correct', () => {
it('should create a new entry in meetings', async () => {
const res = await sendResponseForTag('dinum', 'somefancysecret');

res.should.have.status(200);

const stats = await knex('meetings')
.orderBy('created_at', 'desc')
.first();

describe('POST /v1/post_events unauthenticated', async () => {

it('should create a new entry in meetings', async () => {
let res
try {
const token = jwt.sign({
exp: Math.floor(Date.now() / 1000) + (60 * 60),
}, config.secret, { algorithm: 'HS512', header: {
typ: 'JWT'
}})
res = await chai.request(app)
.post('/v1/post_events?tag=dinum')
.set('content-type', 'application/json')
.set("Authorization", "Bearer " + token)
.set('user-agent', 'BigBlueButton Analytics Callback')
.send(apiResponse)
} catch (e) {
console.log(e)
}
res.should.have.status(200);
const stats = await knex('meetings').orderBy('created_at', 'desc').first()
stats.duration.should.be.equal(apiResponse.data.duration)
stats.moderator_count.should.be.equal(1)
stats.internal_meeting_id.should.be.equal(apiResponse.internal_meeting_id)
stats.tag.should.be.equal('dinum')
stats.weekday.should.be.equal(0)
stats.duration.should.be.equal(apiResponse.data.duration);
stats.moderator_count.should.be.equal(1);
stats.internal_meeting_id.should.be.equal(
apiResponse.internal_meeting_id
);
stats.tag.should.be.equal('dinum');
stats.weekday.should.be.equal(0);
});
});
});
});

0 comments on commit d368d96

Please sign in to comment.