diff --git a/lib/assets/admin-users.mjs b/lib/assets/admin-users.mjs
index 9a54b2b..0288a94 100644
--- a/lib/assets/admin-users.mjs
+++ b/lib/assets/admin-users.mjs
@@ -43,11 +43,11 @@ async function submit(data) {
document.getElementById('shareContainer').hidden = false;
}
- displayOutput(invite.text + '\n' + invite.url, 'Or, copy and paste this invite to a secure channel:');
+ displayOutput(invite.text + '\n' + invite.url, 'Or, copy and paste this to a secure channel:');
} else {
await displayNonsuccess(resp);
if (resp.status === 401) {
- window.location = './login'
+ window.location = '/account/login'
}
}
} catch (err) {
diff --git a/lib/assets/login.mjs b/lib/assets/login.mjs
index 8ae6e4e..dac72de 100644
--- a/lib/assets/login.mjs
+++ b/lib/assets/login.mjs
@@ -8,7 +8,6 @@ import {startAuthentication} from './simplewebauthn-browser.js';
//
// document.getElementById('login').hidden = false;
document.getElementById('login')?.addEventListener('click', loginCatchingErrors);
- displayMessage('Click the button below to log in with a passkey.\n\nIf you need to create a passkey for this device or browser, log in from your old device and invite yourself to create a new passkey.');
// });
async function loginCatchingErrors() {
diff --git a/lib/assets/style.css b/lib/assets/style.css
index 0cf21d9..d308c6c 100644
--- a/lib/assets/style.css
+++ b/lib/assets/style.css
@@ -191,7 +191,7 @@ header.topbar {
width: 100%;
max-width: calc(100vw - 2rem);
min-height: 3rem;
- margin: 1rem 0;
+ margin: 1rem 0 0 0;
}
header.topbar h1 {
@@ -308,6 +308,8 @@ header.topbar .signup:focus {
section.hero {
width: 50rem;
max-width: calc(100vw - 2rem);
+ margin-left: auto;
+ margin-right: auto;
text-align: center;
}
@@ -362,6 +364,16 @@ section.hero header + p {
}
}
+.artwork {
+ margin-top: 1rem;
+}
+
+.fitName {
+ font-size: 1.3rem;
+ font-size: math;
+ overflow: scroll;
+}
+
.centeredBoxContent {
display: flex;
justify-content: center;
@@ -399,29 +411,39 @@ th {
tr:nth-of-type(even) {
background: var(--arma-bckgrd-color-alt);
}
+td:first-child {
+ padding-left: 0.67em;
+}
/*
-Max width before this PARTICULAR table gets nasty
-This query will take effect for any screen smaller than 760px
-and also iPads specifically.
+Max width before these PARTICULAR tables gets nasty
+This query will take effect for any screen smaller than 500px
*/
@media
-only screen and (max-width: 400px) {
+only screen and (max-width: 500px) {
/* Force table to not be like tables anymore */
- table, thead, tbody, th, td, tr {
+ table.adaptive, .adaptive thead, .adaptive tbody, .adaptive th, .adaptive td, .adaptive tr {
display: block;
}
/* Hide table headers (but not display: none;, for accessibility) */
- thead tr {
- position: absolute;
- top: -9999px;
- left: -9999px;
+ .adaptive thead tr {
+ border: 0;
+ padding: 0;
+ margin: 0;
+ position: absolute !important;
+ height: 1px;
+ width: 1px;
+ overflow: hidden;
+ clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
+ clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
+ clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
+ white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}
- tr { border: 1px solid #ccc; }
+ .adaptive tr { border: 1px solid #ccc; }
- td {
+ .adaptive td {
/* Behave like a "row" */
border: none;
border-bottom: 1px solid #eee;
@@ -429,12 +451,11 @@ only screen and (max-width: 400px) {
padding-left: 40%;
}
- td:before {
+ .adaptive td:before {
/* Now like a table header */
position: absolute;
/* Top/left values mimic padding */
- top: 6px;
- left: 6px;
+ left: 0.67em;
width: 35%;
padding-right: 10px;
white-space: nowrap;
@@ -447,12 +468,13 @@ only screen and (max-width: 400px) {
#users td:nth-of-type(1):before { content: "Name"; }
#users td:nth-of-type(2):before { content: "Contact URL"; }
- #users td:nth-of-type(3):before { content: "Last connected"; }
+ #users td:nth-of-type(3):before { content: "Last logged-in"; }
#users td:nth-of-type(4):before { content: "Privileges"; }
}
-.borderSolid {
+#output {
border: black 1px solid;
+ padding: 1em;
}
/* BODY FOOTER */
@@ -532,6 +554,11 @@ body > footer > p {
/* MAIN CONTENT */
+main {
+ width: 100%;
+ max-width: 65rem;
+}
+
main.content {
margin: auto;
}
@@ -562,6 +589,13 @@ main .message {
text-align: center;
}
+.notTooWide {
+ width: 100%;
+ max-width: 30rem;
+ margin-left: auto;
+ margin-right: auto;
+}
+
/* FORMS */
form {
@@ -884,7 +918,7 @@ button[name="deny"]:focus {
}
@media
-only screen and (max-width: 400px) {
+only screen and (max-width: 500px) {
.flexRowWrapResponsive {
display: flex;
flex-flow: row wrap;
@@ -893,6 +927,9 @@ only screen and (max-width: 400px) {
}
}
+.preWrap {
+ white-space: pre-wrap;
+}
/* ICONS */
diff --git a/lib/routes/admin.js b/lib/routes/admin.js
index 4ca354c..378b6cd 100644
--- a/lib/routes/admin.js
+++ b/lib/routes/admin.js
@@ -441,7 +441,7 @@ module.exports = async function (hostIdentity, jwtSecret, accountMgr, storeRoute
} else {
res.logNotes.add('session does not have ADMIN privilege');
if (['GET', 'HEAD'].includes(req.method)) {
- res.redirect(307, './login');
+ res.redirect(307, '/admin/login');
} else { // TODO consider deleting this unused code
res.logNotes.add('session lacks ADMIN privilege');
res.status(401).end();
diff --git a/lib/routes/login.js b/lib/routes/login.js
index 93370ac..f35dae9 100644
--- a/lib/routes/login.js
+++ b/lib/routes/login.js
@@ -31,8 +31,9 @@ module.exports = async function (hostIdentity, jwtSecret, account, isAdminLogin)
host: getHost(req),
privileges: req.session.privileges || {},
accountPrivileges: req.session.user?.privileges || {},
- message: 'select a passkey',
- options: JSON.stringify(options)
+ message: isAdminLogin ? 'Click the button below to authenticate with a passkey.' : 'Click the button below to log in with a passkey.\n\nIf you need to create a passkey for this device or browser, log in from your old device and invite yourself to create a new passkey.',
+ options: JSON.stringify(options),
+ actionLabel: isAdminLogin ? 'Authenticate' : 'Log in'
});
} catch (err) {
removeUserDataFromSession(req.session);
@@ -87,6 +88,7 @@ module.exports = async function (hostIdentity, jwtSecret, account, isAdminLogin)
}
});
+ /** TODO: make this a POST, and change link to form */
router.get('/logout',
// csrfCheck,
async (req, res) => {
@@ -97,7 +99,7 @@ module.exports = async function (hostIdentity, jwtSecret, account, isAdminLogin)
req.session.destroy(err => { if (err) { reject(err); } else { resolve(); } });
});
- res.set('Cache-Control', 'max-age=1500');
+ res.set('Cache-Control', 'private, no-store');
res.render('login/logout.html', {
title: 'Logged Out',
host: getHost(req),
diff --git a/lib/views/account/account.html b/lib/views/account/account.html
index 3855c50..a147e30 100644
--- a/lib/views/account/account.html
+++ b/lib/views/account/account.html
@@ -3,57 +3,61 @@
- <%= title %> <%= username + '@' + host %>
+
+ <%= title %>
+ <%= username + '@' + host %>
+
-
-
- Account Privileges <%= Object.keys(accountPrivileges).join(', ') || '«none»' %>
- Session Privileges <%= Object.keys(privileges).join(', ') || '«none»' %>
-
-
-
-
- Passkeys
-
-
-
-
-
- Created using Created on Last used
-
-
- <% for (const cred of credentials) { %>
-
- <%= cred.name %>
- <%= new Date(cred.createdAt).toLocaleDateString() %>
- <%= cred.lastUsed ? new Date(cred.lastUsed).toLocaleString().replace(/:\d\d(?!:)/, '') : 'never' %>
-
- <% } %>
-
-
-
-
-
-To create a passkey on a new device, invite yourself to create another passkey:
-
- Invite yourself to create another passkey
-
-
-
-
-
-
-
- Share Invite from an account of mine
-
-
-
-
+
+
+
+ Account Privileges <%= Object.keys(accountPrivileges).join(', ') || '«none»' %>
+ Session Privileges <%= Object.keys(privileges).join(', ') || '«none»' %>
+
+
+
+
+ Passkeys
+
+
+
+
+
+ Created using Created on Last used
+
+
+ <% for (const cred of credentials) { %>
+
+ <%= cred.name %>
+ <%= new Date(cred.createdAt).toLocaleDateString() %>
+ <%= cred.lastUsed ? new Date(cred.lastUsed).toLocaleString().replace(/:\d\d(?!:)/, '') : 'never' %>
+
+ <% } %>
+
+
+
+
+
+ To create a passkey on a new device, invite yourself to create another passkey:
+
+ Invite yourself to create another passkey
+
+
+
+
+
+
+
+ Share Invite from an account of mine
+
+
+
+
<%- include('../end.html'); %>
diff --git a/lib/views/admin/invite-requests.html b/lib/views/admin/invite-requests.html
index 8a49f09..0bd4589 100644
--- a/lib/views/admin/invite-requests.html
+++ b/lib/views/admin/invite-requests.html
@@ -46,9 +46,9 @@
Share Invite from an account of mine
-
+
<%- include('../end.html'); %>
diff --git a/lib/views/admin/invite-valid.html b/lib/views/admin/invite-valid.html
index f9b0445..28aa1b8 100644
--- a/lib/views/admin/invite-valid.html
+++ b/lib/views/admin/invite-valid.html
@@ -2,23 +2,28 @@
-
- <%= title %>
- Contact URL: <%= contactURL %>
- Privileges: <%= Object.keys(accountPrivileges).join(', ') || '«none»' %>
-
-
You will always use a passkey to log into your account.
-
-
- <%= message %>
-
-
-
+
+
+ <%= title %>
+ Contact URL: <%= contactURL %>
+ Privileges: <%= Object.keys(accountPrivileges).join(', ') || '«none»' %>
+
+
You will always use a passkey to log into your account.
+
+
+ <%= message %>
+
+
Username:
- Create Passkey
-
-
+
+ Create Passkey
+
+
+
+
<%- include('../end.html'); %>
diff --git a/lib/views/admin/users.html b/lib/views/admin/users.html
index 51d99f9..ba0bce3 100644
--- a/lib/views/admin/users.html
+++ b/lib/views/admin/users.html
@@ -7,52 +7,56 @@
-
-
- Username Contact URL Last connected Privileges
-
-
- <% for (const user of users) { %>
+
+
+
+ Username Contact URL Last logged-in Privileges
+
+
+ <% for (const user of users) { %>
- <%= user.username %>
+ <%= user.username || " " %>
<% if (user.privileges) { %>
re-invite
<% } %>
- <%= user.contactURL?.split(':').join(':') %>
+ <%= user.contactURL?.split(':').join(':') || " " %>
<%= user.lastUsed ? new Date(user.lastUsed).toLocaleString().replace(/:\d\d(?!:)/, '') : 'never' %>
<%= Object.keys(user.privileges || {}).join(', ') || '«none»' %>
- <% } %>
- <% if (!users.length) { %>
- No users
- <% } %>
-
-
-
-
-
-
-
-<%- include('../contact-url.html'); %>
-
-
-
-
-
-
-
-
-
-
- Share Invite from an account of mine
-
-
-
+ <% } %>
+ <% if (!users.length) { %>
+ No users
+ <% } %>
+
+
+
+
+
+
+
+ <%- include('../contact-url.html'); %>
+
+
+
+
+
+
+
+
+
+
+ Share Invite from an account of mine
+
+
+
+
<%- include('../end.html'); %>
diff --git a/lib/views/login/login.html b/lib/views/login/login.html
index 7bbbda4..5d29825 100644
--- a/lib/views/login/login.html
+++ b/lib/views/login/login.html
@@ -16,11 +16,11 @@
<%= title %>
-
<%= message %>
+
<%= message %>
-
Log in
+
<%= actionLabel %>
diff --git a/spec/modular/account.spec.js b/spec/modular/account.spec.js
index ca74ada..0d151c7 100644
--- a/spec/modular/account.spec.js
+++ b/spec/modular/account.spec.js
@@ -68,7 +68,7 @@ describe('account router', function () {
expect(res).to.have.header('Cache-Control', /\bno-cache\b/);
const resText = res.text.replace(/"/g, '"');
expect(resText).to.contain('
Your Account ');
- expect(resText).to.match(new RegExp('
' + USER.username + '@'));
+ expect(resText).to.match(new RegExp('' + USER.username + '@'));
expect(resText).to.contain('STORE ');
expect(resText).to.contain('Apple Mac Firefox ');
expect(resText).to.match(/5\/\d\/2024<\/td>/);
@@ -97,7 +97,7 @@ describe('account router', function () {
expect(res).to.have.header('Cache-Control', /\bno-store\b/);
const resText = res.text.replace(/"/g, '"');
expect(resText).to.contain('Login ');
- expect(resText).to.contain('select a passkey
');
+ expect(resText).to.contain('Click the button below to log in with a passkey.\n\nIf you need to create a passkey for this device or browser, log in from your old device and invite yourself to create a new passkey.
');
expect(resText).to.contain('"challenge":"');
expect(resText).to.contain('"userVerification":"preferred"');
expect(resText).to.contain('"rpId":"psteniusubi.github.io"');
diff --git a/spec/modular/admin.spec.js b/spec/modular/admin.spec.js
index 153f464..f36085e 100644
--- a/spec/modular/admin.spec.js
+++ b/spec/modular/admin.spec.js
@@ -477,7 +477,7 @@ describe('admin module', function () {
expect(res).to.have.header('Cache-Control', /\bno-store\b/);
const resText = res.text.replace(/"/g, '"');
expect(resText).to.contain('Start Admin Session ');
- expect(resText).to.contain('select a passkey
');
+ expect(resText).to.contain('Click the button below to authenticate with a passkey.
');
expect(resText).to.contain('"challenge":"');
expect(resText).to.contain('"userVerification":"preferred"');
expect(resText).to.contain('"rpId":"psteniusubi.github.io"');