Add "Proactively authenticate to relays" option to privacy settings, defaults to off

Fix automatically disconnecting from authenticated relays
This commit is contained in:
hzrd149 2024-09-25 10:31:32 -05:00
parent 2a2ed1c4af
commit bbd19d7d99
6 changed files with 83 additions and 29 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Add "Proactively authenticate to relays" option to privacy settings, defaults to off

View File

@ -0,0 +1,5 @@
---
"nostrudel": patch
---
Fix automatically disconnecting from authenticated relays

View File

@ -10,6 +10,7 @@ import { offlineMode } from "../services/offline-mode";
import processManager from "../services/process-manager";
import signingService from "../services/signing";
import accountService from "../services/account";
import localSettings from "../services/local-settings";
export type Notice = {
message: string;
@ -20,6 +21,8 @@ export type Notice = {
export type RelayAuthMode = "always" | "ask" | "never";
export default class RelayPool {
log = logger.extend("RelayPool");
relays = new Map<string, AbstractRelay>();
onRelayCreated = new Subject<AbstractRelay>();
onRelayChallenge = new Subject<[AbstractRelay, string]>();
@ -35,8 +38,6 @@ export default class RelayPool {
authenticated = new SuperMap<AbstractRelay, Subject<boolean>>(() => new Subject());
log = logger.extend("RelayPool");
getRelay(relayOrUrl: string | URL | AbstractRelay) {
if (typeof relayOrUrl === "string") {
const safeURL = safeRelayUrl(relayOrUrl);
@ -69,10 +70,7 @@ export default class RelayPool {
const key = url.toString();
if (!this.relays.has(key)) {
const r = new AbstractRelay(key, { verifyEvent: verifyEventMethod });
r._onauth = (challenge) => {
this.onRelayChallenge.next([r, challenge]);
this.challenges.get(r).next(challenge);
};
r._onauth = (challenge) => this.handleRelayChallenge(r, challenge);
r.onnotice = (notice) => this.handleRelayNotice(r, notice);
this.relays.set(key, r);
@ -130,7 +128,7 @@ export default class RelayPool {
let relay = this.getRelay(relayOrUrl);
if (!relay) return;
const defaultMode = (localStorage.getItem(`default-auth-mode`) as RelayAuthMode) ?? undefined;
const defaultMode = localSettings.defaultAuthenticationMode.value;
const mode = (localStorage.getItem(this.getRelayAuthStorageKey(relay)) as RelayAuthMode) ?? undefined;
return mode || defaultMode;
@ -189,6 +187,30 @@ export default class RelayPool {
return this.authForSubscribe.get(relay).value !== false;
}
private automaticallyAuthenticate(relay: AbstractRelay) {
const authMode = this.getRelayAuthMode(relay);
// only automatically authenticate if auth mode is set to "always"
if (authMode === "always") {
const account = accountService.current.value;
if (!account) return;
this.authenticate(relay, (draft) => {
return signingService.requestSignature(draft, account);
}).then(() => {
this.log(`Automatically authenticated to ${relay.url}`);
});
}
}
private handleRelayChallenge(relay: AbstractRelay, challenge: string) {
this.onRelayChallenge.next([relay, challenge]);
this.challenges.get(relay).next(challenge);
if (localSettings.proactivelyAuthenticate.value) {
this.automaticallyAuthenticate(relay);
}
}
handleRelayNotice(relay: AbstractRelay, message: string) {
const subject = this.notices.get(relay);
subject.next([...subject.value, { message, date: dayjs().unix(), relay }]);
@ -197,18 +219,8 @@ export default class RelayPool {
const authForSubscribe = this.authForSubscribe.get(relay);
if (!authForSubscribe.value) authForSubscribe.next(true);
const account = accountService.current.value;
if (account) {
const authMode = this.getRelayAuthMode(relay);
if (authMode === "always") {
this.authenticate(relay, (draft) => {
return signingService.requestSignature(draft, account);
}).then(() => {
this.log(`Automatically authenticated to ${relay.url}`);
});
}
}
// try to authenticate
this.automaticallyAuthenticate(relay);
}
}
@ -216,6 +228,9 @@ export default class RelayPool {
for (const [url, relay] of this.relays) {
if (!relay.connected) continue;
// don't disconnect from authenticated relays
if (this.authenticated.get(relay).value) continue;
let disconnect = true;
for (const process of processManager.processes) {
if (process.active && process.relays.has(relay)) {

View File

@ -51,6 +51,10 @@ const addClientTag = new BooleanLocalStorageEntry("add-client-tag", false);
const verifyEventMethod = new LocalStorageEntry("verify-event-method", "wasm"); // wasm, internal, none
const enableKeyboardShortcuts = new BooleanLocalStorageEntry("enable-keyboard-shortcuts", true);
// privacy
const defaultAuthenticationMode = new LocalStorageEntry("default-relay-auth-mode", "ask"); // ask, always, never
const proactivelyAuthenticate = new BooleanLocalStorageEntry("proactively-authenticate", false);
// display settings
const showBrandLogo = new BooleanLocalStorageEntry("show-brand-logo", true);
@ -66,6 +70,8 @@ const localSettings = {
verifyEventMethod,
enableKeyboardShortcuts,
showBrandLogo,
defaultAuthenticationMode,
proactivelyAuthenticate,
};
if (import.meta.env.DEV) {

View File

@ -1,4 +1,3 @@
import { useLocalStorage } from "react-use";
import {
Flex,
FormControl,
@ -18,6 +17,8 @@ import { createRequestProxyUrl } from "../../../helpers/request";
import { RelayAuthMode } from "../../../classes/relay-pool";
import VerticalPageLayout from "../../../components/vertical-page-layout";
import useSettingsForm from "../use-settings-form";
import useSubject from "../../../hooks/use-subject";
import localSettings from "../../../services/local-settings";
async function validateInvidiousUrl(url?: string) {
if (!url) return true;
@ -44,9 +45,8 @@ async function validateRequestProxy(url?: string) {
export default function PrivacySettings() {
const { register, submit, formState } = useSettingsForm();
const [defaultAuthMode, setDefaultAuthMode] = useLocalStorage<RelayAuthMode>("default-relay-auth-mode", "ask", {
raw: true,
});
const defaultAuthenticationMode = useSubject(localSettings.defaultAuthenticationMode);
const proactivelyAuthenticate = useSubject(localSettings.proactivelyAuthenticate);
return (
<VerticalPageLayout as="form" onSubmit={submit} flex={1}>
@ -58,8 +58,8 @@ export default function PrivacySettings() {
w="xs"
rounded="md"
flexShrink={0}
value={defaultAuthMode || "ask"}
onChange={(e) => setDefaultAuthMode(e.target.value as RelayAuthMode)}
value={defaultAuthenticationMode}
onChange={(e) => localSettings.defaultAuthenticationMode.next(e.target.value as RelayAuthMode)}
>
<option value="always">Always authenticate</option>
<option value="ask">Ask every time</option>
@ -68,6 +68,22 @@ export default function PrivacySettings() {
<FormHelperText>How should the app handle relays requesting identification</FormHelperText>
</FormControl>
<FormControl>
<Flex alignItems="center">
<FormLabel htmlFor="proactivelyAuthenticate" mb="0">
Proactively authenticate to relays
</FormLabel>
<Switch
id="proactivelyAuthenticate"
isChecked={proactivelyAuthenticate}
onChange={(e) => localSettings.proactivelyAuthenticate.next(e.currentTarget.checked)}
/>
</Flex>
<FormHelperText>
<span>Authenticate to relays as soon as they send the authentication challenge</span>
</FormHelperText>
</FormControl>
<FormControl isInvalid={!!formState.errors.twitterRedirect}>
<FormLabel>Nitter instance</FormLabel>
<Input

View File

@ -32,6 +32,8 @@ import useRouteSearchValue from "../../../hooks/use-route-search-value";
import processManager from "../../../services/process-manager";
import { RelayAuthMode } from "../../../classes/relay-pool";
import Timestamp from "../../../components/timestamp";
import localSettings from "../../../services/local-settings";
import useSubject from "../../../hooks/use-subject";
function RelayCard({ relay }: { relay: AbstractRelay }) {
return (
@ -50,11 +52,15 @@ function RelayCard({ relay }: { relay: AbstractRelay }) {
function RelayAuthCard({ relay }: { relay: AbstractRelay }) {
const { authenticated } = useRelayAuthMethod(relay);
const defaultMode = useSubject(localSettings.defaultAuthenticationMode);
const processes = processManager.getRootProcessesForRelay(relay);
const [authMode, setAuthMode] = useLocalStorage<RelayAuthMode>(
const [authMode, setAuthMode] = useLocalStorage<RelayAuthMode | "">(
relayPoolService.getRelayAuthStorageKey(relay),
"ask",
{ raw: true },
"",
{
raw: true,
},
);
return (
@ -74,9 +80,10 @@ function RelayAuthCard({ relay }: { relay: AbstractRelay }) {
w="auto"
rounded="md"
flexShrink={0}
value={authMode || "ask"}
value={authMode}
onChange={(e) => setAuthMode(e.target.value as RelayAuthMode)}
>
<option value="">Default ({defaultMode})</option>
<option value="always">Always</option>
<option value="ask">Ask</option>
<option value="never">Never</option>