Stop making people sign in to reach tools that need no account
Some checks failed
CI / backend (push) Failing after 7m9s
CI / frontend (push) Has been cancelled
CI / migrations-mysql (push) Has been cancelled
CI / naming (push) Has been cancelled

/tools, /tools/codes and /tools/secure-send were all behind requiresAuth. None
of them needed to be, and one of them actively contradicted a setting added an
hour earlier: securesend_allow_guests lets somebody with no account create a
link, and the page they would create it on was behind a login wall.

The label generator runs entirely in the browser - no API, no data, nothing to
protect - so gating it only ever cost a guest the use of it.

THE SERVER STILL DECIDES who may create a secret. A signed-in user needs
tools.secrets; a guest needs the site to have opened guest creation. Making the
page reachable is not the same as making the action allowed, and the two were
conflated.

The page now reads the setting on load, so a guest at a site that has NOT opened
guest creation is told to sign in instead of being handed a form that was always
going to be refused - with a line saying that receiving a link never needs an
account, because somebody who lands there confused usually has a link they
should be opening instead.

A signed-in user always sees the form, even without the permission: the server's
"you do not have permission" is a different answer from "sign in", and
collapsing them would send somebody to a login page they are already past.
This commit is contained in:
cproudlock
2026-08-21 16:00:18 -04:00
parent 8750ba2e74
commit 92cf1d71f0
2 changed files with 41 additions and 9 deletions

View File

@@ -15,22 +15,28 @@ export default [
path: 'tools',
name: 'tools',
component: () => import('./views/ToolsIndex.vue'),
meta: { requiresAuth: true, plugin: 'tools' }
// NO requiresAuth. Every tool here is usable without an account: the label
// generator runs entirely in the browser, and secure send decides for
// itself (a site can let guests create, and receiving never needs a login).
// Gating the index turned a public tool into one nobody could reach.
meta: { plugin: 'tools' }
},
{
path: 'tools/codes',
name: 'tools-codes',
component: () => import('./views/CodeGenerator.vue'),
meta: { requiresAuth: true, plugin: 'tools' }
// Browser-only: no API, no data, nothing to protect.
meta: { plugin: 'tools' }
},
{
path: 'tools/secure-send',
name: 'tools-secure-send',
component: () => import('./views/SecureSendCreate.vue'),
// Creating a secret needs a login AND tools.secrets. The route guard covers
// the login; the server enforces the permission, and the page surfaces its
// refusal rather than pretending the form worked.
meta: { requiresAuth: true, plugin: 'tools' }
// Reachable by anyone; the SERVER decides who may actually create. A signed
// in user needs tools.secrets, and a guest needs the site to have turned on
// securesend_allow_guests. The page reads that setting so a guest who
// cannot create is told so, instead of being handed a form that 401s.
meta: { plugin: 'tools' }
},
]

View File

@@ -17,13 +17,25 @@
</p>
</div>
<!-- A guest at a site that has not opened guest creation. Told here rather
than after typing a password into a form that was always going to be
refused. -->
<div v-if="supported && !mayCreate" class="card">
<p>Signing in is needed to create a secret link at this site.</p>
<p class="muted">
Receiving one never needs an account - if somebody sent you a link, open
that instead.
</p>
<router-link to="/login" class="btn btn-primary">Sign in</router-link>
</div>
<div v-if="!supported" class="card control-note">
This browser cannot encrypt in page (Web Crypto is unavailable, which
usually means the site is not being served over HTTPS). Secure send needs
it, because the whole point is that the server never sees your text.
</div>
<template v-else-if="!link">
<template v-else-if="mayCreate && !link">
<div class="card">
<label class="field">
<span class="field-label">Secret</span>
@@ -94,15 +106,29 @@
</template>
<script setup>
import { ref, computed } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { toolsApi } from '../api'
import { withBase } from '@/utils/basePath'
import { getSiteBaseUrl } from '@/utils/siteSettings'
import { getSiteBaseUrl, getSetting } from '@/utils/siteSettings'
import { useAuthStore } from '@/stores/auth'
import {
sealSecret, refuseReason, pasteCarriesFiles, cryptoAvailable, MAX_PLAINTEXT,
} from '../securesend'
const supported = cryptoAvailable()
const auth = useAuthStore()
// Whether THIS visitor can create one. A signed-in user always sees the form -
// the server still enforces tools.secrets and says so if they lack it, which is
// a different message from "sign in". A guest sees it only where the site has
// opened guest creation.
const mayCreate = ref(true)
onMounted(async () => {
if (auth.isAuthenticated) return
const allowed = await getSetting('securesend_allow_guests', 'false')
mayCreate.value = String(allowed).toLowerCase() === 'true'
})
// Matches the server's ceiling. A free number rather than a dropdown because
// "how many people need this password" is the sender's business - a shift