Merge pull request #3844 from thelounge/xpaw/certfp
CertFP support; separate SASL configuration
This commit is contained in:
commit
0642ae58ce
@ -6,25 +6,23 @@
|
|||||||
<form class="container" method="post" action="" @submit.prevent="onSubmit">
|
<form class="container" method="post" action="" @submit.prevent="onSubmit">
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
<template v-if="defaults.uuid">
|
<template v-if="defaults.uuid">
|
||||||
<input type="hidden" name="uuid" :value="defaults.uuid" />
|
<input v-model="defaults.uuid" type="hidden" name="uuid" />
|
||||||
Edit {{ defaults.name }}
|
Edit {{ defaults.name }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
Connect
|
Connect
|
||||||
<template v-if="!config.displayNetwork && config.lockNetwork">
|
<template v-if="config.lockNetwork">to {{ defaults.name }}</template>
|
||||||
to {{ defaults.name }}
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
</h1>
|
</h1>
|
||||||
<template v-if="config.displayNetwork">
|
<template v-if="!config.lockNetwork">
|
||||||
<h2>Network settings</h2>
|
<h2>Network settings</h2>
|
||||||
<div class="connect-row">
|
<div class="connect-row">
|
||||||
<label for="connect:name">Name</label>
|
<label for="connect:name">Name</label>
|
||||||
<input
|
<input
|
||||||
id="connect:name"
|
id="connect:name"
|
||||||
|
v-model="defaults.name"
|
||||||
class="input"
|
class="input"
|
||||||
name="name"
|
name="name"
|
||||||
:value="defaults.name"
|
|
||||||
maxlength="100"
|
maxlength="100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -33,41 +31,52 @@
|
|||||||
<div class="input-wrap">
|
<div class="input-wrap">
|
||||||
<input
|
<input
|
||||||
id="connect:host"
|
id="connect:host"
|
||||||
|
v-model="defaults.host"
|
||||||
class="input"
|
class="input"
|
||||||
name="host"
|
name="host"
|
||||||
:value="defaults.host"
|
|
||||||
aria-label="Server address"
|
aria-label="Server address"
|
||||||
maxlength="255"
|
maxlength="255"
|
||||||
required
|
required
|
||||||
:disabled="config.lockNetwork ? true : false"
|
|
||||||
/>
|
/>
|
||||||
<span id="connect:portseparator">:</span>
|
<span id="connect:portseparator">:</span>
|
||||||
<input
|
<input
|
||||||
id="connect:port"
|
id="connect:port"
|
||||||
ref="serverPort"
|
v-model="defaults.port"
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
max="65535"
|
max="65535"
|
||||||
name="port"
|
name="port"
|
||||||
:value="defaults.port"
|
|
||||||
aria-label="Server port"
|
aria-label="Server port"
|
||||||
:disabled="config.lockNetwork ? true : false"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="connect-row">
|
||||||
|
<label for="connect:password">Password</label>
|
||||||
|
<RevealPassword
|
||||||
|
v-slot:default="slotProps"
|
||||||
|
class="input-wrap password-container"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="connect:password"
|
||||||
|
v-model="defaults.password"
|
||||||
|
class="input"
|
||||||
|
:type="slotProps.isVisible ? 'text' : 'password'"
|
||||||
|
placeholder="Server password (optional)"
|
||||||
|
name="password"
|
||||||
|
maxlength="300"
|
||||||
|
/>
|
||||||
|
</RevealPassword>
|
||||||
|
</div>
|
||||||
<div class="connect-row">
|
<div class="connect-row">
|
||||||
<label></label>
|
<label></label>
|
||||||
<div class="input-wrap">
|
<div class="input-wrap">
|
||||||
<label class="tls">
|
<label class="tls">
|
||||||
<input
|
<input
|
||||||
|
v-model="defaults.tls"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="tls"
|
name="tls"
|
||||||
:checked="defaults.tls ? true : false"
|
:disabled="defaults.hasSTSPolicy"
|
||||||
:disabled="
|
|
||||||
config.lockNetwork || defaults.hasSTSPolicy ? true : false
|
|
||||||
"
|
|
||||||
@change="onSecureChanged"
|
|
||||||
/>
|
/>
|
||||||
Use secure connection (TLS)
|
Use secure connection (TLS)
|
||||||
<span
|
<span
|
||||||
@ -79,10 +88,9 @@
|
|||||||
</label>
|
</label>
|
||||||
<label class="tls">
|
<label class="tls">
|
||||||
<input
|
<input
|
||||||
|
v-model="defaults.rejectUnauthorized"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="rejectUnauthorized"
|
name="rejectUnauthorized"
|
||||||
:checked="defaults.rejectUnauthorized ? true : false"
|
|
||||||
:disabled="config.lockNetwork ? true : false"
|
|
||||||
/>
|
/>
|
||||||
Only allow trusted certificates
|
Only allow trusted certificates
|
||||||
</label>
|
</label>
|
||||||
@ -95,10 +103,10 @@
|
|||||||
<label for="connect:nick">Nick</label>
|
<label for="connect:nick">Nick</label>
|
||||||
<input
|
<input
|
||||||
id="connect:nick"
|
id="connect:nick"
|
||||||
|
v-model="defaults.nick"
|
||||||
class="input nick"
|
class="input nick"
|
||||||
name="nick"
|
name="nick"
|
||||||
pattern="[^\s:!@]+"
|
pattern="[^\s:!@]+"
|
||||||
:value="defaults.nick"
|
|
||||||
maxlength="100"
|
maxlength="100"
|
||||||
required
|
required
|
||||||
@input="onNickChanged"
|
@input="onNickChanged"
|
||||||
@ -110,68 +118,207 @@
|
|||||||
<input
|
<input
|
||||||
id="connect:username"
|
id="connect:username"
|
||||||
ref="usernameInput"
|
ref="usernameInput"
|
||||||
|
v-model="defaults.username"
|
||||||
class="input username"
|
class="input username"
|
||||||
name="username"
|
name="username"
|
||||||
:value="defaults.username"
|
|
||||||
maxlength="100"
|
maxlength="100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="connect-row">
|
|
||||||
<label for="connect:password">Password</label>
|
|
||||||
<RevealPassword v-slot:default="slotProps" class="input-wrap password-container">
|
|
||||||
<input
|
|
||||||
id="connect:password"
|
|
||||||
v-model="defaults.password"
|
|
||||||
class="input"
|
|
||||||
:type="slotProps.isVisible ? 'text' : 'password'"
|
|
||||||
name="password"
|
|
||||||
maxlength="300"
|
|
||||||
/>
|
|
||||||
</RevealPassword>
|
|
||||||
</div>
|
|
||||||
<div class="connect-row">
|
<div class="connect-row">
|
||||||
<label for="connect:realname">Real name</label>
|
<label for="connect:realname">Real name</label>
|
||||||
<input
|
<input
|
||||||
id="connect:realname"
|
id="connect:realname"
|
||||||
|
v-model="defaults.realname"
|
||||||
class="input"
|
class="input"
|
||||||
name="realname"
|
name="realname"
|
||||||
:value="defaults.realname"
|
|
||||||
maxlength="300"
|
maxlength="300"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="defaults.uuid">
|
<template v-if="defaults.uuid && !$store.state.serverConfiguration.public">
|
||||||
<div class="connect-row">
|
<div class="connect-row">
|
||||||
<label for="connect:commands">Commands</label>
|
<label for="connect:commands">
|
||||||
|
Commands
|
||||||
|
<span
|
||||||
|
class="tooltipped tooltipped-ne tooltipped-no-delay"
|
||||||
|
aria-label="One /command per line.
|
||||||
|
Each command will be executed in
|
||||||
|
the server tab on new connection"
|
||||||
|
>
|
||||||
|
<button class="extra-help" />
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
id="connect:commands"
|
id="connect:commands"
|
||||||
|
ref="commandsInput"
|
||||||
|
:value="defaults.commands ? defaults.commands.join('\n') : ''"
|
||||||
class="input"
|
class="input"
|
||||||
name="commands"
|
name="commands"
|
||||||
placeholder="One /command per line, each command will be executed in the server tab on new connection"
|
@input="resizeCommandsInput"
|
||||||
:value="defaults.commands ? defaults.commands.join('\n') : ''"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<button type="submit" class="btn" :disabled="disabled ? true : false">
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else-if="!defaults.uuid">
|
||||||
<div class="connect-row">
|
<div class="connect-row">
|
||||||
<label for="connect:channels">Channels</label>
|
<label for="connect:channels">Channels</label>
|
||||||
<input id="connect:channels" class="input" name="join" :value="defaults.join" />
|
<input
|
||||||
</div>
|
id="connect:channels"
|
||||||
<div>
|
v-model="defaults.join"
|
||||||
<button type="submit" class="btn" :disabled="disabled ? true : false">
|
class="input"
|
||||||
Connect
|
name="join"
|
||||||
</button>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-if="$store.state.serverConfiguration.public">
|
||||||
|
<template v-if="config.lockNetwork">
|
||||||
|
<div class="connect-row">
|
||||||
|
<label></label>
|
||||||
|
<div class="input-wrap">
|
||||||
|
<label class="tls">
|
||||||
|
<input v-model="displayPasswordField" type="checkbox" />
|
||||||
|
I have a password
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="displayPasswordField" class="connect-row">
|
||||||
|
<label for="connect:password">Password</label>
|
||||||
|
<RevealPassword
|
||||||
|
v-slot:default="slotProps"
|
||||||
|
class="input-wrap password-container"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="connect:password"
|
||||||
|
ref="publicPassword"
|
||||||
|
v-model="defaults.password"
|
||||||
|
class="input"
|
||||||
|
:type="slotProps.isVisible ? 'text' : 'password'"
|
||||||
|
placeholder="Server password (optional)"
|
||||||
|
name="password"
|
||||||
|
maxlength="300"
|
||||||
|
/>
|
||||||
|
</RevealPassword>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<h2 id="label-auth">Authentication</h2>
|
||||||
|
<div class="connect-row connect-auth" role="group" aria-labelledby="label-auth">
|
||||||
|
<label class="opt">
|
||||||
|
<input
|
||||||
|
:checked="!defaults.sasl"
|
||||||
|
type="radio"
|
||||||
|
name="sasl"
|
||||||
|
value=""
|
||||||
|
@change="setSaslAuth('')"
|
||||||
|
/>
|
||||||
|
No authentication
|
||||||
|
</label>
|
||||||
|
<label class="opt">
|
||||||
|
<input
|
||||||
|
:checked="defaults.sasl === 'plain'"
|
||||||
|
type="radio"
|
||||||
|
name="sasl"
|
||||||
|
value="plain"
|
||||||
|
@change="setSaslAuth('plain')"
|
||||||
|
/>
|
||||||
|
Username + password (SASL PLAIN)
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
v-if="!$store.state.serverConfiguration.public && defaults.tls"
|
||||||
|
class="opt"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
:checked="defaults.sasl === 'external'"
|
||||||
|
type="radio"
|
||||||
|
name="sasl"
|
||||||
|
value="external"
|
||||||
|
@change="setSaslAuth('external')"
|
||||||
|
/>
|
||||||
|
Client certificate (SASL EXTERNAL)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="defaults.sasl === 'plain'">
|
||||||
|
<div class="connect-row">
|
||||||
|
<label for="connect:username">Account</label>
|
||||||
|
<input
|
||||||
|
id="connect:saslAccount"
|
||||||
|
v-model="defaults.saslAccount"
|
||||||
|
class="input"
|
||||||
|
name="saslAccount"
|
||||||
|
maxlength="100"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="connect-row">
|
||||||
|
<label for="connect:password">Password</label>
|
||||||
|
<RevealPassword
|
||||||
|
v-slot:default="slotProps"
|
||||||
|
class="input-wrap password-container"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="connect:saslPassword"
|
||||||
|
v-model="defaults.saslPassword"
|
||||||
|
class="input"
|
||||||
|
:type="slotProps.isVisible ? 'text' : 'password'"
|
||||||
|
name="saslPassword"
|
||||||
|
maxlength="300"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</RevealPassword>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else-if="defaults.sasl === 'external'" class="connect-sasl-external">
|
||||||
|
<p>
|
||||||
|
The Lounge automatically generates and manages the client certificate.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
On the IRC server, you will need to tell the services to attach the
|
||||||
|
certificate fingerprint (certfp) to your account, for example:
|
||||||
|
</p>
|
||||||
|
<pre><code>/msg NickServ CERT ADD</code></pre>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="btn" :disabled="disabled ? true : false">
|
||||||
|
<template v-if="defaults.uuid">Save network</template>
|
||||||
|
<template v-else>Connect</template>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#connect .connect-auth {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#connect .connect-auth .opt {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#connect .connect-auth input {
|
||||||
|
margin: 3px 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#connect .connect-sasl-external {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #d9edf7;
|
||||||
|
color: #31708f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#connect .connect-sasl-external pre {
|
||||||
|
margin: 0;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import RevealPassword from "./RevealPassword.vue";
|
import RevealPassword from "./RevealPassword.vue";
|
||||||
import SidebarToggle from "./SidebarToggle.vue";
|
import SidebarToggle from "./SidebarToggle.vue";
|
||||||
@ -191,9 +338,33 @@ export default {
|
|||||||
return {
|
return {
|
||||||
config: this.$store.state.serverConfiguration,
|
config: this.$store.state.serverConfiguration,
|
||||||
previousUsername: this.defaults.username,
|
previousUsername: this.defaults.username,
|
||||||
|
displayPasswordField: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
displayPasswordField(value) {
|
||||||
|
if (value) {
|
||||||
|
this.$nextTick(() => this.$refs.publicPassword.focus());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaults.commands"() {
|
||||||
|
this.$nextTick(this.resizeCommandsInput);
|
||||||
|
},
|
||||||
|
"defaults.tls"(isSecureChecked) {
|
||||||
|
const ports = [6667, 6697];
|
||||||
|
const newPort = isSecureChecked ? 0 : 1;
|
||||||
|
|
||||||
|
// If you disable TLS and current port is 6697,
|
||||||
|
// set it to 6667, and vice versa
|
||||||
|
if (this.defaults.port === ports[newPort]) {
|
||||||
|
this.defaults.port = ports[1 - newPort];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
setSaslAuth(type) {
|
||||||
|
this.defaults.sasl = type;
|
||||||
|
},
|
||||||
onNickChanged(event) {
|
onNickChanged(event) {
|
||||||
// Username input is not available when useHexIp is set
|
// Username input is not available when useHexIp is set
|
||||||
if (!this.$refs.usernameInput) {
|
if (!this.$refs.usernameInput) {
|
||||||
@ -209,16 +380,6 @@ export default {
|
|||||||
|
|
||||||
this.previousUsername = event.target.value;
|
this.previousUsername = event.target.value;
|
||||||
},
|
},
|
||||||
onSecureChanged(event) {
|
|
||||||
const ports = ["6667", "6697"];
|
|
||||||
const newPort = event.target.checked ? 0 : 1;
|
|
||||||
|
|
||||||
// If you disable TLS and current port is 6697,
|
|
||||||
// set it to 6667, and vice versa
|
|
||||||
if (this.$refs.serverPort.value === ports[newPort]) {
|
|
||||||
this.$refs.serverPort.value = ports[1 - newPort];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSubmit(event) {
|
onSubmit(event) {
|
||||||
const formData = new FormData(event.target);
|
const formData = new FormData(event.target);
|
||||||
const data = {};
|
const data = {};
|
||||||
@ -229,6 +390,18 @@ export default {
|
|||||||
|
|
||||||
this.handleSubmit(data);
|
this.handleSubmit(data);
|
||||||
},
|
},
|
||||||
|
resizeCommandsInput() {
|
||||||
|
if (!this.$refs.commandsInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset height first so it can down size
|
||||||
|
this.$refs.commandsInput.style.height = "";
|
||||||
|
|
||||||
|
// 2 pixels to account for the border
|
||||||
|
this.$refs.commandsInput.style.height =
|
||||||
|
Math.ceil(this.$refs.commandsInput.scrollHeight + 2) + "px";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -59,16 +59,11 @@ export default {
|
|||||||
// When the network is locked, URL overrides should not affect disabled fields
|
// When the network is locked, URL overrides should not affect disabled fields
|
||||||
if (
|
if (
|
||||||
this.$store.state.serverConfiguration.lockNetwork &&
|
this.$store.state.serverConfiguration.lockNetwork &&
|
||||||
["host", "port", "tls", "rejectUnauthorized"].includes(key)
|
["name", "host", "port", "tls", "rejectUnauthorized"].includes(key)
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the network is not displayed, its name in the UI is not customizable
|
|
||||||
if (!this.$store.state.serverConfiguration.displayNetwork && key === "name") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === "join") {
|
if (key === "join") {
|
||||||
value = value
|
value = value
|
||||||
.split(",")
|
.split(",")
|
||||||
|
@ -481,6 +481,12 @@ This may break orientation if your browser does not support that."
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
textarea#user-specified-css-input {
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import socket from "../../js/socket";
|
import socket from "../../js/socket";
|
||||||
import webpush from "../../js/webpush";
|
import webpush from "../../js/webpush";
|
||||||
|
@ -288,6 +288,7 @@ p {
|
|||||||
.channel-list-item::before,
|
.channel-list-item::before,
|
||||||
#footer .icon,
|
#footer .icon,
|
||||||
#chat .count::before,
|
#chat .count::before,
|
||||||
|
#connect .extra-help,
|
||||||
#settings .extra-help,
|
#settings .extra-help,
|
||||||
#settings #play::before,
|
#settings #play::before,
|
||||||
#form #upload::before,
|
#form #upload::before,
|
||||||
@ -507,6 +508,7 @@ p {
|
|||||||
line-height: 45px;
|
line-height: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#connect .extra-help::before,
|
||||||
#settings .extra-help::before {
|
#settings .extra-help::before {
|
||||||
content: "\f059"; /* http://fontawesome.io/icon/question-circle/ */
|
content: "\f059"; /* http://fontawesome.io/icon/question-circle/ */
|
||||||
}
|
}
|
||||||
@ -938,7 +940,6 @@ background on hover (unless active) */
|
|||||||
|
|
||||||
textarea.input {
|
textarea.input {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
height: 100px;
|
|
||||||
min-height: 35px;
|
min-height: 35px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@ -1826,8 +1827,8 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
|||||||
}
|
}
|
||||||
|
|
||||||
#connect .btn {
|
#connect .btn {
|
||||||
margin-left: 25%;
|
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#settings .apple-push-unsupported,
|
#settings .apple-push-unsupported,
|
||||||
@ -1874,6 +1875,7 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
|||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#connect .extra-help,
|
||||||
#settings .extra-help {
|
#settings .extra-help {
|
||||||
cursor: help;
|
cursor: help;
|
||||||
}
|
}
|
||||||
@ -2621,11 +2623,6 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#connect .btn {
|
|
||||||
margin-left: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#help .help-version-title {
|
#help .help-version-title {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
@ -238,25 +238,11 @@ module.exports = {
|
|||||||
join: "#thelounge",
|
join: "#thelounge",
|
||||||
},
|
},
|
||||||
|
|
||||||
// ### `displayNetwork`
|
|
||||||
//
|
|
||||||
// When set to `false`, network fields will not be shown in the "Connect"
|
|
||||||
// window.
|
|
||||||
//
|
|
||||||
// Note that even though users cannot access and set these fields, they can
|
|
||||||
// still connect to other networks using the `/connect` command. See the
|
|
||||||
// `lockNetwork` setting to restrict users from connecting to other networks.
|
|
||||||
//
|
|
||||||
// This value is set to `true` by default.
|
|
||||||
displayNetwork: true,
|
|
||||||
|
|
||||||
// ### `lockNetwork`
|
// ### `lockNetwork`
|
||||||
//
|
//
|
||||||
// When set to `true`, users will not be able to modify host, port and TLS
|
// When set to `true`, users will not be able to modify host, port and TLS
|
||||||
// settings and will be limited to the configured network.
|
// settings and will be limited to the configured network.
|
||||||
//
|
// These fields will also be hidden from the UI.
|
||||||
// It is often useful to use it with `displayNetwork` when setting The
|
|
||||||
// Lounge as a public web client for a specific IRC network.
|
|
||||||
//
|
//
|
||||||
// This value is set to `false` by default.
|
// This value is set to `false` by default.
|
||||||
lockNetwork: false,
|
lockNetwork: false,
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
"linkify-it": "2.2.0",
|
"linkify-it": "2.2.0",
|
||||||
"lodash": "4.17.15",
|
"lodash": "4.17.15",
|
||||||
"mime-types": "2.1.26",
|
"mime-types": "2.1.26",
|
||||||
|
"node-forge": "0.9.1",
|
||||||
"package-json": "6.5.0",
|
"package-json": "6.5.0",
|
||||||
"read": "1.0.7",
|
"read": "1.0.7",
|
||||||
"read-chunk": "3.2.0",
|
"read-chunk": "3.2.0",
|
||||||
|
@ -227,7 +227,7 @@ Client.prototype.connect = function (args, isStartup = false) {
|
|||||||
const network = new Network({
|
const network = new Network({
|
||||||
uuid: args.uuid,
|
uuid: args.uuid,
|
||||||
name: String(
|
name: String(
|
||||||
args.name || (Helper.config.displayNetwork ? "" : Helper.config.defaults.name) || ""
|
args.name || (Helper.config.lockNetwork ? Helper.config.defaults.name : "") || ""
|
||||||
),
|
),
|
||||||
host: String(args.host || ""),
|
host: String(args.host || ""),
|
||||||
port: parseInt(args.port, 10),
|
port: parseInt(args.port, 10),
|
||||||
@ -238,6 +238,9 @@ Client.prototype.connect = function (args, isStartup = false) {
|
|||||||
nick: String(args.nick || ""),
|
nick: String(args.nick || ""),
|
||||||
username: String(args.username || ""),
|
username: String(args.username || ""),
|
||||||
realname: String(args.realname || ""),
|
realname: String(args.realname || ""),
|
||||||
|
sasl: String(args.sasl || ""),
|
||||||
|
saslAccount: String(args.saslAccount || ""),
|
||||||
|
saslPassword: String(args.saslPassword || ""),
|
||||||
commands: args.commands || [],
|
commands: args.commands || [],
|
||||||
channels: channels,
|
channels: channels,
|
||||||
ignoreList: args.ignoreList ? args.ignoreList : [],
|
ignoreList: args.ignoreList ? args.ignoreList : [],
|
||||||
|
@ -18,6 +18,7 @@ let storagePath;
|
|||||||
let packagesPath;
|
let packagesPath;
|
||||||
let fileUploadPath;
|
let fileUploadPath;
|
||||||
let userLogsPath;
|
let userLogsPath;
|
||||||
|
let clientCertificatesPath;
|
||||||
|
|
||||||
const Helper = {
|
const Helper = {
|
||||||
config: null,
|
config: null,
|
||||||
@ -31,6 +32,7 @@ const Helper = {
|
|||||||
getUsersPath,
|
getUsersPath,
|
||||||
getUserConfigPath,
|
getUserConfigPath,
|
||||||
getUserLogsPath,
|
getUserLogsPath,
|
||||||
|
getClientCertificatesPath,
|
||||||
setHome,
|
setHome,
|
||||||
getVersion,
|
getVersion,
|
||||||
getVersionCacheBust,
|
getVersionCacheBust,
|
||||||
@ -100,6 +102,7 @@ function setHome(newPath) {
|
|||||||
fileUploadPath = path.join(homePath, "uploads");
|
fileUploadPath = path.join(homePath, "uploads");
|
||||||
packagesPath = path.join(homePath, "packages");
|
packagesPath = path.join(homePath, "packages");
|
||||||
userLogsPath = path.join(homePath, "logs");
|
userLogsPath = path.join(homePath, "logs");
|
||||||
|
clientCertificatesPath = path.join(homePath, "certificates");
|
||||||
|
|
||||||
// Reload config from new home location
|
// Reload config from new home location
|
||||||
if (fs.existsSync(configPath)) {
|
if (fs.existsSync(configPath)) {
|
||||||
@ -122,16 +125,6 @@ function setHome(newPath) {
|
|||||||
mergeConfig(this.config, userConfig);
|
mergeConfig(this.config, userConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.config.displayNetwork && !this.config.lockNetwork) {
|
|
||||||
this.config.lockNetwork = true;
|
|
||||||
|
|
||||||
log.warn(
|
|
||||||
`${colors.bold("displayNetwork")} and ${colors.bold(
|
|
||||||
"lockNetwork"
|
|
||||||
)} are false, setting ${colors.bold("lockNetwork")} to true.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.config.fileUpload.baseUrl) {
|
if (this.config.fileUpload.baseUrl) {
|
||||||
try {
|
try {
|
||||||
new URL("test/file.png", this.config.fileUpload.baseUrl);
|
new URL("test/file.png", this.config.fileUpload.baseUrl);
|
||||||
@ -185,6 +178,10 @@ function getUserLogsPath() {
|
|||||||
return userLogsPath;
|
return userLogsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getClientCertificatesPath() {
|
||||||
|
return clientCertificatesPath;
|
||||||
|
}
|
||||||
|
|
||||||
function getStoragePath() {
|
function getStoragePath() {
|
||||||
return storagePath;
|
return storagePath;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ const Chan = require("./chan");
|
|||||||
const Msg = require("./msg");
|
const Msg = require("./msg");
|
||||||
const Helper = require("../helper");
|
const Helper = require("../helper");
|
||||||
const STSPolicies = require("../plugins/sts");
|
const STSPolicies = require("../plugins/sts");
|
||||||
|
const ClientCertificate = require("../plugins/clientCertificate");
|
||||||
|
|
||||||
module.exports = Network;
|
module.exports = Network;
|
||||||
|
|
||||||
@ -34,6 +35,9 @@ function Network(attr) {
|
|||||||
commands: [],
|
commands: [],
|
||||||
username: "",
|
username: "",
|
||||||
realname: "",
|
realname: "",
|
||||||
|
sasl: "",
|
||||||
|
saslAccount: "",
|
||||||
|
saslPassword: "",
|
||||||
channels: [],
|
channels: [],
|
||||||
irc: null,
|
irc: null,
|
||||||
serverOptions: {
|
serverOptions: {
|
||||||
@ -81,11 +85,21 @@ Network.prototype.validate = function (client) {
|
|||||||
this.password = cleanString(this.password);
|
this.password = cleanString(this.password);
|
||||||
this.host = cleanString(this.host).toLowerCase();
|
this.host = cleanString(this.host).toLowerCase();
|
||||||
this.name = cleanString(this.name);
|
this.name = cleanString(this.name);
|
||||||
|
this.saslAccount = cleanString(this.saslAccount);
|
||||||
|
this.saslPassword = cleanString(this.saslPassword);
|
||||||
|
|
||||||
if (!this.port) {
|
if (!this.port) {
|
||||||
this.port = this.tls ? 6697 : 6667;
|
this.port = this.tls ? 6697 : 6667;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!["", "plain", "external"].includes(this.sasl)) {
|
||||||
|
this.sasl = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.tls) {
|
||||||
|
ClientCertificate.remove(this.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
if (Helper.config.lockNetwork) {
|
if (Helper.config.lockNetwork) {
|
||||||
// This check is needed to prevent invalid user configurations
|
// This check is needed to prevent invalid user configurations
|
||||||
if (
|
if (
|
||||||
@ -106,6 +120,7 @@ Network.prototype.validate = function (client) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.name = Helper.config.defaults.name;
|
||||||
this.host = Helper.config.defaults.host;
|
this.host = Helper.config.defaults.host;
|
||||||
this.port = Helper.config.defaults.port;
|
this.port = Helper.config.defaults.port;
|
||||||
this.tls = Helper.config.defaults.tls;
|
this.tls = Helper.config.defaults.tls;
|
||||||
@ -148,24 +163,17 @@ Network.prototype.validate = function (client) {
|
|||||||
Network.prototype.createIrcFramework = function (client) {
|
Network.prototype.createIrcFramework = function (client) {
|
||||||
this.irc = new IrcFramework.Client({
|
this.irc = new IrcFramework.Client({
|
||||||
version: false, // We handle it ourselves
|
version: false, // We handle it ourselves
|
||||||
host: this.host,
|
|
||||||
port: this.port,
|
|
||||||
nick: this.nick,
|
|
||||||
username: Helper.config.useHexIp ? Helper.ip2hex(client.config.browser.ip) : this.username,
|
|
||||||
gecos: this.realname,
|
|
||||||
password: this.password,
|
|
||||||
tls: this.tls,
|
|
||||||
outgoing_addr: Helper.config.bind,
|
outgoing_addr: Helper.config.bind,
|
||||||
rejectUnauthorized: this.rejectUnauthorized,
|
|
||||||
enable_chghost: true,
|
enable_chghost: true,
|
||||||
enable_echomessage: true,
|
enable_echomessage: true,
|
||||||
enable_setname: true,
|
enable_setname: true,
|
||||||
auto_reconnect: true,
|
auto_reconnect: true,
|
||||||
auto_reconnect_wait: 10000 + Math.floor(Math.random() * 1000), // If multiple users are connected to the same network, randomize their reconnections a little
|
auto_reconnect_wait: 10000 + Math.floor(Math.random() * 1000), // If multiple users are connected to the same network, randomize their reconnections a little
|
||||||
auto_reconnect_max_retries: 360, // At least one hour (plus timeouts) worth of reconnections
|
auto_reconnect_max_retries: 360, // At least one hour (plus timeouts) worth of reconnections
|
||||||
webirc: this.createWebIrc(client),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.setIrcFrameworkOptions(client);
|
||||||
|
|
||||||
this.irc.requestCap([
|
this.irc.requestCap([
|
||||||
"znc.in/self-message", // Legacy echo-message for ZNC
|
"znc.in/self-message", // Legacy echo-message for ZNC
|
||||||
]);
|
]);
|
||||||
@ -177,6 +185,36 @@ Network.prototype.createIrcFramework = function (client) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Network.prototype.setIrcFrameworkOptions = function (client) {
|
||||||
|
this.irc.options.host = this.host;
|
||||||
|
this.irc.options.port = this.port;
|
||||||
|
this.irc.options.password = this.password;
|
||||||
|
this.irc.options.nick = this.nick;
|
||||||
|
this.irc.options.username = Helper.config.useHexIp
|
||||||
|
? Helper.ip2hex(client.config.browser.ip)
|
||||||
|
: this.username;
|
||||||
|
this.irc.options.gecos = this.realname;
|
||||||
|
this.irc.options.tls = this.tls;
|
||||||
|
this.irc.options.rejectUnauthorized = this.rejectUnauthorized;
|
||||||
|
this.irc.options.webirc = this.createWebIrc(client);
|
||||||
|
|
||||||
|
this.irc.options.client_certificate = this.tls ? ClientCertificate.get(this.uuid) : null;
|
||||||
|
|
||||||
|
if (!this.sasl) {
|
||||||
|
delete this.irc.options.sasl_mechanism;
|
||||||
|
delete this.irc.options.account;
|
||||||
|
} else if (this.sasl === "external") {
|
||||||
|
this.irc.options.sasl_mechanism = "EXTERNAL";
|
||||||
|
this.irc.options.account = {};
|
||||||
|
} else if (this.sasl === "plain") {
|
||||||
|
delete this.irc.options.sasl_mechanism;
|
||||||
|
this.irc.options.account = {
|
||||||
|
account: this.saslAccount,
|
||||||
|
password: this.saslPassword,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Network.prototype.createWebIrc = function (client) {
|
Network.prototype.createWebIrc = function (client) {
|
||||||
if (
|
if (
|
||||||
!Helper.config.webirc ||
|
!Helper.config.webirc ||
|
||||||
@ -221,6 +259,9 @@ Network.prototype.edit = function (client, args) {
|
|||||||
this.password = String(args.password || "");
|
this.password = String(args.password || "");
|
||||||
this.username = String(args.username || "");
|
this.username = String(args.username || "");
|
||||||
this.realname = String(args.realname || "");
|
this.realname = String(args.realname || "");
|
||||||
|
this.sasl = String(args.sasl || "");
|
||||||
|
this.saslAccount = String(args.saslAccount || "");
|
||||||
|
this.saslPassword = String(args.saslPassword || "");
|
||||||
|
|
||||||
// Split commands into an array
|
// Split commands into an array
|
||||||
this.commands = String(args.commands || "")
|
this.commands = String(args.commands || "")
|
||||||
@ -243,7 +284,7 @@ Network.prototype.edit = function (client, args) {
|
|||||||
// Send new nick straight away
|
// Send new nick straight away
|
||||||
this.irc.changeNick(this.nick);
|
this.irc.changeNick(this.nick);
|
||||||
} else {
|
} else {
|
||||||
this.irc.options.nick = this.irc.user.nick = this.nick;
|
this.irc.user.nick = this.nick;
|
||||||
|
|
||||||
// Update UI nick straight away if IRC is not connected
|
// Update UI nick straight away if IRC is not connected
|
||||||
client.emit("nick", {
|
client.emit("nick", {
|
||||||
@ -261,16 +302,10 @@ Network.prototype.edit = function (client, args) {
|
|||||||
this.irc.raw("SETNAME", this.realname);
|
this.irc.raw("SETNAME", this.realname);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.irc.options.host = this.host;
|
this.setIrcFrameworkOptions(client);
|
||||||
this.irc.options.port = this.port;
|
|
||||||
this.irc.options.password = this.password;
|
|
||||||
this.irc.options.gecos = this.irc.user.gecos = this.realname;
|
|
||||||
this.irc.options.tls = this.tls;
|
|
||||||
this.irc.options.rejectUnauthorized = this.rejectUnauthorized;
|
|
||||||
|
|
||||||
if (!Helper.config.useHexIp) {
|
this.irc.user.username = this.irc.options.username;
|
||||||
this.irc.options.username = this.irc.user.username = this.username;
|
this.irc.user.gecos = this.irc.options.gecos;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client.save();
|
client.save();
|
||||||
@ -297,6 +332,10 @@ Network.prototype.setNick = function (nick) {
|
|||||||
if (this.keepNick === nick) {
|
if (this.keepNick === nick) {
|
||||||
this.keepNick = null;
|
this.keepNick = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.irc) {
|
||||||
|
this.irc.options.nick = nick;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -385,26 +424,24 @@ Network.prototype.quit = function (quitMessage) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Network.prototype.exportForEdit = function () {
|
Network.prototype.exportForEdit = function () {
|
||||||
let fieldsToReturn;
|
const fieldsToReturn = [
|
||||||
|
"uuid",
|
||||||
|
"name",
|
||||||
|
"nick",
|
||||||
|
"password",
|
||||||
|
"username",
|
||||||
|
"realname",
|
||||||
|
"sasl",
|
||||||
|
"saslAccount",
|
||||||
|
"saslPassword",
|
||||||
|
"commands",
|
||||||
|
];
|
||||||
|
|
||||||
if (Helper.config.displayNetwork) {
|
if (!Helper.config.lockNetwork) {
|
||||||
// Return fields required to edit a network
|
fieldsToReturn.push("host");
|
||||||
fieldsToReturn = [
|
fieldsToReturn.push("port");
|
||||||
"uuid",
|
fieldsToReturn.push("tls");
|
||||||
"nick",
|
fieldsToReturn.push("rejectUnauthorized");
|
||||||
"name",
|
|
||||||
"host",
|
|
||||||
"port",
|
|
||||||
"tls",
|
|
||||||
"rejectUnauthorized",
|
|
||||||
"password",
|
|
||||||
"username",
|
|
||||||
"realname",
|
|
||||||
"commands",
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
// Same fields as in getClientConfiguration when network is hidden
|
|
||||||
fieldsToReturn = ["name", "nick", "username", "password", "realname"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = _.pick(this, fieldsToReturn);
|
const data = _.pick(this, fieldsToReturn);
|
||||||
@ -428,6 +465,9 @@ Network.prototype.export = function () {
|
|||||||
"password",
|
"password",
|
||||||
"username",
|
"username",
|
||||||
"realname",
|
"realname",
|
||||||
|
"sasl",
|
||||||
|
"saslAccount",
|
||||||
|
"saslPassword",
|
||||||
"commands",
|
"commands",
|
||||||
"ignoreList",
|
"ignoreList",
|
||||||
]);
|
]);
|
||||||
|
134
src/plugins/clientCertificate.js
Normal file
134
src/plugins/clientCertificate.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
const {md, pki} = require("node-forge");
|
||||||
|
const log = require("../log");
|
||||||
|
const Helper = require("../helper");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
get,
|
||||||
|
remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
function get(uuid) {
|
||||||
|
if (Helper.config.public) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderPath = Helper.getClientCertificatesPath();
|
||||||
|
const paths = getPaths(folderPath, uuid);
|
||||||
|
|
||||||
|
if (!fs.existsSync(paths.privateKeyPath) || !fs.existsSync(paths.certificatePath)) {
|
||||||
|
return generateAndWrite(folderPath, paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
private_key: fs.readFileSync(paths.privateKeyPath, "utf-8"),
|
||||||
|
certificate: fs.readFileSync(paths.certificatePath, "utf-8"),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
log.error("Unable to remove certificate", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(uuid) {
|
||||||
|
if (Helper.config.public) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paths = getPaths(Helper.getClientCertificatesPath(), uuid);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(paths.privateKeyPath)) {
|
||||||
|
fs.unlinkSync(paths.privateKeyPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(paths.certificatePath)) {
|
||||||
|
fs.unlinkSync(paths.certificatePath);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error("Unable to remove certificate", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateAndWrite(folderPath, paths) {
|
||||||
|
const certificate = generate();
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(folderPath, {recursive: true});
|
||||||
|
|
||||||
|
fs.writeFileSync(paths.privateKeyPath, certificate.private_key, {
|
||||||
|
mode: 0o600,
|
||||||
|
});
|
||||||
|
fs.writeFileSync(paths.certificatePath, certificate.certificate, {
|
||||||
|
mode: 0o600,
|
||||||
|
});
|
||||||
|
|
||||||
|
return certificate;
|
||||||
|
} catch (e) {
|
||||||
|
log.error("Unable to write certificate", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generate() {
|
||||||
|
const keys = pki.rsa.generateKeyPair(2048);
|
||||||
|
const cert = pki.createCertificate();
|
||||||
|
|
||||||
|
cert.publicKey = keys.publicKey;
|
||||||
|
cert.serialNumber = crypto.randomBytes(16).toString("hex").toUpperCase();
|
||||||
|
|
||||||
|
// Set notBefore a day earlier just in case the time between
|
||||||
|
// the client and server is not perfectly in sync
|
||||||
|
cert.validity.notBefore = new Date();
|
||||||
|
cert.validity.notBefore.setDate(cert.validity.notBefore.getDate() - 1);
|
||||||
|
|
||||||
|
// Set notAfter 100 years into the future just in case
|
||||||
|
// the server actually validates this field
|
||||||
|
cert.validity.notAfter = new Date();
|
||||||
|
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 100);
|
||||||
|
|
||||||
|
const attrs = [
|
||||||
|
{
|
||||||
|
name: "commonName",
|
||||||
|
value: "The Lounge IRC Client",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
cert.setSubject(attrs);
|
||||||
|
cert.setIssuer(attrs);
|
||||||
|
|
||||||
|
// Set extensions that indicate this is a client authentication certificate
|
||||||
|
cert.setExtensions([
|
||||||
|
{
|
||||||
|
name: "extKeyUsage",
|
||||||
|
clientAuth: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nsCertType",
|
||||||
|
client: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Sign this certificate with a SHA256 signature
|
||||||
|
cert.sign(keys.privateKey, md.sha256.create());
|
||||||
|
|
||||||
|
const pem = {
|
||||||
|
private_key: pki.privateKeyToPem(keys.privateKey),
|
||||||
|
certificate: pki.certificateToPem(cert),
|
||||||
|
};
|
||||||
|
|
||||||
|
return pem;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPaths(folderPath, uuid) {
|
||||||
|
return {
|
||||||
|
privateKeyPath: path.join(folderPath, `${uuid}.pem`),
|
||||||
|
certificatePath: path.join(folderPath, `${uuid}.crt`),
|
||||||
|
};
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const _ = require("lodash");
|
const _ = require("lodash");
|
||||||
|
const ClientCertificate = require("../clientCertificate");
|
||||||
|
|
||||||
exports.commands = ["quit"];
|
exports.commands = ["quit"];
|
||||||
exports.allowDisconnected = true;
|
exports.allowDisconnected = true;
|
||||||
@ -18,5 +19,7 @@ exports.input = function (network, chan, cmd, args) {
|
|||||||
const quitMessage = args[0] ? args.join(" ") : null;
|
const quitMessage = args[0] ? args.join(" ") : null;
|
||||||
network.quit(quitMessage);
|
network.quit(quitMessage);
|
||||||
|
|
||||||
|
ClientCertificate.remove(network.uuid);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -706,18 +706,12 @@ function initializeClient(socket, client, token, lastMessage, openChannel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getClientConfiguration() {
|
function getClientConfiguration() {
|
||||||
const config = _.pick(Helper.config, [
|
const config = _.pick(Helper.config, ["public", "lockNetwork", "useHexIp", "prefetch"]);
|
||||||
"public",
|
|
||||||
"lockNetwork",
|
|
||||||
"displayNetwork",
|
|
||||||
"useHexIp",
|
|
||||||
"prefetch",
|
|
||||||
]);
|
|
||||||
|
|
||||||
config.fileUpload = Helper.config.fileUpload.enable;
|
config.fileUpload = Helper.config.fileUpload.enable;
|
||||||
config.ldapEnabled = Helper.config.ldap.enable;
|
config.ldapEnabled = Helper.config.ldap.enable;
|
||||||
|
|
||||||
if (config.displayNetwork) {
|
if (!config.lockNetwork) {
|
||||||
config.defaults = _.clone(Helper.config.defaults);
|
config.defaults = _.clone(Helper.config.defaults);
|
||||||
} else {
|
} else {
|
||||||
// Only send defaults that are visible on the client
|
// Only send defaults that are visible on the client
|
||||||
@ -738,6 +732,9 @@ function getClientConfiguration() {
|
|||||||
config.themes = themes.getAll();
|
config.themes = themes.getAll();
|
||||||
config.defaultTheme = Helper.config.theme;
|
config.defaultTheme = Helper.config.theme;
|
||||||
config.defaults.nick = Helper.getDefaultNick();
|
config.defaults.nick = Helper.getDefaultNick();
|
||||||
|
config.defaults.sasl = "";
|
||||||
|
config.defaults.saslAccount = "";
|
||||||
|
config.defaults.saslPassword = "";
|
||||||
|
|
||||||
if (Uploader) {
|
if (Uploader) {
|
||||||
config.fileUploadMaxFileSize = Uploader.getMaxFileSize();
|
config.fileUploadMaxFileSize = Uploader.getMaxFileSize();
|
||||||
|
1
test/fixtures/.gitignore
vendored
1
test/fixtures/.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
# Files that may be generated by tests
|
# Files that may be generated by tests
|
||||||
.thelounge/storage/
|
.thelounge/storage/
|
||||||
.thelounge/logs/
|
.thelounge/logs/
|
||||||
|
.thelounge/certificates/
|
||||||
|
|
||||||
# Fixtures contain fake packages, stored in a fake node_modules folder
|
# Fixtures contain fake packages, stored in a fake node_modules folder
|
||||||
!.thelounge/packages/node_modules/
|
!.thelounge/packages/node_modules/
|
||||||
|
@ -14,6 +14,9 @@ describe("Network", function () {
|
|||||||
uuid: "hello world",
|
uuid: "hello world",
|
||||||
awayMessage: "I am away",
|
awayMessage: "I am away",
|
||||||
name: "networkName",
|
name: "networkName",
|
||||||
|
sasl: "plain",
|
||||||
|
saslAccount: "testaccount",
|
||||||
|
saslPassword: "testpassword",
|
||||||
channels: [
|
channels: [
|
||||||
new Chan({name: "#thelounge", key: ""}),
|
new Chan({name: "#thelounge", key: ""}),
|
||||||
new Chan({name: "&foobar", key: ""}),
|
new Chan({name: "&foobar", key: ""}),
|
||||||
@ -37,6 +40,9 @@ describe("Network", function () {
|
|||||||
password: "",
|
password: "",
|
||||||
username: "",
|
username: "",
|
||||||
realname: "",
|
realname: "",
|
||||||
|
sasl: "plain",
|
||||||
|
saslAccount: "testaccount",
|
||||||
|
saslPassword: "testpassword",
|
||||||
commands: [],
|
commands: [],
|
||||||
nick: "chillin`",
|
nick: "chillin`",
|
||||||
channels: [
|
channels: [
|
||||||
@ -121,6 +127,9 @@ describe("Network", function () {
|
|||||||
username: 1234,
|
username: 1234,
|
||||||
password: 4567,
|
password: 4567,
|
||||||
realname: 8901,
|
realname: 8901,
|
||||||
|
sasl: "something",
|
||||||
|
saslAccount: 1337,
|
||||||
|
saslPassword: 1337,
|
||||||
commands: "/command 1 2 3\r\n/ping HELLO\r\r\r\r/whois test\r\n\r\n",
|
commands: "/command 1 2 3\r\n/ping HELLO\r\r\r\r/whois test\r\n\r\n",
|
||||||
ip: "newIp",
|
ip: "newIp",
|
||||||
hostname: "newHostname",
|
hostname: "newHostname",
|
||||||
@ -144,6 +153,9 @@ describe("Network", function () {
|
|||||||
expect(network.username).to.equal("1234");
|
expect(network.username).to.equal("1234");
|
||||||
expect(network.password).to.equal("4567");
|
expect(network.password).to.equal("4567");
|
||||||
expect(network.realname).to.equal("8901");
|
expect(network.realname).to.equal("8901");
|
||||||
|
expect(network.sasl).to.equal("");
|
||||||
|
expect(network.saslAccount).to.equal("1337");
|
||||||
|
expect(network.saslPassword).to.equal("1337");
|
||||||
expect(network.commands).to.deep.equal([
|
expect(network.commands).to.deep.equal([
|
||||||
"/command 1 2 3",
|
"/command 1 2 3",
|
||||||
"/ping HELLO",
|
"/ping HELLO",
|
||||||
|
53
test/plugins/clientCertificate.js
Normal file
53
test/plugins/clientCertificate.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const {expect} = require("chai");
|
||||||
|
const ClientCertificate = require("../../src/plugins/clientCertificate");
|
||||||
|
const Helper = require("../../src/helper");
|
||||||
|
|
||||||
|
describe("ClientCertificate", function () {
|
||||||
|
it("should not generate a client certificate in public mode", function () {
|
||||||
|
Helper.config.public = true;
|
||||||
|
|
||||||
|
const certificate = ClientCertificate.get("this-is-test-uuid");
|
||||||
|
expect(certificate).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should generate a client certificate", function () {
|
||||||
|
Helper.config.public = false;
|
||||||
|
const certificate = ClientCertificate.get("this-is-test-uuid");
|
||||||
|
|
||||||
|
expect(certificate.certificate).to.match(/^-----BEGIN CERTIFICATE-----/);
|
||||||
|
expect(certificate.private_key).to.match(/^-----BEGIN RSA PRIVATE KEY-----/);
|
||||||
|
|
||||||
|
const certificate2 = ClientCertificate.get("this-is-test-uuid");
|
||||||
|
expect(certificate2.certificate).to.equal(certificate.certificate);
|
||||||
|
expect(certificate2.private_key).to.equal(certificate.private_key);
|
||||||
|
|
||||||
|
Helper.config.public = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove the client certificate files", function () {
|
||||||
|
Helper.config.public = false;
|
||||||
|
|
||||||
|
const privateKeyPath = path.join(
|
||||||
|
Helper.getClientCertificatesPath(),
|
||||||
|
`this-is-test-uuid.pem`
|
||||||
|
);
|
||||||
|
const certificatePath = path.join(
|
||||||
|
Helper.getClientCertificatesPath(),
|
||||||
|
`this-is-test-uuid.crt`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(fs.existsSync(privateKeyPath)).to.be.true;
|
||||||
|
expect(fs.existsSync(certificatePath)).to.be.true;
|
||||||
|
|
||||||
|
ClientCertificate.remove("this-is-test-uuid");
|
||||||
|
|
||||||
|
expect(fs.existsSync(privateKeyPath)).to.be.false;
|
||||||
|
expect(fs.existsSync(certificatePath)).to.be.false;
|
||||||
|
|
||||||
|
Helper.config.public = true;
|
||||||
|
});
|
||||||
|
});
|
@ -113,7 +113,6 @@ describe("Server", function () {
|
|||||||
expect(data.defaultTheme).to.equal("default");
|
expect(data.defaultTheme).to.equal("default");
|
||||||
expect(data.themes).to.be.an("array");
|
expect(data.themes).to.be.an("array");
|
||||||
expect(data.lockNetwork).to.equal(false);
|
expect(data.lockNetwork).to.equal(false);
|
||||||
expect(data.displayNetwork).to.equal(true);
|
|
||||||
expect(data.useHexIp).to.equal(false);
|
expect(data.useHexIp).to.equal(false);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
@ -5848,6 +5848,11 @@ node-fetch@2.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
|
||||||
integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=
|
integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=
|
||||||
|
|
||||||
|
node-forge@0.9.1:
|
||||||
|
version "0.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5"
|
||||||
|
integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==
|
||||||
|
|
||||||
node-libs-browser@^2.2.1:
|
node-libs-browser@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
|
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
|
||||||
|
Loading…
Reference in New Issue
Block a user