1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-08-09 07:25:24 +02:00

wip: SAML support (backend)

This commit is contained in:
Simon Tagne 2022-10-15 15:25:43 +02:00
parent 513f9a8ee5
commit 8af873f0bd
No known key found for this signature in database
GPG key ID: 1567DE3ADB69D589
19 changed files with 729 additions and 7 deletions

View file

@ -9,6 +9,11 @@ SECRET_KEY=notsecretkey
# TRUST_PROXY=0
# TOKEN_EXPIRES_IN=365 # In days
# Uncomment for SAML login
#SAML_CONFIG=[{"id": "sso", "name": "My SSO Provider", "idp": {"sso_login_url": "https://idp.example.com/sls", "sso_logout_url": "https://idp.example.com/slo", "certificates": "<PEM idp cert"}, "sp": {"entity_id": "http://localhost:3000/", "assert_endpoint": "http://localhost:1337/api/saml/sso/acs", "certificate": "<sp PEM cert>", "private_key": "<sp PEM key>"}, "bindings": {"username": "username", "full_name": "fullname", "email": "email", "admin": ["groups", "admin"]}}]
DISABLE_LOCAL_AUTH=0
## Do not edit this
TZ=UTC

View file

@ -10,6 +10,9 @@ const Errors = {
INVALID_PASSWORD: {
invalidPassword: 'Invalid password',
},
LOCAL_AUTH_DISABLED: {
localAuthDisabled: 'Local authentication is disabled',
},
};
module.exports = {
@ -37,9 +40,16 @@ module.exports = {
invalidPassword: {
responseType: 'unauthorized',
},
localAuthDisabled: {
responseType: 'unauthorized',
},
},
async fn(inputs) {
if (!sails.config.custom.localAuth) {
throw Errors.LOCAL_AUTH_DISABLED;
}
const remoteAddress = getRemoteAddress(this.req);
const user = await sails.helpers.users.getOneByEmailOrUsername(inputs.emailOrUsername);

View file

@ -1,16 +1,38 @@
module.exports = {
async fn() {
const { accessToken } = this.req;
const { accessToken, currentUser } = this.req;
await Session.updateOne({
const session = await Session.updateOne({
accessToken,
deletedAt: null,
}).set({
deletedAt: new Date().toUTCString(),
});
return {
const result = {
item: accessToken,
};
if (currentUser.ssoId) {
try {
const { sp, idp } = await sails.helpers.saml.getConfig(currentUser.ssoId);
const ssoUrl = await new Promise((resolve, reject) => {
sp.create_logout_request_url(idp, { session_index: session.sso_id }, (err, url) => {
if (err) {
reject();
} else {
resolve(url);
}
});
});
Object.assign(result, { ssoUrl });
} catch (e) {
// not saml or bad config
}
}
return result;
},
};

View file

@ -0,0 +1,8 @@
module.exports = {
async fn() {
return {
local: sails.config.custom.localAuth,
saml: sails.config.custom.samlConfig.map((config) => _.pick(config, ['id', 'name'])),
};
},
};

View file

@ -0,0 +1,87 @@
const { getRemoteAddress } = require('../../../../utils/remoteAddress');
const Errors = {
INVALID_ID: {
invalidId: 'Invalid authentication provider ID',
},
BAD_SAML_RESPONSE: {
badSAMLResponse: 'Bad SAML response',
},
};
module.exports = {
inputs: {
id: {
type: 'string',
required: true,
},
SAMLResponse: {
type: 'string',
required: true,
},
},
exits: {
invalidId: {
responseType: 'notFound',
},
badSAMLResponse: {
responseType: 'forbidden',
},
},
async fn(inputs) {
const { sp, idp, bindings } = await sails.helpers.saml.getConfig(inputs.id);
const options = { request_body: { SAMLResponse: inputs.SAMLResponse } };
const response = await new Promise((resolve, reject) => {
sp.post_assert(idp, options, (err, res) => {
if (err !== null) {
reject(Errors.BAD_SAML_RESPONSE);
return;
}
resolve(res);
});
});
let user = await sails.helpers.users.getOne({
ssoId: inputs.id,
ssoName: response.user.name_id,
});
const values = await sails.helpers.saml.parseAttributes(bindings, response.user.attributes);
if (user === undefined) {
Object.assign(values, { ssoId: inputs.id, ssoName: response.user.name_id });
user = await User.create(values);
} else if (!_.isEqual(_.pick(user, Object.keys(values)), values)) {
user = await User.updateOne(_.pick(user, ['id'])).set(values);
}
const accessToken = sails.helpers.utils.createToken(user.id);
await Session.create({
accessToken,
remoteAddress: getRemoteAddress(this.req),
userId: user.id,
userAgent: this.req.headers['user-agent'],
ssoId: response.user.session_index,
});
this.res.cookie('accessToken', accessToken, {
secure: true,
sameSite: 'strict',
maxAge: sails.config.custom.tokenExpiresIn * 86400000,
});
this.res.cookie('accessTokenVersion', 1, {
secure: true,
sameSite: 'strict',
maxAge: sails.config.custom.tokenExpiresIn * 86400000,
});
return this.res.redirect(
sails.config.custom.baseUrl || (env.NODE_ENV === 'prod' ? '/' : 'http://localhost:3000/'),
);
},
};

View file

@ -0,0 +1,41 @@
const Errors = {
INVALID_ID: {
invalidId: 'Invalid authentication provider ID',
},
BAD_CONFIGURATION: {
badConfiguration: 'Bad SAML configuration',
},
};
module.exports = {
inputs: {
id: {
type: 'string',
required: true,
},
},
exits: {
invalidId: {
responseType: 'notFound',
},
badConfiguration: {
responseType: 'serverError',
},
},
async fn(inputs) {
const { sp, idp } = await sails.helpers.saml.getConfig(inputs.id);
const url = await new Promise((resolve, reject) => {
sp.create_login_request_url(idp, {}, (err, loginUrl) => {
if (err != null) {
reject(Errors.BAD_CONFIGURATION);
}
resolve(loginUrl);
});
});
return { item: url };
},
};

View file

@ -0,0 +1,22 @@
module.exports = {
inputs: {
id: {
type: 'string',
required: true,
},
},
exits: {
invalidId: {
responseType: 'notFound',
},
},
async fn(inputs) {
const { sp } = await sails.helpers.saml.getConfig(inputs.id);
this.res.setHeader('Content-Type', 'text/xml');
return sp.create_metadata();
},
};

View file

@ -0,0 +1,29 @@
const saml2 = require('saml2-js');
module.exports = {
inputs: {
id: {
type: 'string',
required: true,
},
},
exits: {
invalidId: {},
},
async fn(inputs) {
for (let i = 0, l = sails.config.custom.samlConfig.length; i < l; i += 1) {
const config = sails.config.custom.samlConfig[i];
if (config.id === inputs.id) {
return {
sp: new saml2.ServiceProvider(config.sp),
idp: new saml2.IdentityProvider(config.idp),
bindings: config.bindings,
};
}
}
throw 'invalidId';
},
};

View file

@ -0,0 +1,36 @@
module.exports = {
inputs: {
bindings: {
type: 'ref',
required: true,
},
attributes: {
type: 'ref',
required: true,
},
},
async fn({ bindings, attributes }) {
const values = { password: 'SSO' };
if ('email' in bindings) {
[values.email] = attributes[bindings.email];
}
if ('full_name' in bindings) {
[values.name] = attributes[bindings.full_name];
} else if ('first_name' in bindings && 'last_name' in bindings) {
values.name = `${attributes[bindings.first_name][0]} ${attributes[bindings.last_name][0]}`;
}
if ('username' in bindings) {
[values.username] = attributes[bindings.username];
}
if ('admin' in bindings) {
values.isAdmin = _.includes(attributes[bindings.admin[0]], bindings.admin[1]);
}
return values;
},
};

View file

@ -4,13 +4,21 @@ module.exports = {
type: 'string',
required: true,
},
includeSSOUsers: {
type: 'boolean',
defaultsTo: true,
},
},
async fn(inputs) {
const fieldName = inputs.emailOrUsername.includes('@') ? 'email' : 'username';
return sails.helpers.users.getOne({
[fieldName]: inputs.emailOrUsername.toLowerCase(),
});
const conditions = { [fieldName]: inputs.emailOrUsername.toLowerCase() };
if (includeSSOUsers) {
conditions[ssoId] = null;
}
return sails.helpers.users.getOne(conditions);
},
};

View file

@ -31,6 +31,10 @@ module.exports = {
type: 'ref',
columnName: 'deleted_at',
},
ssoId: {
type: 'string',
columnName: 'sso_id',
},
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
// ║╣ ║║║╠╩╗║╣ ║║╚═╗

View file

@ -71,6 +71,16 @@ module.exports = {
type: 'ref',
columnName: 'password_changed_at',
},
ssoId: {
type: 'string',
columnName: 'sso_id',
allowNull: true,
},
ssoName: {
type: 'string',
columnName: 'sso_name',
allowNull: true,
},
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
@ -106,7 +116,7 @@ module.exports = {
customToJSON() {
return {
..._.omit(this, ['password', 'avatarDirname', 'passwordChangedAt']),
..._.omit(this, ['password', 'avatarDirname', 'passwordChangedAt', 'ssoName']),
avatarUrl:
this.avatarDirname &&
`${sails.config.custom.userAvatarsUrl}/${this.avatarDirname}/square-100.jpg`,

View file

@ -0,0 +1,36 @@
/**
* serverError.js
*
* A custom response.
*
* Example usage:
* ```
* return res.serverError();
* // -or-
* return res.serverError(optionalData);
* ```
*
* Or with actions2:
* ```
* exits: {
* somethingHappened: {
* responseType: 'serverError'
* }
* }
* ```
*
* ```
* throw 'somethingHappened';
* // -or-
* throw { somethingHappened: optionalData }
* ```
*/
module.exports = function serverError(message) {
const { res } = this;
return res.status(500).json({
code: 'E_SERVER_ERROR',
message,
});
};

View file

@ -30,4 +30,7 @@ module.exports.custom = {
attachmentsPath: path.join(sails.config.appPath, 'private', 'attachments'),
attachmentsUrl: `${process.env.BASE_URL}/attachments`,
samlConfig: JSON.parse(process.env.SAML_CONFIG || '[]'),
localAuth: !process.env.DISABLE_LOCAL_ENV,
};

View file

@ -24,4 +24,8 @@ module.exports.policies = {
'projects/create': ['is-authenticated', 'is-admin'],
'access-tokens/create': true,
'authentication/saml/login-request': true,
'authentication/saml/metadata': true,
'authentication/saml/acs': true,
'authentication/index': true,
};

View file

@ -9,6 +9,8 @@
*/
module.exports.routes = {
'GET /api/auth': 'authentication/index',
'POST /api/access-tokens': 'access-tokens/create',
'DELETE /api/access-tokens/me': 'access-tokens/delete',
@ -77,6 +79,10 @@ module.exports.routes = {
'GET /api/notifications/:id': 'notifications/show',
'PATCH /api/notifications/:ids': 'notifications/update',
'GET /api/saml/:id/login': 'authentication/saml/login-request',
'GET /api/saml/:id/metadata': 'authentication/saml/metadata',
'POST /api/saml/:id/acs': 'authentication/saml/acs',
'GET /attachments/:id/download/:filename': {
action: 'attachments/download',
skipAssets: false,

View file

@ -0,0 +1,21 @@
module.exports.up = async (knex) => {
await knex.schema.table('user_account', (table) => {
table.text('sso_id');
table.text('sso_name');
});
await knex.schema.table('session', (table) => {
table.text('sso_id');
});
};
module.exports.down = (knex) => {
knex.schema.table('user_account', (table) => {
table.dropColumn('sso_id');
table.dropColumn('sso_name');
});
knex.schema.table('session', (table) => {
table.dropColumn('sso_id');
});
};

368
server/package-lock.json generated
View file

@ -20,6 +20,7 @@
"sails-hook-orm": "^4.0.1",
"sails-hook-sockets": "^2.0.1",
"sails-postgresql-redacted": "^1.0.2-9",
"saml2-js": "^3.0.1",
"sharp": "^0.30.7",
"stream-to-array": "^2.3.0",
"uuid": "^8.3.2",
@ -120,6 +121,50 @@
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@oozcitak/dom": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.8.tgz",
"integrity": "sha512-MoOnLBNsF+ok0HjpAvxYxR4piUhRDCEWK0ot3upwOOHYudJd30j6M+LNcE8RKpwfnclAX9T66nXXzkytd29XSw==",
"dependencies": {
"@oozcitak/infra": "1.0.8",
"@oozcitak/url": "1.0.4",
"@oozcitak/util": "8.3.8"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/@oozcitak/infra": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz",
"integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==",
"dependencies": {
"@oozcitak/util": "8.3.8"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/@oozcitak/url": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz",
"integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==",
"dependencies": {
"@oozcitak/infra": "1.0.8",
"@oozcitak/util": "8.3.8"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/@oozcitak/util": {
"version": "8.3.8",
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz",
"integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==",
"engines": {
"node": ">=8.0"
}
},
"node_modules/@sailshq/binary-search-tree": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@sailshq/binary-search-tree/-/binary-search-tree-0.2.7.tgz",
@ -156,12 +201,25 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"node_modules/@types/node": {
"version": "18.7.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.16.tgz",
"integrity": "sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg=="
},
"node_modules/@ungap/promise-all-settled": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
"integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
"dev": true
},
"node_modules/@xmldom/xmldom": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz",
"integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -1929,6 +1987,18 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/esquery": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
@ -4509,6 +4579,14 @@
}
}
},
"node_modules/node-forge": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/nodemon": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz",
@ -6142,6 +6220,34 @@
"node": ">=4"
}
},
"node_modules/saml2-js": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/saml2-js/-/saml2-js-3.0.1.tgz",
"integrity": "sha512-wRiRUg1M+1Tae6Tcc6Ccps4dQZOu7obrTM86IaZzkCdLULsjjP921SCItAihy1KHGpMCUWlIBFpfxcJibT8u2g==",
"dependencies": {
"async": "^3.2.0",
"debug": "^4.3.0",
"underscore": "^1.8.0",
"xml-crypto": "^2.0.0",
"xml-encryption": "^1.2.1",
"xml2js": "^0.4.0",
"xmlbuilder2": "^2.4.0",
"xmldom": "^0.4.0"
},
"engines": {
"node": ">=10.x"
}
},
"node_modules/saml2-js/node_modules/async": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
},
"node_modules/sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"node_modules/semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -7897,6 +8003,101 @@
}
}
},
"node_modules/xml-crypto": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-2.1.4.tgz",
"integrity": "sha512-ModFeGOy67L/XXHcuepnYGF7DASEDw7fhvy+qIs1ORoH55G1IIr+fN0kaMtttwvmNFFMskD9AHro8wx352/mUg==",
"dependencies": {
"@xmldom/xmldom": "^0.7.0",
"xpath": "0.0.32"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/xml-encryption": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-1.3.0.tgz",
"integrity": "sha512-3P8C4egMMxSR1BmsRM+fG16a3WzOuUEQKS2U4c3AZ5v7OseIfdUeVkD8dwxIhuLryFZSRWUL5OP6oqkgU7hguA==",
"dependencies": {
"@xmldom/xmldom": "^0.7.0",
"escape-html": "^1.0.3",
"node-forge": "^0.10.0",
"xpath": "0.0.32"
},
"engines": {
"node": ">=8"
}
},
"node_modules/xml2js": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"engines": {
"node": ">=4.0"
}
},
"node_modules/xmlbuilder2": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-2.4.1.tgz",
"integrity": "sha512-vliUplZsk5vJnhxXN/mRcij/AE24NObTUm/Zo4vkLusgayO6s3Et5zLEA14XZnY1c3hX5o1ToR0m0BJOPy0UvQ==",
"dependencies": {
"@oozcitak/dom": "1.15.8",
"@oozcitak/infra": "1.0.8",
"@oozcitak/util": "8.3.8",
"@types/node": "*",
"js-yaml": "3.14.0"
},
"engines": {
"node": ">=10.0"
}
},
"node_modules/xmlbuilder2/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/xmlbuilder2/node_modules/js-yaml": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/xmlbuilder2/node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
},
"node_modules/xmldom": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.4.0.tgz",
"integrity": "sha512-2E93k08T30Ugs+34HBSTQLVtpi6mCddaY8uO+pMNk1pqSjV5vElzn4mmh6KLxN3hki8rNcHSYzILoh3TEWORvA==",
"deprecated": "Deprecated due to CVE-2021-21366 resolved in 0.5.0",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz",
@ -7905,6 +8106,14 @@
"node": ">=0.4.0"
}
},
"node_modules/xpath": {
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz",
"integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==",
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@ -8058,6 +8267,38 @@
"tar": "^6.1.11"
}
},
"@oozcitak/dom": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.8.tgz",
"integrity": "sha512-MoOnLBNsF+ok0HjpAvxYxR4piUhRDCEWK0ot3upwOOHYudJd30j6M+LNcE8RKpwfnclAX9T66nXXzkytd29XSw==",
"requires": {
"@oozcitak/infra": "1.0.8",
"@oozcitak/url": "1.0.4",
"@oozcitak/util": "8.3.8"
}
},
"@oozcitak/infra": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz",
"integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==",
"requires": {
"@oozcitak/util": "8.3.8"
}
},
"@oozcitak/url": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz",
"integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==",
"requires": {
"@oozcitak/infra": "1.0.8",
"@oozcitak/util": "8.3.8"
}
},
"@oozcitak/util": {
"version": "8.3.8",
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz",
"integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ=="
},
"@sailshq/binary-search-tree": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@sailshq/binary-search-tree/-/binary-search-tree-0.2.7.tgz",
@ -8096,12 +8337,22 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"@types/node": {
"version": "18.7.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.16.tgz",
"integrity": "sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg=="
},
"@ungap/promise-all-settled": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
"integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
"dev": true
},
"@xmldom/xmldom": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz",
"integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A=="
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -9491,6 +9742,11 @@
"eslint-visitor-keys": "^3.3.0"
}
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"esquery": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
@ -11475,6 +11731,11 @@
"whatwg-url": "^5.0.0"
}
},
"node-forge": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA=="
},
"nodemon": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz",
@ -12751,6 +13012,33 @@
"resolved": "https://registry.npmjs.org/sails.io.js-dist/-/sails.io.js-dist-1.2.1.tgz",
"integrity": "sha512-fBMdntawlqd5N/1xL9Vu6l+J5zvy86jLUf0nFDal5McUeZzUy7PpNqq+Vx/F9KgItAyFJ7RoO3YltO9dD0Q5OQ=="
},
"saml2-js": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/saml2-js/-/saml2-js-3.0.1.tgz",
"integrity": "sha512-wRiRUg1M+1Tae6Tcc6Ccps4dQZOu7obrTM86IaZzkCdLULsjjP921SCItAihy1KHGpMCUWlIBFpfxcJibT8u2g==",
"requires": {
"async": "^3.2.0",
"debug": "^4.3.0",
"underscore": "^1.8.0",
"xml-crypto": "^2.0.0",
"xml-encryption": "^1.2.1",
"xml2js": "^0.4.0",
"xmlbuilder2": "^2.4.0",
"xmldom": "^0.4.0"
},
"dependencies": {
"async": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
}
}
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -14186,11 +14474,91 @@
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
"requires": {}
},
"xml-crypto": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-2.1.4.tgz",
"integrity": "sha512-ModFeGOy67L/XXHcuepnYGF7DASEDw7fhvy+qIs1ORoH55G1IIr+fN0kaMtttwvmNFFMskD9AHro8wx352/mUg==",
"requires": {
"@xmldom/xmldom": "^0.7.0",
"xpath": "0.0.32"
}
},
"xml-encryption": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-1.3.0.tgz",
"integrity": "sha512-3P8C4egMMxSR1BmsRM+fG16a3WzOuUEQKS2U4c3AZ5v7OseIfdUeVkD8dwxIhuLryFZSRWUL5OP6oqkgU7hguA==",
"requires": {
"@xmldom/xmldom": "^0.7.0",
"escape-html": "^1.0.3",
"node-forge": "^0.10.0",
"xpath": "0.0.32"
}
},
"xml2js": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
}
},
"xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
},
"xmlbuilder2": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-2.4.1.tgz",
"integrity": "sha512-vliUplZsk5vJnhxXN/mRcij/AE24NObTUm/Zo4vkLusgayO6s3Et5zLEA14XZnY1c3hX5o1ToR0m0BJOPy0UvQ==",
"requires": {
"@oozcitak/dom": "1.15.8",
"@oozcitak/infra": "1.0.8",
"@oozcitak/util": "8.3.8",
"@types/node": "*",
"js-yaml": "3.14.0"
},
"dependencies": {
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"js-yaml": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
}
}
},
"xmldom": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.4.0.tgz",
"integrity": "sha512-2E93k08T30Ugs+34HBSTQLVtpi6mCddaY8uO+pMNk1pqSjV5vElzn4mmh6KLxN3hki8rNcHSYzILoh3TEWORvA=="
},
"xmlhttprequest-ssl": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz",
"integrity": "sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q=="
},
"xpath": {
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz",
"integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw=="
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View file

@ -8,6 +8,7 @@
"db:migrate": "knex migrate:latest --cwd db",
"db:seed": "knex seed:run --cwd db",
"lint": "eslint . --max-warnings=0 --report-unused-disable-directives && echo '✔ Your .js files look good.'",
"lint-fix": "eslint . --max-warnings=0 --report-unused-disable-directives --fix && echo 'looks good'",
"start": "nodemon",
"start:prod": "node app.js --prod",
"test": "mocha test/lifecycle.test.js test/integration/**/*.test.js test/utils/**/*.test.js"
@ -52,6 +53,7 @@
"sails-hook-orm": "^4.0.1",
"sails-hook-sockets": "^2.0.1",
"sails-postgresql-redacted": "^1.0.2-9",
"saml2-js": "^3.0.1",
"sharp": "^0.30.7",
"stream-to-array": "^2.3.0",
"uuid": "^8.3.2",