Files
Felitendo a69e0c4009 feat: give a forgotten password somewhere to go
There was no way to change an operator password anywhere, and no way at all to
help somebody who had forgotten theirs. On a server with no mail to send a link
through, that left the database as the only answer.

An operator can now change their own, from the dashboard and from the account
row on the console's home screen. It does not ask for the current one, and that
is a decision rather than an oversight: the session already authorises emptying
the library, deleting every account and every board, and minting invitations, so
refusing it a password change protects nothing that somebody holding it would
want, while asking would cost the only recovery this thing has. What replaces
the check is that the change signs every other browser and device out and is
logged, so a hostile one is visible the moment it happens. The session doing the
changing is kept, or the screen would log you out for fixing your password.

For everybody else there is a one-time link. An operator makes one from the
person's row and hands it over however they already talk to them; opening it
asks for a new password and nothing else. It is shaped exactly like an
invitation, and for the same reasons: long, stored only as a digest, shown once
in the response that makes it, spent once, and dead after a day. Making a second
kills the first, because a replaced link left alive is a loose end nobody is
tracking, and redeeming one signs every device out, since somebody resetting a
password is very often doing it because somebody else has the account.

The front door no longer talks about whoever runs the place. It says
registrations are closed and to ask for an invitation link, which is the thing
the reader can actually act on.

And a pass over where things are configured. Two switches both meant "can new
people sign up" for two completely different sets of people and neither said
which, so they are named after their populations now and each points at the
other. On the console, the buttons that start a scan moved out of the group that
records what has already happened and in with the sources they act on, and the
group that held both people and settings is two groups, because who may use this
server and how it is set up are different questions and neither should be read
past to reach the other.
2026-09-02 09:11:01 +02:00

130 lines
4.1 KiB
Swift

//
// AdminMoreView.swift
// FTStore Admin
//
// Everything that is not an ordinary day's work.
//
// The main screen used to list all fourteen destinations in one column, which
// made the five that get opened constantly no easier to reach than the nine
// that get opened when something needs changing. A list stops being navigation
// at about that length and starts being an inventory.
//
// The split is by how often, not by how important: the home screen keeps what
// answers "what happened?", and this keeps what answers "how is it set up?"
// and "what has it been doing?".
//
// Grouped rather than flat, because nine in a row would only move the problem
// one screen along.
//
import SwiftUI
struct AdminMoreView: View {
@EnvironmentObject private var session: AdminSession
var body: some View {
List {
Section {
NavigationLink {
AdminSourcesView()
} label: {
Label("Channels & Sources", systemImage: "antenna.radiowaves.left.and.right")
}
NavigationLink {
AdminTelegramView()
} label: {
Label { Text("Telegram") } icon: { TelegramMark(size: 17) }
}
NavigationLink {
AdminTweaksView()
} label: {
Label("Known Tweaks", systemImage: "wrench.and.screwdriver")
}
// Next to Known Tweaks, which is the list of what to recognise.
// This is the list of what to refuse, and the two are read
// together often enough that separating them would be a nuisance.
NavigationLink {
AdminBlocklistView()
} label: {
Label("Blocked Tweaks", systemImage: "hand.raised")
}
// With the sources it acts on rather than under "What it has been
// doing", which is a record of the past: a button that starts a
// scan is not a record of anything.
NavigationLink {
AdminActionsView()
} label: {
Label("Scraper Actions", systemImage: "bolt.horizontal")
}
} header: {
Text("Where builds come from")
} footer: {
Text("Where FTStore looks for builds, what it recognises when it finds one, what it refuses, and the buttons that make it look now.")
}
Section {
NavigationLink {
AdminUsageView()
} label: {
Label("Usage", systemImage: "chart.line.uptrend.xyaxis")
}
NavigationLink {
AdminLogsView()
} label: {
Label("Logs", systemImage: "scroll")
}
} header: {
Text("What it has been doing")
} footer: {
Text("The record: how much has been downloaded and stored, and every step the scraper took getting there.")
}
// Two groups rather than one called "People and settings", which was a
// bag with two different kinds of thing in it. Who may use this
// server is one question and how it is configured is another, and an
// operator looking for one of them should not have to read past the
// other.
Section {
// Access is a list of people too, and a third one: not operators
// and not the people who write in FTStore, but whoever is allowed
// to reach the server at all.
NavigationLink {
AdminAccessView()
} label: {
Label("Access", systemImage: "lock.shield")
}
if session.isAdmin {
// "Operators", not "Users": these are the accounts that sign
// in and run this server. The people who use FTStore are the
// Users entry on the main screen, and the two were named the
// same thing for long enough that somebody went looking for
// a poster here.
NavigationLink {
AdminUsersView()
} label: {
Label("Operators", systemImage: "person.2")
}
}
} header: {
Text("Who may use it")
} footer: {
Text("Access decides who may reach the store at all. Operators are the accounts that sign in here and run it. The people who post and review are Users, on the main screen.")
}
Section {
NavigationLink {
AdminServerSettingsView()
} label: {
Label("Server Settings", systemImage: "slider.horizontal.3")
}
} header: {
Text("How it is set up")
} footer: {
Text("Addresses, intervals, limits and where files are kept. Nothing here is about people.")
}
}
.navigationTitle("Setup & Diagnostics")
.navigationBarTitleDisplayMode(.inline)
}
}