Give the guest setting somewhere to be changed
Reported: no Tools section in Settings. The setting existed in the database and had no page, which from a person's side is indistinguishable from the feature not existing. get_settings_defaults SEEDS a setting. get_settings_cards (ADR-010) is what puts it in the rail. Declaring only the first is a silent half-feature: nothing fails, no test notices, and the switch is unreachable. The page states the cost where the DECISION is made rather than in a document nobody opens - turn it on and it says a guest-created link records no creator, so the audit trail can say when and from where but never who. Admin-only, like every other settings page, because this one decides whether an unauthenticated caller may write to the site. Also caught while wiring it: the card asked for a 'shield' icon, which is not in the settings iconMap, so it would have rendered the fallback puzzle piece - the glyph that reads as "nobody chose this". Uses wrench, matching Tech Tools. The row still comes from `flask plugin upgrade-all`; the card appears either way, and without the row the page loads empty.
This commit is contained in:
@@ -28,6 +28,14 @@ export default [
|
||||
// Browser-only: no API, no data, nothing to protect.
|
||||
meta: { plugin: 'tools' }
|
||||
},
|
||||
{
|
||||
path: 'settings/secure-send',
|
||||
name: 'secure-send-settings',
|
||||
component: () => import('./views/SecureSendSettings.vue'),
|
||||
// Admin, like every other settings page: this decides whether an
|
||||
// unauthenticated caller may write to the site.
|
||||
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'tools' }
|
||||
},
|
||||
{
|
||||
path: 'tools/secure-send',
|
||||
name: 'tools-secure-send',
|
||||
|
||||
77
plugins/tools/frontend/views/SecureSendSettings.vue
Normal file
77
plugins/tools/frontend/views/SecureSendSettings.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="page-header">
|
||||
<h1>Secure Send</h1>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="card loading">Loading...</div>
|
||||
|
||||
<div v-else class="card">
|
||||
<label class="switch-row">
|
||||
<input v-model="allowGuests" type="checkbox" />
|
||||
<span>
|
||||
<strong>Let people without an account create links</strong>
|
||||
<span class="hint">
|
||||
Off by default. With it off, creating a link needs a login and the
|
||||
<code>tools.secrets</code> permission. Receiving one never needs an
|
||||
account either way.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<!-- The cost, stated where the decision is made rather than in a document
|
||||
nobody opens. -->
|
||||
<p class="control-note" v-if="allowGuests">
|
||||
A link created by a guest has no creator recorded. The audit trail can
|
||||
say when it was made and from where, but never who made it.
|
||||
</p>
|
||||
|
||||
<button class="btn btn-primary" :disabled="saving" @click="save">
|
||||
{{ saving ? 'Saving...' : 'Save' }}
|
||||
</button>
|
||||
<span v-if="saved" class="saved">Saved</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { settingsApi } from '@/api'
|
||||
|
||||
const KEY = 'securesend_allow_guests'
|
||||
|
||||
const allowGuests = ref(false)
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const saved = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await settingsApi.list({ category: 'tools' })
|
||||
const rows = response.data.data || []
|
||||
const row = rows.find(setting => setting.key === KEY)
|
||||
allowGuests.value = String(row?.value).toLowerCase() === 'true'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
await settingsApi.update(KEY, allowGuests.value ? 'true' : 'false')
|
||||
saved.value = true
|
||||
setTimeout(() => { saved.value = false }, 2000)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.switch-row { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 1rem; }
|
||||
.switch-row span { display: block; }
|
||||
.hint { display: block; color: var(--text-light); font-size: 0.875rem; margin-top: 4px; }
|
||||
.control-note { color: var(--warning); font-size: 0.875rem; margin: 0 0 1rem; }
|
||||
.saved { margin-left: 10px; color: var(--text-light); font-size: 0.875rem; }
|
||||
</style>
|
||||
@@ -92,6 +92,28 @@ class ToolsPlugin(BasePlugin):
|
||||
},
|
||||
]
|
||||
|
||||
def get_settings_cards(self) -> List[dict]:
|
||||
"""A page for the one switch this plugin owns (ADR-010).
|
||||
|
||||
get_settings_defaults SEEDS the row; this is what gives somebody a place
|
||||
to change it. Declaring only the first left the setting in the database
|
||||
with no way to reach it, which is indistinguishable from the feature not
|
||||
existing.
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'group': 'Tools',
|
||||
'to': '/settings/secure-send',
|
||||
# From the settings iconMap; an unknown key falls back to a
|
||||
# puzzle piece, which reads as "nobody chose this".
|
||||
'icon': 'wrench',
|
||||
'title': 'Secure Send',
|
||||
'description': 'Whether people without an account can create '
|
||||
'one-view password links',
|
||||
'position': 60,
|
||||
},
|
||||
]
|
||||
|
||||
def get_navigation_items(self) -> List[dict]:
|
||||
return [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user