fix: disable autocomplete on admin auth + storage pages

scarlett
NGPixel 4 days ago
parent fb184f6375
commit 800fb31bbd
No known key found for this signature in database

@ -1,28 +0,0 @@
import path from 'node:path'
import fse from 'fs-extra'
export async function task(): Promise<void> {
WIKI.logger.info('Purging orphaned upload files...')
try {
const uplTempPath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads')
await fse.ensureDir(uplTempPath)
const ls = await fse.readdir(uplTempPath)
const fifteenAgo = Temporal.Now.instant().subtract({ minutes: 15 })
for (const f of ls) {
const stat = await fse.stat(path.join(uplTempPath, f))
// -> Compared as epoch millis. Temporal deliberately has no `valueOf`, so relational
// operators on its types throw — comparisons must be explicit.
if (stat.isFile() && stat.ctime.getTime() < fifteenAgo.epochMilliseconds) {
await fse.unlink(path.join(uplTempPath, f))
}
}
WIKI.logger.info('Purging orphaned upload files: [ COMPLETED ]')
} catch (err: any) {
WIKI.logger.error('Purging orphaned upload files: [ FAILED ]')
WIKI.logger.error(err.message)
throw err
}
}

@ -353,15 +353,21 @@ const hasError = computed(() => Boolean(errorMessage.value))
/* /*
The opt-out attributes, as one object bound in a single `v-bind`. The opt-out attributes, as one object bound in a single `v-bind`.
`autocomplete="off"` is the standards half and the only one any browser reads; the four `data-` `autocomplete` is the standards half and the only one any browser reads; the four `data-`
attributes are what the password managers that ignore it read instead -- 1Password, LastPass, attributes are what the password managers that ignore it read instead -- 1Password, LastPass,
Bitwarden and Dashlane respectively, each having settled on its own spelling. They are inert Bitwarden and Dashlane respectively, each having settled on its own spelling. They are inert
everywhere else, so they cost a field that nobody's extension looks at nothing. everywhere else, so they cost a field that nobody's extension looks at nothing.
`new-password` rather than `off` on a password field, and that difference is the whole point on
the fields that matter most: Chrome deliberately disregards `off` there -- too many banks had used
it to stop people pasting -- and offers the saved credential anyway. `new-password` is the one
value it does honour, because a field being filled with an EXISTING password is exactly what it
says this is not. Every other type reads `off`, which they all honour.
*/ */
const autofillAttrs = computed(() => const autofillAttrs = computed(() =>
props.noAutofill props.noAutofill
? { ? {
autocomplete: 'off', autocomplete: props.type === 'password' ? 'new-password' : 'off',
'data-1p-ignore': 'true', 'data-1p-ignore': 'true',
'data-lpignore': 'true', 'data-lpignore': 'true',
'data-bwignore': 'true', 'data-bwignore': 'true',

@ -3,10 +3,16 @@
v-if="max > 1" v-if="max > 1"
class="w-pagination flex flex-nowrap items-center gap-1" class="w-pagination flex flex-nowrap items-center gap-1"
:aria-label="ariaLabel"> :aria-label="ariaLabel">
<!--
`dark:text-white` on every button: `.w-unstyled` leaves the colour to `inherit`, and this
control is used OUTSIDE a card straight on the admin page background where nothing up the
tree sets a dark-mode colour, so the numbers were drawn in the light theme's black on a dark
page. The ellipsis beside them already carried its own pair.
-->
<button <button
v-if="directionLinks" v-if="directionLinks"
type="button" type="button"
class="w-unstyled w-pagination-btn" class="w-unstyled w-pagination-btn dark:text-white"
:disabled="modelValue <= 1" :disabled="modelValue <= 1"
:aria-label="prevLabel" :aria-label="prevLabel"
@click="go(modelValue - 1)"> @click="go(modelValue - 1)">
@ -20,7 +26,7 @@
<button <button
v-else v-else
type="button" type="button"
class="w-unstyled w-pagination-btn" class="w-unstyled w-pagination-btn dark:text-white"
:class="page === modelValue ? 'w-pagination-btn--active' : ''" :class="page === modelValue ? 'w-pagination-btn--active' : ''"
:aria-current="page === modelValue ? 'page' : undefined" :aria-current="page === modelValue ? 'page' : undefined"
:aria-label="`${pageLabel} ${page}`" :aria-label="`${pageLabel} ${page}`"
@ -32,7 +38,7 @@
<button <button
v-if="directionLinks" v-if="directionLinks"
type="button" type="button"
class="w-unstyled w-pagination-btn" class="w-unstyled w-pagination-btn dark:text-white"
:disabled="modelValue >= max" :disabled="modelValue >= max"
:aria-label="nextLabel" :aria-label="nextLabel"
@click="go(modelValue + 1)"> @click="go(modelValue + 1)">

@ -309,11 +309,17 @@
options-dense options-dense
:aria-label="cfg.title" :aria-label="cfg.title"
:disable="cfg.readOnly" /> :disable="cfg.readOnly" />
<!-- -> `no-autofill` on every prop a strategy declares, not only the sensitive
ones: a manager offers to fill whatever LOOKS like a credential, and a
client ID or an issuer URL beside a secret is exactly that shape. What is
typed here is the wiki's credential with an identity provider, never the
operator's own. -->
<w-input <w-input
v-else v-else
outlined outlined
v-model="cfg.value" v-model="cfg.value"
dense dense
no-autofill
:type="inputTypeFor(cfg)" :type="inputTypeFor(cfg)"
:aria-label="cfg.title" :aria-label="cfg.title"
:disable="cfg.readOnly" :disable="cfg.readOnly"

@ -201,11 +201,17 @@
options-dense options-dense
:aria-label="cfg.title" :aria-label="cfg.title"
:disable="cfg.readOnly" /> :disable="cfg.readOnly" />
<!-- -> `no-autofill` on every field a module declares, not only the
sensitive ones: a manager offers to fill whatever LOOKS like a credential,
and a target's host and account name are exactly that shape. What is being
typed here is the wiki's credential for somebody else's bucket, never the
operator's own. -->
<w-input <w-input
v-else v-else
outlined outlined
v-model="cfg.value" v-model="cfg.value"
dense dense
no-autofill
:type="inputTypeFor(cfg)" :type="inputTypeFor(cfg)"
:aria-label="cfg.title" :aria-label="cfg.title"
:disable="cfg.readOnly" :disable="cfg.readOnly"
@ -270,6 +276,7 @@
<w-input <w-input
outlined outlined
dense dense
no-autofill
v-model="state.target.assetDelivery.baseUrl" v-model="state.target.assetDelivery.baseUrl"
placeholder="https://files.example.com" placeholder="https://files.example.com"
:aria-label="t(`admin.storage.deliveryBaseUrl`)" /> :aria-label="t(`admin.storage.deliveryBaseUrl`)" />
@ -288,6 +295,7 @@
<w-input <w-input
outlined outlined
dense dense
no-autofill
v-model="state.target.assetDelivery.linkExpiration" v-model="state.target.assetDelivery.linkExpiration"
:aria-label="t(`admin.storage.deliveryExpiration`)" /> :aria-label="t(`admin.storage.deliveryExpiration`)" />
</w-item-section> </w-item-section>
@ -544,6 +552,7 @@
<w-input <w-input
outlined outlined
dense dense
no-autofill
v-model="state.largeThreshold" v-model="state.largeThreshold"
:aria-label="t(`admin.storage.largeThreshold`)" /> :aria-label="t(`admin.storage.largeThreshold`)" />
</w-item-section> </w-item-section>
@ -559,6 +568,7 @@
<w-input <w-input
outlined outlined
dense dense
no-autofill
v-model="state.syncInterval" v-model="state.syncInterval"
:aria-label="t(`admin.storage.syncInterval`)" /> :aria-label="t(`admin.storage.syncInterval`)" />
</w-item-section> </w-item-section>

Loading…
Cancel
Save