Skip to content

Commit 553bbce

Browse files
authored
Add webhooks (sqlpad#790)
* Add webhooks helper class * webhook Stringify JSON * Fire userCreated webhook on user creation * Add webhooks to Req typedef * Add createdAt to user payload * Initial createUser test * Add query created webhook * Remove URL from var name * Revert "Remove URL from var name" This reverts commit 0e51513. * Remove name for hookserver * Add hook name to header * Add start of webhooks documentation * Add env var to doc * words * Add hook configs for batch and statement * Add batch webhook implementation * Update webhooks docs * Update webhooks.js * Update webhooks.js * Add webhook enabled config * Envelope hook payload * Fire userCreated hook for auto user creation * Add statement hooks and tests * Add wait for final test * Remove TODO
1 parent c0c4965 commit 553bbce

16 files changed

Lines changed: 960 additions & 13 deletions

File tree

docs/webhooks.md

Lines changed: 406 additions & 0 deletions
Large diffs are not rendered by default.

server/app.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const pino = require('pino');
66
const session = require('express-session');
77
const FileStore = require('session-file-store')(session);
88
const appLog = require('./lib/app-log');
9+
const Webhooks = require('./lib/webhooks.js');
910
const bodyParser = require('body-parser');
1011
const favicon = require('serve-favicon');
1112
const passport = require('passport');
@@ -37,6 +38,8 @@ async function makeApp(config, models) {
3738
throw new Error('models is required to create app');
3839
}
3940

41+
const webhooks = new Webhooks(config, models, appLog);
42+
4043
const expressPino = expressPinoLogger({
4144
level: config.get('webLogLevel'),
4245
timestamp: pino.stdTimeFunctions.isoTime,
@@ -72,6 +75,7 @@ async function makeApp(config, models) {
7275
req.config = config;
7376
req.models = models;
7477
req.appLog = appLog;
78+
req.webhooks = webhooks;
7579

7680
res.utils = new ResponseUtils(res, next);
7781

server/auth-strategies/auth-proxy.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const appLog = require('../lib/app-log');
1515
*/
1616
async function authProxyStrategy(req, done) {
1717
try {
18-
const { config, models } = req;
18+
const { config, models, webhooks } = req;
1919

2020
const headerUser = getHeaderUser(req);
2121

@@ -80,6 +80,7 @@ async function authProxyStrategy(req, done) {
8080
const newUser = await models.users.create({
8181
...headerUser,
8282
});
83+
webhooks.userCreated(newUser);
8384
return done(null, newUser);
8485
}
8586

server/auth-strategies/google.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ async function passportGoogleStrategyHandler(
1010
profile,
1111
done
1212
) {
13-
const { models, config } = req;
13+
const { models, config, webhooks } = req;
1414
const email = profile && profile._json && profile._json.email;
1515

1616
if (!email) {
@@ -42,6 +42,7 @@ async function passportGoogleStrategyHandler(
4242
role: openAdminRegistration ? 'admin' : 'editor',
4343
signupAt: new Date(),
4444
});
45+
webhooks.userCreated(newUser);
4546
return done(null, newUser);
4647
}
4748
// at this point we don't have an error, but authentication is invalid

server/auth-strategies/oidc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ async function passportOidcStrategyHandler(
1212
refreshToken,
1313
done
1414
) {
15-
const { models, config, appLog } = req;
15+
const { models, config, appLog, webhooks } = req;
1616
const _json = profile._json || {};
1717

1818
// _json.sub appears to be an id. Is it? Should .sub be used for SQLPad user id?
@@ -55,6 +55,7 @@ async function passportOidcStrategyHandler(
5555
role: openAdminRegistration ? 'admin' : 'editor',
5656
signupAt: new Date(),
5757
});
58+
webhooks.userCreated(newUser);
5859
appLog.debug(`OIDC User ${email} created`);
5960
return done(null, newUser);
6061
}

server/auth-strategies/saml.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function enableSaml(config) {
3737
}
3838
appLog.info('User attempts log in via SAML %s', email);
3939

40-
const { models } = req;
40+
const { models, webhooks } = req;
4141

4242
let [openAdminRegistration, user] = await Promise.all([
4343
models.users.adminRegistrationOpen(),
@@ -65,7 +65,7 @@ function enableSaml(config) {
6565
config.get('samlDefaultRole_d'),
6666
signupAt: new Date(),
6767
});
68-
68+
webhooks.userCreated(newUser);
6969
return done(null, newUser);
7070
}
7171

server/lib/config/config-items.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,46 @@ const configItems = [
478478
envVar: 'SQLPAD_OIDC_LINK_HTML',
479479
default: 'Sign in with OpenID',
480480
},
481+
{
482+
key: 'webhookEnabled',
483+
envVar: 'SQLPAD_WEBHOOK_ENABLED',
484+
default: false,
485+
},
486+
{
487+
key: 'webhookSecret',
488+
envVar: 'SQLPAD_WEBHOOK_SECRET',
489+
default: '',
490+
},
491+
{
492+
key: 'webhookUserCreatedUrl',
493+
envVar: 'SQLPAD_WEBHOOK_USER_CREATED_URL',
494+
default: '',
495+
},
496+
{
497+
key: 'webhookQueryCreatedUrl',
498+
envVar: 'SQLPAD_WEBHOOK_QUERY_CREATED_URL',
499+
default: '',
500+
},
501+
{
502+
key: 'webhookBatchCreatedUrl',
503+
envVar: 'SQLPAD_WEBHOOK_BATCH_CREATED_URL',
504+
default: '',
505+
},
506+
{
507+
key: 'webhookBatchFinishedUrl',
508+
envVar: 'SQLPAD_WEBHOOK_BATCH_FINISHED_URL',
509+
default: '',
510+
},
511+
{
512+
key: 'webhookStatementCreatedUrl',
513+
envVar: 'SQLPAD_WEBHOOK_STATEMENT_CREATED_URL',
514+
default: '',
515+
},
516+
{
517+
key: 'webhookStatementFinishedUrl',
518+
envVar: 'SQLPAD_WEBHOOK_STATEMENT_FINISHED_URL',
519+
default: '',
520+
},
481521
];
482522

483523
module.exports = configItems;

server/lib/execute-batch.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ const ConnectionClient = require('./connection-client');
66
* Batch must already be created.
77
* @param {Object} config
88
* @param {import('../models/index')} models
9+
* @param {import('./webhooks')} webhooks
910
* @param {string} batchId
1011
*/
11-
async function executeBatch(config, models, batchId) {
12+
async function executeBatch(config, models, webhooks, batchId) {
1213
const batch = await models.batches.findOneById(batchId);
1314
const user = await models.users.findOneById(batch.userId);
1415
const connection = await models.connections.findOneById(batch.connectionId);
@@ -52,6 +53,7 @@ async function executeBatch(config, models, batchId) {
5253
stopTime,
5354
stopTime - statementStartTime
5455
);
56+
webhooks.statementFinished(user, connection, batch, statement.id);
5557
} catch (error) {
5658
statementError = error;
5759
stopTime = new Date();
@@ -65,22 +67,25 @@ async function executeBatch(config, models, batchId) {
6567
stopTime - statementStartTime
6668
);
6769

68-
await models.batches.update(batch.id, {
70+
const updatedBatch = await models.batches.update(batch.id, {
6971
status: 'error',
7072
stopTime,
7173
durationMs: stopTime - batchStartTime,
7274
});
75+
webhooks.statementFinished(user, connection, updatedBatch, statement.id);
76+
webhooks.batchFinished(user, connection, updatedBatch);
7377
break;
7478
}
7579
}
7680
stopTime = new Date();
7781

7882
if (!statementError) {
79-
await models.batches.update(batch.id, {
83+
const updatedBatch = await models.batches.update(batch.id, {
8084
status: 'finished',
8185
stopTime,
8286
durationMs: stopTime - batchStartTime,
8387
});
88+
webhooks.batchFinished(user, connection, updatedBatch);
8489
}
8590

8691
if (disconnectOnFinish) {

0 commit comments

Comments
 (0)