update devDependencies, update tests for new validator, fixes for validation issues
[squeep-indie-auther] / src / template / authorization-request-html.js
index 3e312af683721642a18357b1a6e99084aa98daca..ac2ea064efe6cc0109c6c3db49c21db6cf0bbd49 100644 (file)
@@ -1,7 +1,7 @@
 'use strict';
 
 const th = require('./template-helper');
-
+const { sessionNavLinks } = require('@squeep/authentication-module');
 
 /**
  * @param {Object} hApp
@@ -19,13 +19,13 @@ function renderClientIdentifierProperties(hApp) {
   const { url, summary, logo, name } = properties;
 
   parts.push('<span class="client-identifier">');
-  if (url && url.length) {
+  if (url?.length) {
     parts.push(`<a href="${url[0]}">`);
   }
-  if (summary && summary.length) {
+  if (summary?.length) {
     imgTitle = ` title="${summary[0]}"`;
   }
-  if (logo && logo.length) {
+  if (logo?.length) {
     let src, alt;
     if (typeof logo[0] === 'string') {
       src = logo[0];
@@ -35,10 +35,10 @@ function renderClientIdentifierProperties(hApp) {
     }
     parts.push(`<img src="${src}" alt="${alt}"${imgTitle}>`);
   }
-  if (name && name.length) {
+  if (name?.length) {
     parts.push(properties['name'][0]);
   }
-  if (url && url.length) {
+  if (url?.length) {
     parts.push('</a>');
   }
   parts.push('</span>');
@@ -52,7 +52,7 @@ function renderClientIdentifierProperties(hApp) {
  * @returns {String}
  */
 function renderClientIdentifier(clientIdentifier) {
-  const hAppEntries = clientIdentifier && clientIdentifier.items || [];
+  const hAppEntries = clientIdentifier?.items || [];
   return hAppEntries.map(renderClientIdentifierProperties).join('');
 }
 
@@ -74,7 +74,7 @@ function renderProfileOption(profile, selected) {
  */
 function renderProfileFieldset(availableProfiles, hintProfile) {
   if (!availableProfiles || availableProfiles.length <= 1) {
-    const profile = availableProfiles && availableProfiles[0] || hintProfile;
+    const profile = availableProfiles?.[0] || hintProfile;
     return `<input type="hidden" name="me" value="${profile}">`;
   }
   return `
@@ -109,21 +109,21 @@ function renderScopeCheckboxLI(scope, checked) {
     scopeDescription = '';
   }
   let profileClass;
-  if (scope.profiles && scope.profiles.length) {
-    profileClass = ['profile-scope'].concat(scope.profiles.map((profile) => th.escapeCSS(profile))).join(' ');
+  if (scope.profiles?.length) {
+    profileClass = ['profile-scope'].concat(scope.profiles).join(' ');
   } else {
     profileClass = '';
   }
   return `
         <li class="${profileClass}">
-          <input type="checkbox" id="scope_${scope.scope}" name="accepted_scopes" value="${scope.scope}"${checked ? ' checked' : ''}>
+          <input type="checkbox" id="scope_${scope.scope}" name="accepted_scopes[]" value="${scope.scope}"${checked ? ' checked' : ''}>
           <label for="scope_${scope.scope}">${scope.scope}</label>${scopeDescription}
         </li>`;
 }
 
 
 function renderRequestedScopes(requestedScopes) {
-  if (!requestedScopes || !requestedScopes.length) {
+  if (!requestedScopes?.length) {
     return '';
   }
   return `
@@ -165,7 +165,7 @@ ${additionalScopes.map((scopeDetails) => renderScopeCheckboxLI(scopeDetails, fal
       You may also specify a space-separated list of any additional ad hoc scopes you would like to associate with this authorization request, which were not explicitly requested by the client application.
     </div>
     <label for="ad-hoc-scopes">Ad Hoc Scopes</label>
-    <input id="ad-hoc-scopes" name="ad_hoc_scopes" value="">
+    <input type="text" id="ad-hoc-scopes" name="ad_hoc_scopes" value="">
   </fieldset>`);
   return parts.join('');
 }
@@ -189,18 +189,10 @@ function renderExpiration(requestedScopes) {
 \t\t<br>
 \t\t<details>
 \t\t\t<summary>Set Expiration</summary>
-\t\t\t<div>
 \t\t\t\t${radioButton('expires', 'never', 'Never', true)}
-\t\t\t</div>
-\t\t\t<div>
 \t\t\t\t${radioButton('expires', '1d', '1 Day')}
-\t\t\t</div>
-\t\t\t<div>
 \t\t\t\t${radioButton('expires', '1w', '1 Week')}
-\t\t\t</div>
-\t\t\t<div>
 \t\t\t\t${radioButton('expires', '1m', '1 Month')}
-\t\t\t</div>
 \t\t\t<div>
 \t\t\t\t${radioButton('expires', 'custom', 'Other:')}
 \t\t\t\t<input type="number" id="expires-seconds" name="expires-seconds">
@@ -210,22 +202,15 @@ function renderExpiration(requestedScopes) {
 \t\t\t<div>
 \t\t\t\tTokens with expirations may be allowed to be renewed for a fresh token for an amount of time after they expire.
 \t\t\t</div>
-\t\t\t<div>
 \t\t\t\t${radioButton('refresh', 'none', 'Not Refreshable', true)}
-\t\t\t</div>
-\t\t\t<div>
 \t\t\t\t${radioButton('refresh', '1d', '1 Day')}
-\t\t\t</div>
-\t\t\t<div>
 \t\t\t\t${radioButton('refresh', '1w', '1 Week')}
-\t\t\t</div>
-\t\t\t<div>
 \t\t\t\t${radioButton('refresh', '1m', '1 Month')}
 \t\t\t<div>
 \t\t\t\t${radioButton('refresh', 'custom', 'Other:')}
 \t\t\t\t<input type="number" id="refresh-seconds" name="refresh-seconds">
 \t\t\t\t<label for="refresh-seconds">seconds</label>
-\t\t\t </div>
+\t\t\t</div>
 \t\t</details>
 \t</fieldset>`;
 }
@@ -264,11 +249,13 @@ function radioButton(name, value, label, checked = false, indent = 0) {
  */
 function mainContent(ctx, options) { // eslint-disable-line no-unused-vars
   const session = ctx.session || {};
-  const hintedProfile = (session.me && session.me.href) || (session.profiles && session.profiles.length && session.profiles[0]) || '';
+  const hintedProfile = session.me?.href || session.profiles?.[0] || '';
   const scopeIndex = session.scopeIndex || {};
 
-  // Add requested scopes to index, if not already present,
-  // and de-associate requested scopes from profiles.
+  /**
+   * Add requested scopes to index, if not already present,
+   * and de-associate requested scopes from profiles.
+   */
   const scopes = session.scope || [];
   scopes.forEach((scopeName) => {
     if ((scopeName in scopeIndex)) {
@@ -296,9 +283,7 @@ function mainContent(ctx, options) { // eslint-disable-line no-unused-vars
 
   return [
     `<section class="information">
-\tThe application client
-\t${renderClientIdentifier(session.clientIdentifier)}
-\tat <a class="uri" name="${session.clientId}">${session.clientId}</a> would like to identify you as <a class="uri" name="${hintedProfile}">${hintedProfile}</a>.
+\tThe application client ${renderClientIdentifier(session.clientIdentifier)} at <a class="uri" aria-label="client-identifier" id="${session.clientId}">${session.clientId}</a> would like to identify you as <a class="uri" aria-label="profile"${hintedProfile ? ' id="' + hintedProfile + '"' : ''}>${hintedProfile ? hintedProfile : '(unspecified)'}</a>.
 </section>
 <section class="choices">
 \t<form action="consent" method="POST" class="form-consent">`,
@@ -310,14 +295,14 @@ function mainContent(ctx, options) { // eslint-disable-line no-unused-vars
 \t\t<br>
 \t\t<fieldset>
 \t\t\t<legend>Do you want to allow this?</legend>
-\t\t\t<button class="button-accept" name="accept" value="true">Accept</button>
-\t\t\t<button class="button-decline" name="accept" value="false">Decline</button>
+\t\t\t<button type="submit" class="button-accept" name="accept" value="true">Accept</button>
+\t\t\t<button type="submit" class="button-decline" name="accept" value="false">Decline</button>
 \t\t</fieldset>
 \t\t<input type="hidden" name="session" value="${session.persist}">
 \t</form>
 \t<br>
 \t<div>
-\t\tYou will be redirected to <a class="uri" name="${session.redirectUri}">${session.redirectUri}</a>.
+\t\tYou will be redirected to <a class="uri" id="${session.redirectUri ? session.redirectUri : 'unknown-redirect'}">${session.redirectUri}</a>.
 \t</div>
 </section>`,
   ];
@@ -342,6 +327,7 @@ function mainContent(ctx, options) { // eslint-disable-line no-unused-vars
  * @returns {String}
  */
 module.exports = (ctx, options) => {
+  const pagePathLevel = 0;
   const htmlOptions = {
     pageTitle: `${options.manager.pageTitle} &mdash; Authorization Request`,
     logoUrl: options.manager.logoUrl,
@@ -355,22 +341,23 @@ function queryAll(query, fn) {
 }
 function profileSelected(element) {
   const profileClass = CSS.escape(element.value);
-  console.log('new profile:', element.value, profileClass);
-  queryAll('.profile-scope input', (n) => n.setAttribute('disabled', true));
+  // queryAll('.profile-scope input', (n) => n.setAttribute('disabled', ''));
   queryAll('.profile-scope', (n) => n.classList.add('disabled'));
   const profileQuery = '.profile-scope.' + profileClass;
-  queryAll(profileQuery + ' input', (n) => n.setAttribute('disabled', false));
+  // queryAll(profileQuery + ' input', (n) => n.removeAttribute('disabled'));
   queryAll(profileQuery, (n) => n.classList.remove('disabled'));
 }
 function onLoad() {
-  return; // The escaped class selection does not seem to work, so ignore it all for now.
   const profileSelect = document.getElementById('me');
   profileSelect.onchange = () => profileSelected(profileSelect);
   profileSelected(profileSelect);
 }
+window.onload = onLoad;
 </script>`,
     ],
   };
+  th.navLinks(pagePathLevel, ctx, htmlOptions);
+  sessionNavLinks(pagePathLevel, ctx, htmlOptions);
   const content = mainContent(ctx, options);
-  return th.htmlPage(0, ctx, htmlOptions, content);
+  return th.htmlPage(pagePathLevel, ctx, htmlOptions, content);
 };