From 8af873f0bdfea5d967a0d28ddf83e017f70ce542 Mon Sep 17 00:00:00 2001 From: Simon Tagne <40598597+SimonTagne@users.noreply.github.com> Date: Sat, 15 Oct 2022 15:25:43 +0200 Subject: [PATCH] wip: SAML support (backend) --- server/.env.sample | 5 + .../api/controllers/access-tokens/create.js | 10 + .../api/controllers/access-tokens/delete.js | 28 +- .../api/controllers/authentication/index.js | 8 + .../controllers/authentication/saml/acs.js | 87 +++++ .../authentication/saml/login-request.js | 41 ++ .../authentication/saml/metadata.js | 22 ++ server/api/helpers/saml/get-config.js | 29 ++ server/api/helpers/saml/parse-attributes.js | 36 ++ .../users/get-one-by-email-or-username.js | 14 +- server/api/models/Session.js | 4 + server/api/models/User.js | 12 +- server/api/responses/serverError.js | 36 ++ server/config/custom.js | 3 + server/config/policies.js | 4 + server/config/routes.js | 6 + .../20221009201347_add_sso_fields.js | 21 + server/package-lock.json | 368 ++++++++++++++++++ server/package.json | 2 + 19 files changed, 729 insertions(+), 7 deletions(-) create mode 100644 server/api/controllers/authentication/index.js create mode 100644 server/api/controllers/authentication/saml/acs.js create mode 100644 server/api/controllers/authentication/saml/login-request.js create mode 100644 server/api/controllers/authentication/saml/metadata.js create mode 100644 server/api/helpers/saml/get-config.js create mode 100644 server/api/helpers/saml/parse-attributes.js create mode 100644 server/api/responses/serverError.js create mode 100644 server/db/migrations/20221009201347_add_sso_fields.js diff --git a/server/.env.sample b/server/.env.sample index b9b6f2e96..d156cd3c3 100644 --- a/server/.env.sample +++ b/server/.env.sample @@ -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": "", "private_key": ""}, "bindings": {"username": "username", "full_name": "fullname", "email": "email", "admin": ["groups", "admin"]}}] + +DISABLE_LOCAL_AUTH=0 + ## Do not edit this TZ=UTC diff --git a/server/api/controllers/access-tokens/create.js b/server/api/controllers/access-tokens/create.js index cdb762434..c049c3166 100755 --- a/server/api/controllers/access-tokens/create.js +++ b/server/api/controllers/access-tokens/create.js @@ -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); diff --git a/server/api/controllers/access-tokens/delete.js b/server/api/controllers/access-tokens/delete.js index a42b739ed..aa96b82d3 100644 --- a/server/api/controllers/access-tokens/delete.js +++ b/server/api/controllers/access-tokens/delete.js @@ -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; }, }; diff --git a/server/api/controllers/authentication/index.js b/server/api/controllers/authentication/index.js new file mode 100644 index 000000000..9dba1697a --- /dev/null +++ b/server/api/controllers/authentication/index.js @@ -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'])), + }; + }, +}; diff --git a/server/api/controllers/authentication/saml/acs.js b/server/api/controllers/authentication/saml/acs.js new file mode 100644 index 000000000..b69adedea --- /dev/null +++ b/server/api/controllers/authentication/saml/acs.js @@ -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/'), + ); + }, +}; diff --git a/server/api/controllers/authentication/saml/login-request.js b/server/api/controllers/authentication/saml/login-request.js new file mode 100644 index 000000000..a0fcae111 --- /dev/null +++ b/server/api/controllers/authentication/saml/login-request.js @@ -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 }; + }, +}; diff --git a/server/api/controllers/authentication/saml/metadata.js b/server/api/controllers/authentication/saml/metadata.js new file mode 100644 index 000000000..af0585b67 --- /dev/null +++ b/server/api/controllers/authentication/saml/metadata.js @@ -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(); + }, +}; diff --git a/server/api/helpers/saml/get-config.js b/server/api/helpers/saml/get-config.js new file mode 100644 index 000000000..32e172f36 --- /dev/null +++ b/server/api/helpers/saml/get-config.js @@ -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'; + }, +}; diff --git a/server/api/helpers/saml/parse-attributes.js b/server/api/helpers/saml/parse-attributes.js new file mode 100644 index 000000000..71aa583bf --- /dev/null +++ b/server/api/helpers/saml/parse-attributes.js @@ -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; + }, +}; diff --git a/server/api/helpers/users/get-one-by-email-or-username.js b/server/api/helpers/users/get-one-by-email-or-username.js index 824065d1e..0da3a9390 100644 --- a/server/api/helpers/users/get-one-by-email-or-username.js +++ b/server/api/helpers/users/get-one-by-email-or-username.js @@ -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); }, }; diff --git a/server/api/models/Session.js b/server/api/models/Session.js index 61a9df40e..1ff4285cd 100755 --- a/server/api/models/Session.js +++ b/server/api/models/Session.js @@ -31,6 +31,10 @@ module.exports = { type: 'ref', columnName: 'deleted_at', }, + ssoId: { + type: 'string', + columnName: 'sso_id', + }, // ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗ // ║╣ ║║║╠╩╗║╣ ║║╚═╗ diff --git a/server/api/models/User.js b/server/api/models/User.js index 4d03e0e4b..de3d74be4 100755 --- a/server/api/models/User.js +++ b/server/api/models/User.js @@ -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`, diff --git a/server/api/responses/serverError.js b/server/api/responses/serverError.js new file mode 100644 index 000000000..003509276 --- /dev/null +++ b/server/api/responses/serverError.js @@ -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, + }); +}; diff --git a/server/config/custom.js b/server/config/custom.js index 8d8043c28..9b13d8d82 100644 --- a/server/config/custom.js +++ b/server/config/custom.js @@ -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, }; diff --git a/server/config/policies.js b/server/config/policies.js index 9095e72af..97e24a6cf 100644 --- a/server/config/policies.js +++ b/server/config/policies.js @@ -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, }; diff --git a/server/config/routes.js b/server/config/routes.js index 05450eaca..b905e9fb0 100644 --- a/server/config/routes.js +++ b/server/config/routes.js @@ -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, diff --git a/server/db/migrations/20221009201347_add_sso_fields.js b/server/db/migrations/20221009201347_add_sso_fields.js new file mode 100644 index 000000000..1b7cbfad0 --- /dev/null +++ b/server/db/migrations/20221009201347_add_sso_fields.js @@ -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'); + }); +}; diff --git a/server/package-lock.json b/server/package-lock.json index c5e1c9316..e89b86c1c 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -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", diff --git a/server/package.json b/server/package.json index 2a291ffec..9ddc76e8f 100644 --- a/server/package.json +++ b/server/package.json @@ -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",