n8n-nodes-microsoft-sso-form 0.1.0 → 0.2.0
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/README.md
CHANGED
|
@@ -12,6 +12,8 @@ Form trigger for n8n protected by Microsoft Entra ID sign-in (OIDC auth code + P
|
|
|
12
12
|
## n8n setup
|
|
13
13
|
Create a **Microsoft SSO Form API** credential (tenant GUID, client ID/secret, 32+ char session secret). Set `WEBHOOK_URL` to your public HTTPS URL. Add the node, configure fields and Access Control (domain / group object IDs).
|
|
14
14
|
|
|
15
|
+
Password reset: with **Enable Password Reset** on, the signed-in page shows the user's email, a "Reset password" link (new + confirm password), and Sign out. Saving outputs `{ action: 'passwordReset', newPassword, submittedAt, user }` to the next node. The password is plain text in the execution data, so apply it immediately (e.g. Graph `PATCH /users/{id}` with `passwordProfile`) and consider disabling "Save successful executions" for that workflow.
|
|
16
|
+
|
|
15
17
|
Output: submitted fields plus `submittedAt` and `user` `{oid,name,email,tenantId,groups}`.
|
|
16
18
|
|
|
17
19
|
## Install
|
|
@@ -139,14 +139,14 @@ class MicrosoftSsoForm {
|
|
|
139
139
|
httpMethod: 'GET',
|
|
140
140
|
responseMode: 'onReceived',
|
|
141
141
|
path: '={{$parameter["path"]}}',
|
|
142
|
-
isFullPath:
|
|
142
|
+
isFullPath: true,
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
name: 'default',
|
|
146
146
|
httpMethod: 'POST',
|
|
147
147
|
responseMode: 'onReceived',
|
|
148
148
|
path: '={{$parameter["path"]}}',
|
|
149
|
-
isFullPath:
|
|
149
|
+
isFullPath: true,
|
|
150
150
|
},
|
|
151
151
|
],
|
|
152
152
|
properties: [
|
|
@@ -456,30 +456,72 @@ class MicrosoftSsoForm {
|
|
|
456
456
|
return redirect(authUrl, [cookie(LOGIN_COOKIE, loginCookie, cookiePath, 600)]);
|
|
457
457
|
}
|
|
458
458
|
const fields = this.getNodeParameter('formFields.values', []) ?? [];
|
|
459
|
-
|
|
459
|
+
const resetEnabled = this.getNodeParameter('enablePasswordReset', true);
|
|
460
|
+
const minLen = this.getNodeParameter('minPasswordLength', 8);
|
|
461
|
+
const blank = (f) => !f.fieldLabel && !f.fieldName && f.fieldType !== 'html';
|
|
462
|
+
const header = (links) => `<div class="who"><strong>Signed in as ${esc(session.email)}</strong>${session.name ? ` (${esc(session.name)})` : ''}<br>${links}</div>`;
|
|
463
|
+
// ---- GET: sign out / reset page / form ----
|
|
460
464
|
if (req.method === 'GET') {
|
|
465
|
+
if (q.action === 'logout')
|
|
466
|
+
return redirect(formUrl, [cookie(SESSION_COOKIE, '', cookiePath, 0)]);
|
|
467
|
+
if (resetEnabled && q.action === 'reset') {
|
|
468
|
+
return send(200, page('Reset password', `<h1>Reset password</h1>${header(`<a href="${esc(formUrl)}">Back</a> · <a href="${esc(formUrl)}?action=logout">Sign out</a>`)}
|
|
469
|
+
<form method="POST" action="${esc(formUrl)}">
|
|
470
|
+
<input type="hidden" name="_csrf" value="${esc(session.csrf)}">
|
|
471
|
+
<input type="hidden" name="_action" value="passwordReset">
|
|
472
|
+
<label>New password *</label><input type="password" name="newPassword" minlength="${minLen}" autocomplete="new-password" required>
|
|
473
|
+
<label>Confirm password *</label><input type="password" name="confirmPassword" minlength="${minLen}" autocomplete="new-password" required>
|
|
474
|
+
<button type="submit">Save</button></form>`));
|
|
475
|
+
}
|
|
461
476
|
const title = this.getNodeParameter('formTitle', '');
|
|
462
477
|
const desc = this.getNodeParameter('formDescription', '');
|
|
463
478
|
const button = this.getNodeParameter('buttonLabel', 'Submit');
|
|
464
479
|
const hasFile = fields.some((f) => f.fieldType === 'file');
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
480
|
+
const visible = fields.map((f, i) => (blank(f) ? '' : renderField(f, i))).join('\n');
|
|
481
|
+
const links = (resetEnabled ? `<a href="${esc(formUrl)}?action=reset">Reset password</a> · ` : '') +
|
|
482
|
+
`<a href="${esc(formUrl)}?action=logout">Sign out</a>`;
|
|
483
|
+
const formHtml = visible.trim()
|
|
484
|
+
? `<form method="POST" action="${esc(formUrl)}"${hasFile ? ' enctype="multipart/form-data"' : ''}>
|
|
468
485
|
<input type="hidden" name="_csrf" value="${esc(session.csrf)}">
|
|
469
|
-
${
|
|
470
|
-
<button type="submit">${esc(button)}</button></form>`
|
|
486
|
+
${visible}
|
|
487
|
+
<button type="submit">${esc(button)}</button></form>`
|
|
488
|
+
: '';
|
|
489
|
+
return send(200, page(title, `<h1>${esc(title)}</h1>${header(links)}<p>${esc(desc)}</p>${formHtml}`));
|
|
471
490
|
}
|
|
472
491
|
// ---- POST: submit ----
|
|
473
492
|
const body = (req.body ?? {});
|
|
474
493
|
const data = (body.data && typeof body.data === 'object' ? body.data : body);
|
|
475
494
|
if (data._csrf !== session.csrf)
|
|
476
495
|
return denied('Invalid form token. Reload the form and try again.');
|
|
496
|
+
const userInfo = {
|
|
497
|
+
oid: session.oid,
|
|
498
|
+
name: session.name,
|
|
499
|
+
email: session.email,
|
|
500
|
+
tenantId: session.tenantId,
|
|
501
|
+
groups: session.groups,
|
|
502
|
+
};
|
|
503
|
+
if (data._action === 'passwordReset') {
|
|
504
|
+
if (!resetEnabled)
|
|
505
|
+
return denied('Password reset is not enabled.');
|
|
506
|
+
const newPassword = String(data.newPassword ?? '');
|
|
507
|
+
if (newPassword.length < minLen)
|
|
508
|
+
return denied(`Password must be at least ${minLen} characters.`);
|
|
509
|
+
if (newPassword !== String(data.confirmPassword ?? ''))
|
|
510
|
+
return denied('Passwords do not match.');
|
|
511
|
+
send(200, page('Password saved', `<h1>Password saved</h1><p>Your password reset request was submitted.</p>`));
|
|
512
|
+
return {
|
|
513
|
+
noWebhookResponse: true,
|
|
514
|
+
workflowData: [
|
|
515
|
+
[{ json: { action: 'passwordReset', newPassword, submittedAt: new Date().toISOString(), user: userInfo } }],
|
|
516
|
+
],
|
|
517
|
+
};
|
|
518
|
+
}
|
|
477
519
|
const json = {};
|
|
478
520
|
const binary = {};
|
|
479
521
|
const reqFiles = (req.files ?? body.files ?? {});
|
|
480
522
|
for (let i = 0; i < fields.length; i++) {
|
|
481
523
|
const f = fields[i];
|
|
482
|
-
if (f.fieldType === 'html')
|
|
524
|
+
if (f.fieldType === 'html' || blank(f))
|
|
483
525
|
continue;
|
|
484
526
|
const key = inputName(i);
|
|
485
527
|
const outKey = outputKey(f, i);
|
|
@@ -505,13 +547,8 @@ ${fields.map(renderField).join('\n')}
|
|
|
505
547
|
json[outKey] = value ?? null;
|
|
506
548
|
}
|
|
507
549
|
json.submittedAt = new Date().toISOString();
|
|
508
|
-
json.
|
|
509
|
-
|
|
510
|
-
name: session.name,
|
|
511
|
-
email: session.email,
|
|
512
|
-
tenantId: session.tenantId,
|
|
513
|
-
groups: session.groups,
|
|
514
|
-
};
|
|
550
|
+
json.action = 'form';
|
|
551
|
+
json.user = userInfo;
|
|
515
552
|
const message = this.getNodeParameter('completionMessage', '');
|
|
516
553
|
send(200, page('Submitted', `<h1>Thank you</h1><p>${esc(message)}</p>`));
|
|
517
554
|
const item = { json };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-microsoft-sso-form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "n8n form trigger node protected by Microsoft Entra ID single sign-on, with optional group restriction",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": ["n8n-community-node-package", "microsoft", "entra", "sso", "form"],
|