support interaction between module and apps for updating templates before rendering
[squeep-authentication-module] / lib / template / login-html.js
1 'use strict';
2
3 const { TemplateHelper: th } = require('@squeep/html-template-helper');
4 const { sessionNavLinks } = require('./helpers');
5
6 /**
7 * Login form.
8 */
9 function indieAuthSection(ctx, options) {
10 const indieAuthBlurb = (options.indieAuthBlurb || []).map((x) => '\t'.repeat(6) + x).join('\n');
11 const showIndieAuthForm = options.authnEnabled.includes('indieAuth');
12 return showIndieAuthForm ? `\t\t\t<section class="indieauth">
13 \t\t\t\t<h2>Login</h2>
14 \t\t\t\t<form method="POST">
15 \t\t\t\t\t<fieldset>
16 \t\t\t\t\t\t<legend>IndieAuth</legend>
17 \t\t\t\t\t\t<label for="me">Profile URL:</label>
18 \t\t\t\t\t\t<input id="me" name="me" type="url" size="40" placeholder="https://example.com/my_profile_url" value="" autofocus>
19 \t\t\t\t\t\t<input type="hidden" name="me_auto_scheme">
20 \t\t\t\t\t\t<button type="submit">Login</button>
21 ${indieAuthBlurb}
22 \t\t\t\t\t</fieldset>
23 \t\t\t\t</form>
24 \t\t\t</section>`
25 : '';
26 }
27
28
29 /**
30 * Default all URL inputs to https if scheme not specified,
31 * and set a flag if that happened.
32 * From https://aaronparecki.com/2019/05/13/2/https
33 */
34 function indieAuthURLTrySecureFirstScript(ctx, options) {
35 const showIndieAuthForm = options.authnEnabled.includes('indieAuth');
36 return showIndieAuthForm ? `
37 <script>
38 document.addEventListener('DOMContentLoaded', function() {
39 function addDefaultScheme(target) {
40 if (target.value.match(/^(?!https?:).+\\..+/)) {
41 const autoSchemeField = document.querySelector('input[name=' + target.getAttribute('name') + '_auto_scheme]');
42 let scheme;
43 if (autoSchemeField) {
44 scheme = 'https://';
45 autoSchemeField.value = '1';
46 } else {
47 scheme = 'http://';
48 }
49 target.value = scheme + target.value;
50 }
51 }
52 const elements = document.querySelectorAll('input[type=url]');
53 Array.prototype.forEach.call(elements, function (el, i) {
54 el.addEventListener('blur', function(e) {
55 addDefaultScheme(e.target);
56 });
57 el.addEventListener('keydown', function(e) {
58 if (e.keyCode === 13) {
59 addDefaultScheme(e.target);
60 }
61 });
62 });
63 });
64 </script>` : '';
65 }
66
67 const userAuthn = ['argon2', 'pam'];
68 function userSection(ctx, options) {
69 const userBlurb = (options.userBlurb || []).map((x) => '\t'.repeat(6) + x).join('\n');
70 const secure = (ctx.clientProtocol || '').toLowerCase() === 'https';
71 const showUserForm = options.authnEnabled.filter((x) => userAuthn.includes(x)).length
72 && (secure || !options.secureAuthOnly);
73 return showUserForm ? `\t\t\t<section class="user">
74 \t\t\t\t<form method="POST">
75 \t\t\t\t\t<fieldset>
76 \t\t\t\t\t\t<legend>User Account</legend>
77 \t\t\t\t\t\t<label for="identifier">Username:</label>
78 \t\t\t\t\t\t<input id="identifier" name="identifier" type="text" value="">
79 \t\t\t\t\t\t<br>
80 \t\t\t\t\t\t<label for="credential">Password:</label>
81 \t\t\t\t\t\t<input id="credential" name="credential" type="password" value="">
82 \t\t\t\t\t\t<br>
83 \t\t\t\t\t\t<button type="submit">Login</button>
84 ${userBlurb}
85 \t\t\t\t\t</fieldset>
86 \t\t\t\t</form>
87 \t\t\t</section>`
88 : '';
89 }
90
91
92 /**
93 * Render login form for both local and profile authentication.
94 * @param {Object} ctx
95 * @param {String[]=} ctx.errors
96 * @param {String} ctx.clientProtocol
97 * @param {Object} options
98 * @param {Boolean} options.authenticator.secureAuthOnly
99 * @param {String[]=} options.authenticator.loginBlurb
100 * @param {String[]=} options.authenticator.indieAuthBlurb
101 * @param {String[]=} options.authenticator.userBlurb
102 * @param {Object} options.manager
103 * @param {String} options.manager.pageTitle
104 * @param {String=} options.manager.logoUrl
105 * @param {Object} options.dingus
106 * @param {String} options.dingus.selfBaseUrl
107 * @param {() => {}} appCb
108 * @returns {String}
109 */
110 module.exports = (ctx, options, appCb = () => {}) => {
111 const pagePathLevel = 1;
112 const htmlOptions = {
113 pageIdentifier: 'login',
114 pageTitle: options.manager.pageTitle,
115 logoUrl: options.manager.logoUrl,
116 footerEntries: options.manager.footerEntries,
117 secureAuthOnly: options.authenticator.secureAuthOnly,
118 authnEnabled: options.authenticator.authnEnabled,
119 indieAuthBlurb: options.authenticator.indieAuthBlurb,
120 userBlurb: options.authenticator.userBlurb,
121 };
122 appCb(pagePathLevel, ctx, htmlOptions);
123 sessionNavLinks(pagePathLevel, ctx, htmlOptions);
124 const mainContent = [
125 ...(options.authenticator.loginBlurb || []),
126 indieAuthURLTrySecureFirstScript(ctx, htmlOptions),
127 indieAuthSection(ctx, htmlOptions),
128 userSection(ctx, htmlOptions),
129 ];
130 return th.htmlPage(pagePathLevel, ctx, htmlOptions, mainContent);
131 };