Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 59 additions & 14 deletions client/components/NetworkForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<!--
Only what `ConnectOptions` (client/js/irc/types.ts) and the saved
network store support is offered. Dropped from TheLounge's form:
server password, "only allow trusted certificates", SOCKS proxy,
"only allow trusted certificates", SOCKS proxy,
username / real name / leave message, SASL EXTERNAL (client
certificates) and the STS lock — none of them apply to a browser
WebSocket connection, or they wait on later phases (STS: D.6).
Expand Down Expand Up @@ -207,20 +207,60 @@
/>
</RevealPassword>
</div>
<div class="connect-row">
<label></label>
<div class="input-wrap">
<label class="tls">
<input
v-model="defaults.rememberPassword"
v-switch
type="checkbox"
name="rememberPassword"
/>
{{ t("form.rememberPassword") }}
</label>
</div>
</template>

<!-- PASS before registration: bouncers without client SASL (ZNC). -->
<div class="connect-row">
<label></label>
<div class="input-wrap">
<label class="tls">
<input
v-model="defaults.hasServerPassword"
v-switch
type="checkbox"
name="hasServerPassword"
/>
{{ t("form.serverPasswordToggle") }}
<span
class="tooltipped tooltipped-n tooltipped-no-delay"
:aria-label="serverPasswordHelp"
>
<button class="extra-help" type="button" />
</span>
</label>
</div>
</div>
<div v-if="defaults.hasServerPassword" class="connect-row">
<label for="connect:serverPassword">{{ t("form.serverPassword") }}</label>
<RevealPassword v-slot:default="slotProps" class="input-wrap password-container">
<input
id="connect:serverPassword"
v-model="defaults.serverPassword"
dir="auto"
class="input"
:type="slotProps.isVisible ? 'text' : 'password'"
name="serverPassword"
maxlength="300"
autocomplete="off"
:placeholder="serverPasswordWasSet ? passwordPlaceholder : undefined"
/>
</RevealPassword>
</div>
<div v-if="defaults.sasl === 'plain' || defaults.hasServerPassword" class="connect-row">
<label></label>
<div class="input-wrap">
<label class="tls">
<input
v-model="defaults.rememberPassword"
v-switch
type="checkbox"
name="rememberPassword"
/>
{{ t("form.rememberPassword") }}
</label>
</div>
</div>
<template v-if="defaults.sasl === 'plain'">
<!-- A desktop shell takes no push (webpush.ts pushBackend). -->
<div v-if="!desktopShell" class="connect-row">
<label></label>
Expand Down Expand Up @@ -567,6 +607,9 @@ export default defineComponent({
const commandsHelp = computed(() => t("form.commandsHelp").replace(/\s*\n\s*/g, " "));
const passwordPlaceholder = computed(() => t("form.passwordPlaceholder"));
const pushHelp = computed(() => t("form.pushHelp"));
const serverPasswordHelp = computed(() => t("form.serverPasswordHelp"));
// The form opened on a configured one: blank keeps it (saved-networks fromForm).
const serverPasswordWasSet = !!props.defaults.hasServerPassword;
const browserNotificationsHelp = computed(() => t("form.browserNotificationsHelp"));
const store = useStore();
const commandsInput = ref<HTMLTextAreaElement | null>(null);
Expand Down Expand Up @@ -882,6 +925,8 @@ export default defineComponent({
commandsHelp,
passwordPlaceholder,
pushHelp,
serverPasswordHelp,
serverPasswordWasSet,
browserNotificationsHelp,
presenceMode,
presenceHint,
Expand Down
80 changes: 72 additions & 8 deletions client/components/Windows/Connect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -260,21 +260,65 @@
</label>
</div>
</div>
<div v-if="showSavedNetworks" class="connect-row">
</template>

<!-- PASS before registration: bouncers without client SASL (ZNC). -->
<template v-if="!hostLocked">
<div class="connect-row">
<label></label>
<div class="input-wrap">
<label class="tls">
<input
v-model="rememberPassword"
v-model="showServerPassword"
v-switch
type="checkbox"
name="rememberPassword"
name="hasServerPassword"
/>
{{ t("connect.rememberPassword") }}
{{ t("form.serverPasswordToggle") }}
<span
class="tooltipped tooltipped-n tooltipped-no-delay"
:aria-label="serverPasswordHelp"
>
<button class="extra-help" type="button" />
</span>
</label>
</div>
</div>
<div v-if="showServerPassword" class="connect-row">
<label for="connect:serverPassword">{{ t("form.serverPassword") }}</label>
<RevealPassword
v-slot:default="slotProps"
class="input-wrap password-container"
>
<input
id="connect:serverPassword"
ref="serverPasswordInput"
v-model="form.serverPassword"
dir="auto"
class="input"
:type="slotProps.isVisible ? 'text' : 'password'"
name="serverPassword"
maxlength="300"
autocomplete="off"
required
/>
</RevealPassword>
</div>
</template>
<div v-if="showSavedNetworks && (showSasl || showServerPassword)" class="connect-row">
<label></label>
<div class="input-wrap">
<label class="tls">
<input
v-model="rememberPassword"
v-switch
type="checkbox"
name="rememberPassword"
/>
{{ t("connect.rememberPassword") }}
</label>
</div>
</div>

<div class="connect-row">
<label></label>
Expand Down Expand Up @@ -457,6 +501,7 @@ export default defineComponent({
const serverAddressLabel = computed(() => t("connect.serverAddress"));
const serverPortLabel = computed(() => t("connect.serverPort"));
const channelsPlaceholder = computed(() => t("connect.channelsPlaceholder"));
const serverPasswordHelp = computed(() => t("form.serverPasswordHelp"));
const tls = network?.tls ?? defaults?.tls ?? true;
// The server the deploy points at. Pinned when the host is locked.
const server = {
Expand All @@ -474,6 +519,7 @@ export default defineComponent({
sasl: defaults?.sasl === "plain" ? "plain" : "",
saslAccount: defaults?.saslAccount || "",
saslPassword: defaults?.saslPassword || "",
serverPassword: "",
});

// `lockHost` hides the server fields; `allowCustomServer: false` does the
Expand Down Expand Up @@ -503,6 +549,7 @@ export default defineComponent({
const rememberMe = ref(false);

const showSasl = ref(false);
const showServerPassword = ref(false);
const rememberPassword = ref(false);
const autoconnect = ref(false);
/** Push defaults to on for a new network: the flag means "register when
Expand Down Expand Up @@ -535,6 +582,7 @@ export default defineComponent({
const submitted = ref<ConnectOptions | null>(null);
const notice = ref("");
const passwordInput = ref<HTMLInputElement | null>(null);
const serverPasswordInput = ref<HTMLInputElement | null>(null);

const prefill = (net: SavedNetwork) => {
form.host = net.host;
Expand All @@ -546,6 +594,8 @@ export default defineComponent({
form.saslAccount = net.saslAccount;
form.saslPassword = net.saslPassword;
showSasl.value = net.sasl === "plain";
form.serverPassword = net.serverPassword ?? "";
showServerPassword.value = !!net.hasServerPassword;
rememberPassword.value = !!net.rememberPassword;
autoconnect.value = !!net.autoconnect;
pushEnabled.value = net.pushEnabled !== false;
Expand All @@ -567,7 +617,7 @@ export default defineComponent({
const savedLink = savedLinkUuid ? saved.get(savedLinkUuid) : undefined;
const linkIgnored = firstParam(props.queryParams?.linkIgnored);
const fromLink = isTruthyParam(props.queryParams?.fromLink);
let focusPassword = false;
let focusPassword: "sasl" | "server" | false = false;

if (savedLink) {
prefill(savedLink);
Expand All @@ -579,7 +629,10 @@ export default defineComponent({

if (savedLink.sasl === "plain" && !savedLink.saslPassword) {
notice.value = t("connect.enterPassword", {account: savedLink.saslAccount});
focusPassword = true;
focusPassword = "sasl";
} else if (savedLink.hasServerPassword && !savedLink.serverPassword) {
notice.value = t("connect.enterServerPassword", {host: savedLink.host});
focusPassword = "server";
}
} else if (hasConnectParams) {
applyQueryParams(form, props.queryParams);
Expand Down Expand Up @@ -660,6 +713,7 @@ export default defineComponent({
const client = createNetwork({
...submitted.value,
uuid: (signInMode ? signInUuid() : selectedUuid.value) ?? undefined,
hasServerPassword: !!submitted.value.serverPassword,
pushEnabled: pushEnabled.value,
notifyEnabled: notifyEnabled.value,
...options,
Expand Down Expand Up @@ -713,8 +767,13 @@ export default defineComponent({
form.saslPassword = "";
}

if (!showServerPassword.value) {
form.serverPassword = "";
}

start({
rememberPassword: showSasl.value && rememberPassword.value,
rememberPassword:
(showSasl.value || showServerPassword.value) && rememberPassword.value,
autoconnect: autoconnect.value,
});
};
Expand All @@ -741,8 +800,10 @@ export default defineComponent({
onMounted(() => {
// A link to a saved network whose password was not remembered:
// everything is filled in but the password, so put the cursor there.
if (focusPassword) {
if (focusPassword === "sasl") {
passwordInput.value?.focus();
} else if (focusPassword === "server") {
serverPasswordInput.value?.focus();
}
});

Expand All @@ -764,6 +825,8 @@ export default defineComponent({
networkLabel,
showSavedNetworks,
showSasl,
showServerPassword,
serverPasswordHelp,
rememberPassword,
autoconnect,
pushEnabled,
Expand All @@ -775,6 +838,7 @@ export default defineComponent({
linkNotice,
fromLink,
passwordInput,
serverPasswordInput,
onSubmit,
cancelLink,
};
Expand Down
2 changes: 1 addition & 1 deletion client/js/boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ async function handleQueryParams(

// The connection would need a password nobody stored: back to the
// form, pre-filled from the saved entry, to type it.
if (!live?.isConnected && entry.sasl === "plain" && !entry.saslPassword) {
if (!live?.isConnected && saved.needsPassword(entry)) {
await router.push({
name: "Connect",
query: {savedLink: entry.uuid, join: suggestion.join, fromLink: "1"},
Expand Down
3 changes: 2 additions & 1 deletion client/js/helpers/linkTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
* names some other host → refuse it, with a message;
* - anything else → the connect form, pre-filled, for the user to approve.
*
* Secrets never ride along: a URL cannot supply `saslPassword`, and
* Secrets never ride along: a URL cannot supply `saslPassword` or
* `serverPassword` (the allow-list below), and
* `autoconnect` is not honoured from URLs at all — the user must click.
*
* Kept free of Vue/store/DOM imports so mocha covers it
Expand Down
11 changes: 7 additions & 4 deletions client/js/helpers/settingsBackup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ function storedBackupKeys(): string[] {
}

export interface CollectOptions {
/** Keep `saslPassword` on the saved networks; off strips it. */
/** Keep `saslPassword` and `serverPassword` on the saved networks; off strips them. */
includePasswords?: boolean;
/** The live settings object; storage only holds `settings` once one has
* been changed, so the caller passes the store's, defaults and all. */
Expand Down Expand Up @@ -166,8 +166,9 @@ export function collectBackup(options: CollectOptions = {}): SettingsBackup {
return net;
}

const {saslPassword, ...rest} = net as Record<string, unknown>;
const {saslPassword, serverPassword, ...rest} = net as Record<string, unknown>;
void saslPassword;
void serverPassword;
return {...rest, rememberPassword: false};
});
}
Expand Down Expand Up @@ -202,8 +203,10 @@ export function hasPasswords(backup: SettingsBackup): boolean {
(net) =>
typeof net === "object" &&
net !== null &&
typeof (net as Record<string, unknown>).saslPassword === "string" &&
(net as Record<string, unknown>).saslPassword !== ""
["saslPassword", "serverPassword"].some((key) => {
const value = (net as Record<string, unknown>)[key];
return typeof value === "string" && value !== "";
})
)
);
}
Expand Down
7 changes: 7 additions & 0 deletions client/js/i18n/call-sites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export const STATIC_CALL_SITES = new Set<string>([
"connect.disconnected",
"connect.enabledCaps",
"connect.enterPassword",
"connect.enterServerPassword",
"connect.guestNickRequired",
"connect.guestSubmit",
"connect.guestTitle",
Expand Down Expand Up @@ -205,6 +206,9 @@ export const STATIC_CALL_SITES = new Set<string>([
"connect.saslTrustedGaveUpHint",
"connect.server",
"connect.serverAddress",
"connect.serverPasswordHint",
"connect.serverPasswordNeeded",
"connect.serverPasswordRefused",
"connect.serverPort",
"connect.signInIntro",
"connect.signInSubmit",
Expand Down Expand Up @@ -314,6 +318,9 @@ export const STATIC_CALL_SITES = new Set<string>([
"form.save",
"form.server",
"form.serverAddress",
"form.serverPassword",
"form.serverPasswordHelp",
"form.serverPasswordToggle",
"form.serverPort",
"form.servicesAccount",
"form.servicesAccountPlaceholder",
Expand Down
Loading
Loading