@steedos/accounts 2.2.52-beta.7 → 2.2.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +2 -5
- package/lib/core/index.js +34 -28
- package/lib/core/index.js.map +1 -1
- package/lib/database-mongo/mongo.js +105 -101
- package/lib/database-mongo/mongo.js.map +1 -1
- package/lib/index.js +10 -20
- package/lib/index.js.map +1 -1
- package/lib/mail.js +5 -5
- package/lib/mail.js.map +1 -1
- package/lib/password/accounts-password.js +54 -41
- package/lib/password/accounts-password.js.map +1 -1
- package/lib/password/index.js +1 -1
- package/lib/password/index.js.map +1 -1
- package/lib/password/utils/encryption.js +3 -3
- package/lib/rest-express/endpoints/authorize.js +2 -2
- package/lib/rest-express/endpoints/get-user.js +2 -2
- package/lib/rest-express/endpoints/impersonate.js +2 -2
- package/lib/rest-express/endpoints/initServer.js +2 -2
- package/lib/rest-express/endpoints/login.js +2 -2
- package/lib/rest-express/endpoints/logout.js +2 -2
- package/lib/rest-express/endpoints/oauth/provider-callback.js +3 -3
- package/lib/rest-express/endpoints/oauth/provider-callback.js.map +1 -1
- package/lib/rest-express/endpoints/password/change-password.js +2 -2
- package/lib/rest-express/endpoints/password/register.js +2 -2
- package/lib/rest-express/endpoints/password/reset.js +4 -4
- package/lib/rest-express/endpoints/password/two-factor.js +6 -6
- package/lib/rest-express/endpoints/password/verify-email.js +6 -6
- package/lib/rest-express/endpoints/password/verify.js +4 -4
- package/lib/rest-express/endpoints/put-user-name.js +2 -2
- package/lib/rest-express/endpoints/refresh-access-token.js +2 -2
- package/lib/rest-express/endpoints/service-authenticate.js +2 -2
- package/lib/rest-express/endpoints/spaces.js +2 -2
- package/lib/rest-express/endpoints/steedos/create-tenant.js +2 -2
- package/lib/rest-express/endpoints/steedos/get-tenant.js +2 -4
- package/lib/rest-express/endpoints/steedos/get-tenant.js.map +1 -1
- package/lib/rest-express/endpoints/steedos/settings.js +9 -4
- package/lib/rest-express/endpoints/steedos/settings.js.map +1 -1
- package/lib/rest-express/endpoints/update-session.js +2 -2
- package/lib/rest-express/express-middleware.js +1 -1
- package/lib/rest-express/express-middleware.js.map +1 -1
- package/lib/rest-express/user-loader.js +2 -2
- package/lib/rest-express/utils/steedos-auth.js +2 -2
- package/lib/rest-express/utils/steedos-auth.js.map +1 -1
- package/lib/rest-express/utils/users.js +4 -4
- package/lib/server/accounts-server.js +56 -30
- package/lib/server/accounts-server.js.map +1 -1
- package/lib/server/utils/email.js +2 -2
- package/lib/types/index.js +14 -14
- package/lib/types/index.js.map +1 -1
- package/package.json +14 -20
- package/src/core/index.ts +12 -5
- package/src/database-mongo/mongo.ts +29 -26
- package/src/index.ts +6 -16
- package/src/mail.ts +3 -3
- package/src/password/accounts-password.ts +17 -5
- package/src/rest-express/endpoints/steedos/get-tenant.ts +0 -2
- package/src/rest-express/endpoints/steedos/settings.ts +15 -1
- package/src/server/accounts-server.ts +26 -3
- package/src/server/types/jwt-data.ts +10 -1
- package/src/types/types/connection-informations.ts +9 -0
- package/lib/oauth2/client.js +0 -1
- package/lib/oauth2/client.js.map +0 -1
- package/lib/oauth2/config.js +0 -14
- package/lib/oauth2/config.js.map +0 -1
- package/lib/oauth2/consent.js +0 -192
- package/lib/oauth2/consent.js.map +0 -1
- package/lib/oauth2/login.js +0 -166
- package/lib/oauth2/login.js.map +0 -1
- package/lib/oauth2/logout.js +0 -60
- package/lib/oauth2/logout.js.map +0 -1
- package/lib/oauth2/stub/oidc-cert.js +0 -67
- package/lib/oauth2/stub/oidc-cert.js.map +0 -1
- package/lib/saml-idp/config.js +0 -82
- package/lib/saml-idp/config.js.map +0 -1
- package/lib/saml-idp/connectedApps.js +0 -20
- package/lib/saml-idp/connectedApps.js.map +0 -1
- package/lib/saml-idp/express-middleware.js +0 -684
- package/lib/saml-idp/express-middleware.js.map +0 -1
- package/lib/saml-idp/index.js +0 -13
- package/lib/saml-idp/index.js.map +0 -1
- package/lib/saml-idp/simpleProfileMapper.js +0 -75
- package/lib/saml-idp/simpleProfileMapper.js.map +0 -1
- package/src/oauth2/client.ts +0 -0
- package/src/oauth2/config.ts +0 -15
- package/src/oauth2/consent.ts +0 -208
- package/src/oauth2/login.ts +0 -179
- package/src/oauth2/logout.ts +0 -66
- package/src/oauth2/stub/oidc-cert.ts +0 -86
- package/src/saml-idp/config.js +0 -85
- package/src/saml-idp/connectedApps.ts +0 -16
- package/src/saml-idp/express-middleware.js +0 -811
- package/src/saml-idp/index.ts +0 -14
- package/src/saml-idp/simpleProfileMapper.js +0 -82
- package/src/saml-idp/views/error.hbs +0 -11
- package/src/saml-idp/views/layout.hbs +0 -72
- package/src/saml-idp/views/samlresponse.hbs +0 -20
- package/src/saml-idp/views/settings.hbs +0 -175
- package/src/saml-idp/views/user.hbs +0 -261
- package/views/oauth2/consent.pug +0 -141
|
@@ -1,811 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Module dependencies.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const chalk = require('chalk'),
|
|
7
|
-
express = require('express'),
|
|
8
|
-
os = require('os'),
|
|
9
|
-
fs = require('fs'),
|
|
10
|
-
http = require('http'),
|
|
11
|
-
https = require('https'),
|
|
12
|
-
path = require('path'),
|
|
13
|
-
extend = require('extend'),
|
|
14
|
-
hbs = require('hbs'),
|
|
15
|
-
logger = require('morgan'),
|
|
16
|
-
bodyParser = require('body-parser'),
|
|
17
|
-
session = require('express-session'),
|
|
18
|
-
yargs = require('yargs/yargs'),
|
|
19
|
-
xmlFormat = require('xml-formatter'),
|
|
20
|
-
samlp = require('samlp'),
|
|
21
|
-
Parser = require('xmldom').DOMParser,
|
|
22
|
-
SessionParticipants = require('samlp/lib/sessionParticipants'),
|
|
23
|
-
SimpleProfileMapper = require('./simpleProfileMapper.js');
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Globals
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
const IDP_PATHS = {
|
|
30
|
-
SSO: '/idp/sso',
|
|
31
|
-
SLO: '/idp/slo',
|
|
32
|
-
METADATA: '/metadata',
|
|
33
|
-
SIGN_IN: '/signin',
|
|
34
|
-
SIGN_OUT: '/signout',
|
|
35
|
-
SETTINGS: '/settings'
|
|
36
|
-
}
|
|
37
|
-
const CERT_OPTIONS = [
|
|
38
|
-
'cert',
|
|
39
|
-
'key',
|
|
40
|
-
'encryptionCert',
|
|
41
|
-
'encryptionPublicKey',
|
|
42
|
-
'httpsPrivateKey',
|
|
43
|
-
'httpsCert',
|
|
44
|
-
];
|
|
45
|
-
const WILDCARD_ADDRESSES = ['0.0.0.0', '::'];
|
|
46
|
-
const UNDEFINED_VALUE = 'None';
|
|
47
|
-
const CRYPT_TYPES = {
|
|
48
|
-
certificate: /-----BEGIN CERTIFICATE-----[^-]*-----END CERTIFICATE-----/,
|
|
49
|
-
'RSA private key': /-----BEGIN RSA PRIVATE KEY-----\n[^-]*\n-----END RSA PRIVATE KEY-----/,
|
|
50
|
-
'public key': /-----BEGIN PUBLIC KEY-----\n[^-]*\n-----END PUBLIC KEY-----/,
|
|
51
|
-
};
|
|
52
|
-
const KEY_CERT_HELP_TEXT = dedent(chalk`
|
|
53
|
-
To generate a key/cert pair for the IdP, run the following command:
|
|
54
|
-
|
|
55
|
-
{gray openssl req -x509 -new -newkey rsa:2048 -nodes \
|
|
56
|
-
-subj '/C=US/ST=California/L=San Francisco/O=JankyCo/CN=Test Identity Provider' \
|
|
57
|
-
-keyout idp-private-key.pem \
|
|
58
|
-
-out idp-public-cert.pem -days 7300}`
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
function matchesCertType(value, type) {
|
|
62
|
-
return CRYPT_TYPES[type] && CRYPT_TYPES[type].test(value);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function resolveFilePath(filePath) {
|
|
66
|
-
|
|
67
|
-
if (filePath.startsWith('saml-idp/')) {
|
|
68
|
-
// Allows file path options to files included in this package, like config.js
|
|
69
|
-
const resolvedPath = require.resolve(filePath.replace(/^saml\-idp\//, `${__dirname}/`));
|
|
70
|
-
return fs.existsSync(resolvedPath) && resolvedPath;
|
|
71
|
-
}
|
|
72
|
-
var possiblePath;
|
|
73
|
-
if (fs.existsSync(filePath)) {
|
|
74
|
-
return filePath;
|
|
75
|
-
}
|
|
76
|
-
if (filePath.startsWith('~/')) {
|
|
77
|
-
possiblePath = path.resolve(process.env.HOME, filePath.slice(2));
|
|
78
|
-
if (fs.existsSync(possiblePath)) {
|
|
79
|
-
return possiblePath;
|
|
80
|
-
} else {
|
|
81
|
-
// for ~/ paths, don't try to resolve further
|
|
82
|
-
return filePath;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return ['.', __dirname]
|
|
86
|
-
.map(base => path.resolve(base, filePath))
|
|
87
|
-
.find(possiblePath => fs.existsSync(possiblePath));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function makeCertFileCoercer(type, description, helpText) {
|
|
91
|
-
return function certFileCoercer(value) {
|
|
92
|
-
if (matchesCertType(value, type)) {
|
|
93
|
-
return value;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const filePath = resolveFilePath(value);
|
|
97
|
-
if (filePath) {
|
|
98
|
-
return fs.readFileSync(filePath)
|
|
99
|
-
}
|
|
100
|
-
throw new Error(
|
|
101
|
-
chalk`{red Invalid / missing {bold ${description}}} - {yellow not a valid crypt key/cert or file path}${helpText ? '\n' + helpText : ''}`
|
|
102
|
-
)
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function getHashCode(str) {
|
|
107
|
-
var hash = 0;
|
|
108
|
-
if (str.length == 0) return hash;
|
|
109
|
-
for (i = 0; i < str.length; i++) {
|
|
110
|
-
char = str.charCodeAt(i);
|
|
111
|
-
hash = ((hash<<5)-hash)+char;
|
|
112
|
-
hash = hash & hash; // Convert to 32bit integer
|
|
113
|
-
}
|
|
114
|
-
return hash;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function dedent(str) {
|
|
118
|
-
// Reduce the indentation of all lines by the indentation of the first line
|
|
119
|
-
const match = str.match(/^\n?( +)/);
|
|
120
|
-
if (!match) {
|
|
121
|
-
return str;
|
|
122
|
-
}
|
|
123
|
-
const indentRe = new RegExp(`\n${match[1]}`, 'g');
|
|
124
|
-
return str.replace(indentRe, '\n').replace(/^\n/, '');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function formatOptionValue(key, value) {
|
|
128
|
-
if (typeof value === 'string') {
|
|
129
|
-
return value;
|
|
130
|
-
}
|
|
131
|
-
if (CERT_OPTIONS.includes(key)) {
|
|
132
|
-
return chalk`${
|
|
133
|
-
value.toString()
|
|
134
|
-
.replace(/-----.+?-----|\n/g, '')
|
|
135
|
-
.substring(0, 80)
|
|
136
|
-
}{white …}`;
|
|
137
|
-
}
|
|
138
|
-
if (!value && value !== false) {
|
|
139
|
-
return UNDEFINED_VALUE;
|
|
140
|
-
}
|
|
141
|
-
if (typeof value === 'function') {
|
|
142
|
-
const lines = `${value}`.split('\n');
|
|
143
|
-
return lines[0].slice(0, -2);
|
|
144
|
-
}
|
|
145
|
-
return `${JSON.stringify(value)}`;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function prettyPrintXml(xml, indent) {
|
|
149
|
-
// This works well, because we format the xml before applying the replacements
|
|
150
|
-
const prettyXml = xmlFormat(xml, {indentation: ' '})
|
|
151
|
-
// Matches `<{prefix}:{name} .*?>`
|
|
152
|
-
.replace(/<(\/)?((?:[\w]+)(?::))?([\w]+)(.*?)>/g, chalk`<{green $1$2{bold $3}}$4>`)
|
|
153
|
-
// Matches ` {attribute}="{value}"
|
|
154
|
-
.replace(/ ([\w:]+)="(.+?)"/g, chalk` {white $1}={cyan "$2"}`);
|
|
155
|
-
if (indent) {
|
|
156
|
-
return prettyXml.replace(/(^|\n)/g, `$1${' '.repeat(indent)}`);
|
|
157
|
-
}
|
|
158
|
-
return prettyXml;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Arguments
|
|
163
|
-
*/
|
|
164
|
-
function processArgs(args, options) {
|
|
165
|
-
var baseArgv;
|
|
166
|
-
|
|
167
|
-
if (options) {
|
|
168
|
-
baseArgv = yargs(args).config(options);
|
|
169
|
-
} else {
|
|
170
|
-
baseArgv = yargs(args);
|
|
171
|
-
}
|
|
172
|
-
return baseArgv
|
|
173
|
-
.usage('\nSimple IdP for SAML 2.0 WebSSO & SLO Profile\n\n' +
|
|
174
|
-
'Launches an IdP web server that mints SAML assertions or logout responses for a Service Provider (SP)\n\n' +
|
|
175
|
-
'Usage:\n\t$0 --acsUrl {url} --audience {uri}')
|
|
176
|
-
.alias({h: 'help'})
|
|
177
|
-
.options({
|
|
178
|
-
host: {
|
|
179
|
-
description: 'IdP Web Server Listener Host',
|
|
180
|
-
required: false,
|
|
181
|
-
default: 'localhost'
|
|
182
|
-
},
|
|
183
|
-
port: {
|
|
184
|
-
description: 'IdP Web Server Listener Port',
|
|
185
|
-
required: true,
|
|
186
|
-
alias: 'p',
|
|
187
|
-
default: 7000
|
|
188
|
-
},
|
|
189
|
-
cert: {
|
|
190
|
-
description: 'IdP Signature PublicKey Certificate',
|
|
191
|
-
required: true,
|
|
192
|
-
default: './idp-public-cert.pem',
|
|
193
|
-
coerce: makeCertFileCoercer('certificate', 'IdP Signature PublicKey Certificate', KEY_CERT_HELP_TEXT)
|
|
194
|
-
},
|
|
195
|
-
key: {
|
|
196
|
-
description: 'IdP Signature PrivateKey Certificate',
|
|
197
|
-
required: true,
|
|
198
|
-
default: './idp-private-key.pem',
|
|
199
|
-
coerce: makeCertFileCoercer('RSA private key', 'IdP Signature PrivateKey Certificate', KEY_CERT_HELP_TEXT)
|
|
200
|
-
},
|
|
201
|
-
issuer: {
|
|
202
|
-
description: 'IdP Issuer URI',
|
|
203
|
-
required: true,
|
|
204
|
-
alias: 'iss',
|
|
205
|
-
default: 'urn:example:idp'
|
|
206
|
-
},
|
|
207
|
-
acsUrl: {
|
|
208
|
-
description: 'SP Assertion Consumer URL',
|
|
209
|
-
required: true,
|
|
210
|
-
alias: 'acs'
|
|
211
|
-
},
|
|
212
|
-
sloUrl: {
|
|
213
|
-
description: 'SP Single Logout URL',
|
|
214
|
-
required: false,
|
|
215
|
-
alias: 'slo'
|
|
216
|
-
},
|
|
217
|
-
audience: {
|
|
218
|
-
description: 'SP Audience URI',
|
|
219
|
-
required: true,
|
|
220
|
-
alias: 'aud'
|
|
221
|
-
},
|
|
222
|
-
serviceProviderId: {
|
|
223
|
-
description: 'SP Issuer/Entity URI',
|
|
224
|
-
required: false,
|
|
225
|
-
alias: 'spId',
|
|
226
|
-
string: true
|
|
227
|
-
},
|
|
228
|
-
relayState: {
|
|
229
|
-
description: 'Default SAML RelayState for SAMLResponse',
|
|
230
|
-
required: false,
|
|
231
|
-
alias: 'rs'
|
|
232
|
-
},
|
|
233
|
-
disableRequestAcsUrl: {
|
|
234
|
-
description: 'Disables ability for SP AuthnRequest to specify Assertion Consumer URL',
|
|
235
|
-
required: false,
|
|
236
|
-
boolean: true,
|
|
237
|
-
alias: 'static',
|
|
238
|
-
default: false
|
|
239
|
-
},
|
|
240
|
-
encryptAssertion: {
|
|
241
|
-
description: 'Encrypts assertion with SP Public Key',
|
|
242
|
-
required: false,
|
|
243
|
-
boolean: true,
|
|
244
|
-
alias: 'enc',
|
|
245
|
-
default: false
|
|
246
|
-
},
|
|
247
|
-
encryptionCert: {
|
|
248
|
-
description: 'SP Certificate (pem) for Assertion Encryption',
|
|
249
|
-
required: false,
|
|
250
|
-
string: true,
|
|
251
|
-
alias: 'encCert',
|
|
252
|
-
coerce: makeCertFileCoercer('certificate', 'Encryption cert')
|
|
253
|
-
},
|
|
254
|
-
encryptionPublicKey: {
|
|
255
|
-
description: 'SP RSA Public Key (pem) for Assertion Encryption ' +
|
|
256
|
-
'(e.g. openssl x509 -pubkey -noout -in sp-cert.pem)',
|
|
257
|
-
required: false,
|
|
258
|
-
string: true,
|
|
259
|
-
alias: 'encKey',
|
|
260
|
-
coerce: makeCertFileCoercer('public key', 'Encryption public key')
|
|
261
|
-
},
|
|
262
|
-
httpsPrivateKey: {
|
|
263
|
-
description: 'Web Server TLS/SSL Private Key (pem)',
|
|
264
|
-
required: false,
|
|
265
|
-
string: true,
|
|
266
|
-
coerce: makeCertFileCoercer('RSA private key')
|
|
267
|
-
},
|
|
268
|
-
httpsCert: {
|
|
269
|
-
description: 'Web Server TLS/SSL Certificate (pem)',
|
|
270
|
-
required: false,
|
|
271
|
-
string: true,
|
|
272
|
-
coerce: makeCertFileCoercer('certificate')
|
|
273
|
-
},
|
|
274
|
-
https: {
|
|
275
|
-
description: 'Enables HTTPS Listener (requires httpsPrivateKey and httpsCert)',
|
|
276
|
-
required: true,
|
|
277
|
-
boolean: true,
|
|
278
|
-
default: false
|
|
279
|
-
},
|
|
280
|
-
signResponse: {
|
|
281
|
-
description: 'Enables signing of responses',
|
|
282
|
-
required: false,
|
|
283
|
-
boolean: true,
|
|
284
|
-
default: true,
|
|
285
|
-
alias: 'signResponse'
|
|
286
|
-
},
|
|
287
|
-
configFile: {
|
|
288
|
-
description: 'Path to a SAML attribute config file',
|
|
289
|
-
required: true,
|
|
290
|
-
default: 'saml-idp/config.js',
|
|
291
|
-
alias: 'conf'
|
|
292
|
-
},
|
|
293
|
-
rollSession: {
|
|
294
|
-
description: 'Create a new session for every authn request instead of reusing an existing session',
|
|
295
|
-
required: false,
|
|
296
|
-
boolean: true,
|
|
297
|
-
default: false
|
|
298
|
-
},
|
|
299
|
-
authnContextClassRef: {
|
|
300
|
-
description: 'Authentication Context Class Reference',
|
|
301
|
-
required: false,
|
|
302
|
-
string: true,
|
|
303
|
-
default: 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport',
|
|
304
|
-
alias: 'acr'
|
|
305
|
-
},
|
|
306
|
-
authnContextDecl: {
|
|
307
|
-
description: 'Authentication Context Declaration (XML FilePath)',
|
|
308
|
-
required: false,
|
|
309
|
-
string: true,
|
|
310
|
-
alias: 'acd',
|
|
311
|
-
coerce: function (value) {
|
|
312
|
-
const filePath = resolveFilePath(value);
|
|
313
|
-
if (filePath) {
|
|
314
|
-
return fs.readFileSync(filePath, 'utf8')
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
})
|
|
319
|
-
.example('$0 --acsUrl http://acme.okta.com/auth/saml20/exampleidp --audience https://www.okta.com/saml2/service-provider/spf5aFRRXFGIMAYXQPNV', '')
|
|
320
|
-
.check(function(argv, aliases) {
|
|
321
|
-
if (argv.encryptAssertion) {
|
|
322
|
-
if (argv.encryptionPublicKey === undefined) {
|
|
323
|
-
return 'encryptionPublicKey argument is also required for assertion encryption';
|
|
324
|
-
}
|
|
325
|
-
if (argv.encryptionCert === undefined) {
|
|
326
|
-
return 'encryptionCert argument is also required for assertion encryption';
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return true;
|
|
330
|
-
})
|
|
331
|
-
.check(function(argv, aliases) {
|
|
332
|
-
if (argv.config) {
|
|
333
|
-
return true;
|
|
334
|
-
}
|
|
335
|
-
const configFilePath = resolveFilePath(argv.configFile);
|
|
336
|
-
|
|
337
|
-
if (!configFilePath) {
|
|
338
|
-
return 'SAML attribute config file path "' + argv.configFile + '" is not a valid path.\n';
|
|
339
|
-
}
|
|
340
|
-
try {
|
|
341
|
-
argv.config = require(configFilePath);
|
|
342
|
-
} catch (error) {
|
|
343
|
-
return 'Encountered an exception while loading SAML attribute config file "' + configFilePath + '".\n' + error;
|
|
344
|
-
}
|
|
345
|
-
return true;
|
|
346
|
-
})
|
|
347
|
-
.wrap(baseArgv.terminalWidth());
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const app = express.Router();
|
|
351
|
-
|
|
352
|
-
function _runServer(argv) {
|
|
353
|
-
|
|
354
|
-
const blocks = {};
|
|
355
|
-
|
|
356
|
-
console.log(dedent(chalk`
|
|
357
|
-
Listener Port:
|
|
358
|
-
{cyan ${argv.host}:${argv.port}}
|
|
359
|
-
HTTPS Enabled:
|
|
360
|
-
{cyan ${argv.https}}
|
|
361
|
-
|
|
362
|
-
{bold [{yellow Identity Provider}]}
|
|
363
|
-
|
|
364
|
-
Issuer URI:
|
|
365
|
-
{cyan ${argv.issuer}}
|
|
366
|
-
Sign Response Message:
|
|
367
|
-
{cyan ${argv.signResponse}}
|
|
368
|
-
Encrypt Assertion:
|
|
369
|
-
{cyan ${argv.encryptAssertion}}
|
|
370
|
-
Authentication Context Class Reference:
|
|
371
|
-
{cyan ${argv.authnContextClassRef || UNDEFINED_VALUE}}
|
|
372
|
-
Authentication Context Declaration:
|
|
373
|
-
{cyan ${argv.authnContextDecl || UNDEFINED_VALUE}}
|
|
374
|
-
Default RelayState:
|
|
375
|
-
{cyan ${argv.relayState || UNDEFINED_VALUE}}
|
|
376
|
-
|
|
377
|
-
{bold [{yellow Service Provider}]}
|
|
378
|
-
|
|
379
|
-
serviceProviderId URI:
|
|
380
|
-
{cyan ${argv.serviceProviderId || UNDEFINED_VALUE}}
|
|
381
|
-
Audience URI:
|
|
382
|
-
{cyan ${argv.audience || UNDEFINED_VALUE}}
|
|
383
|
-
ACS URL:
|
|
384
|
-
{cyan ${argv.acsUrl || UNDEFINED_VALUE}}
|
|
385
|
-
SLO URL:
|
|
386
|
-
{cyan ${argv.sloUrl || UNDEFINED_VALUE}}
|
|
387
|
-
Trust ACS URL in Request:
|
|
388
|
-
{cyan ${!argv.disableRequestAcsUrl}}
|
|
389
|
-
`));
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* IdP Configuration
|
|
394
|
-
*/
|
|
395
|
-
|
|
396
|
-
const idpOptions = {
|
|
397
|
-
issuer: argv.issuer,
|
|
398
|
-
serviceProviderId: argv.serviceProviderId || argv.audience,
|
|
399
|
-
cert: argv.cert,
|
|
400
|
-
key: argv.key,
|
|
401
|
-
audience: argv.audience,
|
|
402
|
-
recipient: argv.acsUrl,
|
|
403
|
-
destination: argv.acsUrl,
|
|
404
|
-
acsUrl: argv.acsUrl,
|
|
405
|
-
sloUrl: argv.sloUrl,
|
|
406
|
-
RelayState: argv.relayState,
|
|
407
|
-
allowRequestAcsUrl: !argv.disableRequestAcsUrl,
|
|
408
|
-
digestAlgorithm: 'sha256',
|
|
409
|
-
signatureAlgorithm: 'rsa-sha256',
|
|
410
|
-
signResponse: argv.signResponse,
|
|
411
|
-
encryptAssertion: argv.encryptAssertion,
|
|
412
|
-
encryptionCert: argv.encryptionCert,
|
|
413
|
-
encryptionPublicKey: argv.encryptionPublicKey,
|
|
414
|
-
encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
|
|
415
|
-
keyEncryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p',
|
|
416
|
-
lifetimeInSeconds: 3600,
|
|
417
|
-
authnContextClassRef: argv.authnContextClassRef,
|
|
418
|
-
authnContextDecl: argv.authnContextDecl,
|
|
419
|
-
includeAttributeNameFormat: true,
|
|
420
|
-
profileMapper: SimpleProfileMapper.fromMetadata(argv.config.metadata),
|
|
421
|
-
postEndpointPath: IDP_PATHS.SSO,
|
|
422
|
-
redirectEndpointPath: IDP_PATHS.SSO,
|
|
423
|
-
logoutEndpointPaths: argv.sloUrl ?
|
|
424
|
-
{
|
|
425
|
-
redirect: IDP_PATHS.SLO,
|
|
426
|
-
post: IDP_PATHS.SLO
|
|
427
|
-
} : {},
|
|
428
|
-
getUserFromRequest: function(req) { return req.user; },
|
|
429
|
-
getPostURL: function (audience, authnRequestDom, req, callback) {
|
|
430
|
-
return callback(null, (req.authnRequest && req.authnRequest.acsUrl) ?
|
|
431
|
-
req.authnRequest.acsUrl :
|
|
432
|
-
argv.acsUrl);
|
|
433
|
-
},
|
|
434
|
-
transformAssertion: function(assertionDom) {
|
|
435
|
-
if (argv.authnContextDecl) {
|
|
436
|
-
var declDoc;
|
|
437
|
-
try {
|
|
438
|
-
declDoc = new Parser().parseFromString(argv.authnContextDecl);
|
|
439
|
-
} catch(err){
|
|
440
|
-
console.log('Unable to parse Authentication Context Declaration XML', err);
|
|
441
|
-
}
|
|
442
|
-
if (declDoc) {
|
|
443
|
-
const authnContextDeclEl = assertionDom.createElementNS('urn:oasis:names:tc:SAML:2.0:assertion', 'saml:AuthnContextDecl');
|
|
444
|
-
authnContextDeclEl.appendChild(declDoc.documentElement);
|
|
445
|
-
const authnContextEl = assertionDom.getElementsByTagName('saml:AuthnContext')[0];
|
|
446
|
-
authnContextEl.appendChild(authnContextDeclEl);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
},
|
|
450
|
-
responseHandler: function(response, opts, req, res, next) {
|
|
451
|
-
console.log(dedent(chalk`
|
|
452
|
-
Sending SAML Response to {cyan ${opts.postUrl}} =>
|
|
453
|
-
{bold RelayState} =>
|
|
454
|
-
{cyan ${opts.RelayState || UNDEFINED_VALUE}}
|
|
455
|
-
{bold SAMLResponse} =>`
|
|
456
|
-
));
|
|
457
|
-
|
|
458
|
-
console.log(prettyPrintXml(response.toString(), 4));
|
|
459
|
-
|
|
460
|
-
res.render('samlresponse', {
|
|
461
|
-
AcsUrl: opts.postUrl,
|
|
462
|
-
SAMLResponse: response.toString('base64'),
|
|
463
|
-
RelayState: opts.RelayState
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* App Environment
|
|
470
|
-
*/
|
|
471
|
-
|
|
472
|
-
// app.set('host', process.env.HOST || argv.host);
|
|
473
|
-
// app.set('port', process.env.PORT || argv.port);
|
|
474
|
-
// app.set('views', path.join(__dirname, 'views'));
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* View Engine
|
|
478
|
-
*/
|
|
479
|
-
|
|
480
|
-
// app.set('view engine', 'hbs');
|
|
481
|
-
// app.set('view options', { layout: 'layout' })
|
|
482
|
-
// app.engine('handlebars', hbs.__express);
|
|
483
|
-
|
|
484
|
-
// Register Helpers
|
|
485
|
-
hbs.registerHelper('extend', function(name, context) {
|
|
486
|
-
var block = blocks[name];
|
|
487
|
-
if (!block) {
|
|
488
|
-
block = blocks[name] = [];
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
block.push(context.fn(this));
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
hbs.registerHelper('block', function(name) {
|
|
495
|
-
const val = (blocks[name] || []).join('\n');
|
|
496
|
-
// clear the block
|
|
497
|
-
blocks[name] = [];
|
|
498
|
-
return val;
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
hbs.registerHelper('select', function(selected, options) {
|
|
503
|
-
return options.fn(this).replace(
|
|
504
|
-
new RegExp(' value=\"' + selected + '\"'), '$& selected="selected"');
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
hbs.registerHelper('getProperty', function(attribute, context) {
|
|
508
|
-
return context[attribute];
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
hbs.registerHelper('serialize', function(context) {
|
|
512
|
-
return new Buffer(JSON.stringify(context)).toString('base64');
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* Middleware
|
|
517
|
-
*/
|
|
518
|
-
|
|
519
|
-
app.use(logger(':date> :method :url - {:referrer} => :status (:response-time ms)', {
|
|
520
|
-
skip: function (req, res)
|
|
521
|
-
{
|
|
522
|
-
return req.path.startsWith('/bower_components') || req.path.startsWith('/css')
|
|
523
|
-
}
|
|
524
|
-
}));
|
|
525
|
-
app.use(bodyParser.urlencoded({extended: true}));
|
|
526
|
-
app.use(express.static(path.join(__dirname, 'public')));
|
|
527
|
-
app.use(session({
|
|
528
|
-
secret: 'The universe works on a math equation that never even ever really ends in the end',
|
|
529
|
-
resave: false,
|
|
530
|
-
saveUninitialized: true,
|
|
531
|
-
name: 'idp_sid',
|
|
532
|
-
cookie: { maxAge: 60 * 60 * 1000 }
|
|
533
|
-
}));
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* View Handlers
|
|
537
|
-
*/
|
|
538
|
-
|
|
539
|
-
const showUser = function (req, res, next) {
|
|
540
|
-
res.render('user', {
|
|
541
|
-
user: req.user,
|
|
542
|
-
participant: req.participant,
|
|
543
|
-
metadata: req.metadata,
|
|
544
|
-
authnRequest: req.authnRequest,
|
|
545
|
-
idp: req.idp.options,
|
|
546
|
-
paths: IDP_PATHS
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* Shared Handlers
|
|
552
|
-
*/
|
|
553
|
-
|
|
554
|
-
const parseSamlRequest = function(req, res, next) {
|
|
555
|
-
samlp.parseRequest(req, function(err, data) {
|
|
556
|
-
if (err) {
|
|
557
|
-
return res.render('error', {
|
|
558
|
-
message: 'SAML AuthnRequest Parse Error: ' + err.message,
|
|
559
|
-
error: err
|
|
560
|
-
});
|
|
561
|
-
};
|
|
562
|
-
if (data) {
|
|
563
|
-
req.authnRequest = {
|
|
564
|
-
relayState: req.query.RelayState || req.body.RelayState,
|
|
565
|
-
id: data.id,
|
|
566
|
-
issuer: data.issuer,
|
|
567
|
-
destination: data.destination,
|
|
568
|
-
acsUrl: data.assertionConsumerServiceURL,
|
|
569
|
-
forceAuthn: data.forceAuthn === 'true'
|
|
570
|
-
};
|
|
571
|
-
console.log('Received AuthnRequest => \n', req.authnRequest);
|
|
572
|
-
}
|
|
573
|
-
if (req.user != undefined){
|
|
574
|
-
return showUser(req, res, next);
|
|
575
|
-
} else {
|
|
576
|
-
console.log('Redirect to login => \n');
|
|
577
|
-
res.redirect("/accounts/a/#/login?redirect_uri="+ encodeURIComponent(req.originalUrl));
|
|
578
|
-
return res.end();
|
|
579
|
-
}
|
|
580
|
-
})
|
|
581
|
-
};
|
|
582
|
-
|
|
583
|
-
const getSessionIndex = function(req) {
|
|
584
|
-
if (req && req.session) {
|
|
585
|
-
return Math.abs(getHashCode(req.session.id)).toString();
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
const getParticipant = function(req) {
|
|
590
|
-
return {
|
|
591
|
-
serviceProviderId: req.idp.options.serviceProviderId,
|
|
592
|
-
sessionIndex: getSessionIndex(req),
|
|
593
|
-
nameId: req.user.username,
|
|
594
|
-
nameIdFormat: req.user.nameIdFormat,
|
|
595
|
-
serviceProviderLogoutURL: req.idp.options.sloUrl
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
const parseLogoutRequest = function(req, res, next) {
|
|
600
|
-
if (!req.idp.options.sloUrl) {
|
|
601
|
-
return res.render('error', {
|
|
602
|
-
message: 'SAML Single Logout Service URL not defined for Service Provider'
|
|
603
|
-
});
|
|
604
|
-
};
|
|
605
|
-
|
|
606
|
-
console.log('Processing SAML SLO request for participant => \n', req.participant);
|
|
607
|
-
|
|
608
|
-
return samlp.logout({
|
|
609
|
-
issuer: req.idp.options.issuer,
|
|
610
|
-
cert: req.idp.options.cert,
|
|
611
|
-
key: req.idp.options.key,
|
|
612
|
-
digestAlgorithm: req.idp.options.digestAlgorithm,
|
|
613
|
-
signatureAlgorithm: req.idp.options.signatureAlgorithm,
|
|
614
|
-
sessionParticipants: new SessionParticipants(
|
|
615
|
-
[
|
|
616
|
-
req.participant
|
|
617
|
-
]),
|
|
618
|
-
clearIdPSession: function(callback) {
|
|
619
|
-
console.log('Destroying session ' + req.session.id + ' for participant', req.participant);
|
|
620
|
-
req.session.destroy();
|
|
621
|
-
callback();
|
|
622
|
-
}
|
|
623
|
-
})(req, res, next);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* Routes
|
|
628
|
-
*/
|
|
629
|
-
|
|
630
|
-
app.use(function(req, res, next){
|
|
631
|
-
if (argv.rollSession) {
|
|
632
|
-
req.session.regenerate(function(err) {
|
|
633
|
-
return next();
|
|
634
|
-
});
|
|
635
|
-
} else {
|
|
636
|
-
next()
|
|
637
|
-
}
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
app.use(function(req, res, next){
|
|
641
|
-
//req.user = argv.config.user;
|
|
642
|
-
req.metadata = argv.config.metadata;
|
|
643
|
-
req.idp = { options: idpOptions };
|
|
644
|
-
if (req.user)
|
|
645
|
-
req.participant = getParticipant(req);
|
|
646
|
-
next();
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
app.get(['/', '/idp', IDP_PATHS.SSO], parseSamlRequest);
|
|
650
|
-
app.post(['/', '/idp', IDP_PATHS.SSO], parseSamlRequest);
|
|
651
|
-
|
|
652
|
-
app.get(IDP_PATHS.SLO, parseLogoutRequest);
|
|
653
|
-
app.post(IDP_PATHS.SLO, parseLogoutRequest);
|
|
654
|
-
|
|
655
|
-
app.post(IDP_PATHS.SIGN_IN, function(req, res) {
|
|
656
|
-
const authOptions = extend({}, req.idp.options);
|
|
657
|
-
Object.keys(req.body).forEach(function(key) {
|
|
658
|
-
var buffer;
|
|
659
|
-
if (key === '_authnRequest') {
|
|
660
|
-
buffer = new Buffer(req.body[key], 'base64');
|
|
661
|
-
req.authnRequest = JSON.parse(buffer.toString('utf8'));
|
|
662
|
-
|
|
663
|
-
// Apply AuthnRequest Params
|
|
664
|
-
authOptions.inResponseTo = req.authnRequest.id;
|
|
665
|
-
if (req.idp.options.allowRequestAcsUrl && req.authnRequest.acsUrl) {
|
|
666
|
-
authOptions.acsUrl = req.authnRequest.acsUrl;
|
|
667
|
-
authOptions.recipient = req.authnRequest.acsUrl;
|
|
668
|
-
authOptions.destination = req.authnRequest.acsUrl;
|
|
669
|
-
authOptions.forceAuthn = req.authnRequest.forceAuthn;
|
|
670
|
-
}
|
|
671
|
-
if (req.authnRequest.relayState) {
|
|
672
|
-
authOptions.RelayState = req.authnRequest.relayState;
|
|
673
|
-
}
|
|
674
|
-
} else {
|
|
675
|
-
req.user[key] = req.body[key];
|
|
676
|
-
}
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
if (!authOptions.encryptAssertion) {
|
|
680
|
-
delete authOptions.encryptionCert;
|
|
681
|
-
delete authOptions.encryptionPublicKey;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// Set Session Index
|
|
685
|
-
authOptions.sessionIndex = getSessionIndex(req);
|
|
686
|
-
|
|
687
|
-
// Keep calm and Single Sign On
|
|
688
|
-
console.log(dedent(chalk`
|
|
689
|
-
Generating SAML Response using =>
|
|
690
|
-
{bold User} => ${Object.entries(req.user).map(([key, value]) => chalk`
|
|
691
|
-
${key}: {cyan ${value}}`
|
|
692
|
-
).join('')}
|
|
693
|
-
{bold SAMLP Options} => ${Object.entries(authOptions).map(([key, value]) => chalk`
|
|
694
|
-
${key}: {cyan ${formatOptionValue(key, value)}}`
|
|
695
|
-
).join('')}
|
|
696
|
-
`));
|
|
697
|
-
samlp.auth(authOptions)(req, res);
|
|
698
|
-
})
|
|
699
|
-
|
|
700
|
-
app.get(IDP_PATHS.METADATA, function(req, res, next) {
|
|
701
|
-
samlp.metadata(req.idp.options)(req, res);
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
app.post(IDP_PATHS.METADATA, function(req, res, next) {
|
|
705
|
-
if (req.body && req.body.attributeName && req.body.name) {
|
|
706
|
-
var attributeExists = false;
|
|
707
|
-
const attribute = {
|
|
708
|
-
id: req.body.attributeName,
|
|
709
|
-
optional: true,
|
|
710
|
-
name: req.body.name,
|
|
711
|
-
description: req.body.description || '',
|
|
712
|
-
multiValue: req.body.valueType === 'multi'
|
|
713
|
-
};
|
|
714
|
-
|
|
715
|
-
req.metadata.forEach(function(entry) {
|
|
716
|
-
if (entry.id === req.body.attributeName) {
|
|
717
|
-
entry = attribute;
|
|
718
|
-
attributeExists = true;
|
|
719
|
-
}
|
|
720
|
-
});
|
|
721
|
-
|
|
722
|
-
if (!attributeExists) {
|
|
723
|
-
req.metadata.push(attribute);
|
|
724
|
-
}
|
|
725
|
-
console.log("Updated SAML Attribute Metadata => \n", req.metadata)
|
|
726
|
-
res.status(200).end();
|
|
727
|
-
}
|
|
728
|
-
});
|
|
729
|
-
|
|
730
|
-
app.get(IDP_PATHS.SIGN_OUT, function(req, res, next) {
|
|
731
|
-
if (req.idp.options.sloUrl) {
|
|
732
|
-
console.log('Initiating SAML SLO request for user: ' + req.user.username +
|
|
733
|
-
' with sessionIndex: ' + getSessionIndex(req));
|
|
734
|
-
res.redirect(IDP_PATHS.SLO);
|
|
735
|
-
} else {
|
|
736
|
-
console.log('SAML SLO is not enabled for SP, destroying IDP session');
|
|
737
|
-
req.session.destroy(function(err) {
|
|
738
|
-
if (err) {
|
|
739
|
-
throw err;
|
|
740
|
-
}
|
|
741
|
-
res.redirect('back');
|
|
742
|
-
})
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
app.get([IDP_PATHS.SETTINGS], function(req, res, next) {
|
|
747
|
-
res.render('settings', {
|
|
748
|
-
idp: req.idp.options
|
|
749
|
-
});
|
|
750
|
-
});
|
|
751
|
-
|
|
752
|
-
app.post([IDP_PATHS.SETTINGS], function(req, res, next) {
|
|
753
|
-
Object.keys(req.body).forEach(function(key) {
|
|
754
|
-
switch(req.body[key].toLowerCase()){
|
|
755
|
-
case "true": case "yes": case "1":
|
|
756
|
-
req.idp.options[key] = true;
|
|
757
|
-
break;
|
|
758
|
-
case "false": case "no": case "0":
|
|
759
|
-
req.idp.options[key] = false;
|
|
760
|
-
break;
|
|
761
|
-
default:
|
|
762
|
-
req.idp.options[key] = req.body[key];
|
|
763
|
-
break;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
if (req.body[key].match(/^\d+$/)) {
|
|
767
|
-
req.idp.options[key] = parseInt(req.body[key], '10');
|
|
768
|
-
}
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
console.log('Updated IdP Configuration => \n', req.idp.options);
|
|
772
|
-
res.redirect('/');
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
// catch 404 and forward to error handler
|
|
776
|
-
app.use(function(req, res, next) {
|
|
777
|
-
const err = new Error('Route Not Found');
|
|
778
|
-
err.status = 404;
|
|
779
|
-
next(err);
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
// development error handler
|
|
783
|
-
app.use(function(err, req, res, next) {
|
|
784
|
-
if (err) {
|
|
785
|
-
res.status(err.status || 500);
|
|
786
|
-
res.render('error', {
|
|
787
|
-
message: err.message,
|
|
788
|
-
error: err
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
});
|
|
792
|
-
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
function runServer(options) {
|
|
796
|
-
const args = processArgs([], options);
|
|
797
|
-
return _runServer(args.argv);
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
function main () {
|
|
801
|
-
const args = processArgs(process.argv.slice(2));
|
|
802
|
-
_runServer(args.argv);
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
module.exports = {
|
|
807
|
-
samlIdp: {
|
|
808
|
-
run: runServer,
|
|
809
|
-
expressMiddleware: app
|
|
810
|
-
}
|
|
811
|
-
};
|