Preliminary SASL UI

This commit is contained in:
Pavel Djundik 2020-03-31 11:02:18 +03:00
parent f8f692af05
commit 8a281bacd8
6 changed files with 226 additions and 49 deletions

View file

@ -56,6 +56,23 @@
/>
</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">
<label></label>
<div class="input-wrap">
@ -117,19 +134,6 @@
/>
</div>
</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">
<label for="connect:realname">Real name</label>
<input
@ -151,27 +155,162 @@
:value="defaults.commands ? defaults.commands.join('\n') : ''"
/>
</div>
<div>
<button type="submit" class="btn" :disabled="disabled ? true : false">
Save
</button>
</div>
</template>
<template v-else>
<div class="connect-row">
<label for="connect:channels">Channels</label>
<input id="connect:channels" class="input" name="join" :value="defaults.join" />
</div>
<div>
<button type="submit" class="btn" :disabled="disabled ? true : false">
Connect
</button>
</template>
<template v-if="$store.state.serverConfiguration.public">
<template v-if="!config.displayNetwork">
<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"
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"
:value="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"
:value="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>
</div>
</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>
import RevealPassword from "./RevealPassword.vue";
import SidebarToggle from "./SidebarToggle.vue";
@ -191,9 +330,13 @@ export default {
return {
config: this.$store.state.serverConfiguration,
previousUsername: this.defaults.username,
displayPasswordField: false,
};
},
methods: {
setSaslAuth(type) {
this.defaults.sasl = type;
},
onNickChanged(event) {
// Username input is not available when useHexIp is set
if (!this.$refs.usernameInput) {

View file

@ -1826,8 +1826,8 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
}
#connect .btn {
margin-left: 25%;
margin-top: 15px;
width: 100%;
}
#settings .apple-push-unsupported,
@ -2621,11 +2621,6 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
flex-grow: 1;
}
#connect .btn {
margin-left: 0;
width: 100%;
}
#help .help-version-title {
flex-direction: column;
}

View file

@ -238,6 +238,9 @@ Client.prototype.connect = function (args, isStartup = false) {
nick: String(args.nick || ""),
username: String(args.username || ""),
realname: String(args.realname || ""),
sasl: String(args.sasl || ""),
saslAccount: String(args.saslAccount || ""),
saslPassword: String(args.saslPassword || ""),
commands: args.commands || [],
channels: channels,
ignoreList: args.ignoreList ? args.ignoreList : [],

View file

@ -35,6 +35,9 @@ function Network(attr) {
commands: [],
username: "",
realname: "",
sasl: "",
saslAccount: "",
saslPassword: "",
channels: [],
irc: null,
serverOptions: {
@ -82,11 +85,17 @@ Network.prototype.validate = function (client) {
this.password = cleanString(this.password);
this.host = cleanString(this.host).toLowerCase();
this.name = cleanString(this.name);
this.saslAccount = cleanString(this.saslAccount);
this.saslPassword = cleanString(this.saslPassword);
if (!this.port) {
this.port = this.tls ? 6697 : 6667;
}
if (!["", "plain", "external"].includes(this.sasl)) {
this.sasl = "";
}
if (!this.tls) {
ClientCertificate.remove(this.uuid);
}
@ -190,10 +199,18 @@ Network.prototype.setIrcFrameworkOptions = function (client) {
this.irc.options.client_certificate = this.tls ? ClientCertificate.get(this.uuid) : null;
if (this.irc.options.client_certificate && !this.irc.options.password) {
this.irc.options.sasl_mechanism = "EXTERNAL";
} else {
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,
};
}
};
@ -241,6 +258,9 @@ Network.prototype.edit = function (client, args) {
this.password = String(args.password || "");
this.username = String(args.username || "");
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
this.commands = String(args.commands || "")
@ -403,26 +423,24 @@ Network.prototype.quit = function (quitMessage) {
};
Network.prototype.exportForEdit = function () {
let fieldsToReturn;
const fieldsToReturn = [
"uuid",
"name",
"nick",
"password",
"username",
"realname",
"sasl",
"saslAccount",
"saslPassword",
"commands",
];
if (Helper.config.displayNetwork) {
// Return fields required to edit a network
fieldsToReturn = [
"uuid",
"nick",
"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"];
fieldsToReturn.push("host");
fieldsToReturn.push("port");
fieldsToReturn.push("tls");
fieldsToReturn.push("rejectUnauthorized");
}
const data = _.pick(this, fieldsToReturn);
@ -446,6 +464,9 @@ Network.prototype.export = function () {
"password",
"username",
"realname",
"sasl",
"saslAccount",
"saslPassword",
"commands",
"ignoreList",
]);

View file

@ -738,6 +738,9 @@ function getClientConfiguration() {
config.themes = themes.getAll();
config.defaultTheme = Helper.config.theme;
config.defaults.nick = Helper.getDefaultNick();
config.defaults.sasl = "";
config.defaults.saslAccount = "";
config.defaults.saslPassword = "";
if (Uploader) {
config.fileUploadMaxFileSize = Uploader.getMaxFileSize();

View file

@ -14,6 +14,9 @@ describe("Network", function () {
uuid: "hello world",
awayMessage: "I am away",
name: "networkName",
sasl: "plain",
saslAccount: "testaccount",
saslPassword: "testpassword",
channels: [
new Chan({name: "#thelounge", key: ""}),
new Chan({name: "&foobar", key: ""}),
@ -37,6 +40,9 @@ describe("Network", function () {
password: "",
username: "",
realname: "",
sasl: "plain",
saslAccount: "testaccount",
saslPassword: "testpassword",
commands: [],
nick: "chillin`",
channels: [
@ -121,6 +127,9 @@ describe("Network", function () {
username: 1234,
password: 4567,
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",
ip: "newIp",
hostname: "newHostname",
@ -144,6 +153,9 @@ describe("Network", function () {
expect(network.username).to.equal("1234");
expect(network.password).to.equal("4567");
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([
"/command 1 2 3",
"/ping HELLO",