mirror of
https://codeberg.org/emersion/gamja.git
synced 2024-11-25 08:48:09 -05:00
Add post-connect UI to login via SASL
If the server supports SASL and if we aren't logged in with any account, add a UI to authenticate via SASL. This allows users to login anonymously then login via SASL. This will also ease the draft/account-registration implementation.
This commit is contained in:
parent
24b50a332c
commit
3e2ac307f6
4 changed files with 120 additions and 3 deletions
|
@ -8,12 +8,13 @@ import ConnectForm from "./connect-form.js";
|
||||||
import JoinForm from "./join-form.js";
|
import JoinForm from "./join-form.js";
|
||||||
import Help from "./help.js";
|
import Help from "./help.js";
|
||||||
import NetworkForm from "./network-form.js";
|
import NetworkForm from "./network-form.js";
|
||||||
|
import AuthForm from "./auth-form.js";
|
||||||
import Composer from "./composer.js";
|
import Composer from "./composer.js";
|
||||||
import ScrollManager from "./scroll-manager.js";
|
import ScrollManager from "./scroll-manager.js";
|
||||||
import Dialog from "./dialog.js";
|
import Dialog from "./dialog.js";
|
||||||
import { html, Component, createRef } from "../lib/index.js";
|
import { html, Component, createRef } from "../lib/index.js";
|
||||||
import { strip as stripANSI } from "../lib/ansi.js";
|
import { strip as stripANSI } from "../lib/ansi.js";
|
||||||
import { SERVER_BUFFER, BufferType, ReceiptType, ServerStatus, Unread, State } from "../state.js";
|
import { SERVER_BUFFER, BufferType, ReceiptType, ServerStatus, Unread, State, getServerName } from "../state.js";
|
||||||
import commands from "../commands.js";
|
import commands from "../commands.js";
|
||||||
import { setup as setupKeybindings } from "../keybindings.js";
|
import { setup as setupKeybindings } from "../keybindings.js";
|
||||||
import * as store from "../store.js";
|
import * as store from "../store.js";
|
||||||
|
@ -191,6 +192,7 @@ export default class App extends Component {
|
||||||
this.handleNetworkSubmit = this.handleNetworkSubmit.bind(this);
|
this.handleNetworkSubmit = this.handleNetworkSubmit.bind(this);
|
||||||
this.handleNetworkRemove = this.handleNetworkRemove.bind(this);
|
this.handleNetworkRemove = this.handleNetworkRemove.bind(this);
|
||||||
this.handleDismissError = this.handleDismissError.bind(this);
|
this.handleDismissError = this.handleDismissError.bind(this);
|
||||||
|
this.handleAuthSubmit = this.handleAuthSubmit.bind(this);
|
||||||
|
|
||||||
this.saveReceipts = debounce(this.saveReceipts.bind(this), 500);
|
this.saveReceipts = debounce(this.saveReceipts.bind(this), 500);
|
||||||
|
|
||||||
|
@ -1377,6 +1379,35 @@ export default class App extends Component {
|
||||||
this.setState({ dialog: null, dialogData: null });
|
this.setState({ dialog: null, dialogData: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleAuthClick(serverID) {
|
||||||
|
let client = this.clients.get(serverID);
|
||||||
|
this.openDialog("auth", { username: client.nick });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAuthSubmit(username, password) {
|
||||||
|
let serverID = State.getActiveServerID(this.state);
|
||||||
|
let client = this.clients.get(serverID);
|
||||||
|
client.authenticate("PLAIN", { username, password }).then(() => {
|
||||||
|
let firstClient = this.clients.values().next().value;
|
||||||
|
if (client !== firstClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let autoconnect = store.autoconnect.load();
|
||||||
|
if (!autoconnect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Saving SASL PLAIN credentials");
|
||||||
|
autoconnect = {
|
||||||
|
...autoconnect,
|
||||||
|
saslPlain: { username, password },
|
||||||
|
};
|
||||||
|
store.autoconnect.put(autoconnect);
|
||||||
|
});
|
||||||
|
this.dismissDialog();
|
||||||
|
}
|
||||||
|
|
||||||
handleAddNetworkClick() {
|
handleAddNetworkClick() {
|
||||||
this.openDialog("network");
|
this.openDialog("network");
|
||||||
}
|
}
|
||||||
|
@ -1560,6 +1591,13 @@ export default class App extends Component {
|
||||||
</>
|
</>
|
||||||
`;
|
`;
|
||||||
break;
|
break;
|
||||||
|
case "auth":
|
||||||
|
dialog = html`
|
||||||
|
<${Dialog} title="Login to ${getServerName(activeServer, activeBouncerNetwork, isBouncer)}" onDismiss=${this.dismissDialog}>
|
||||||
|
<${AuthForm} username=${dialogData.username} onSubmit=${this.handleAuthSubmit}/>
|
||||||
|
</>
|
||||||
|
`;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = null;
|
let error = null;
|
||||||
|
@ -1616,7 +1654,9 @@ export default class App extends Component {
|
||||||
server=${activeServer}
|
server=${activeServer}
|
||||||
isBouncer=${isBouncer}
|
isBouncer=${isBouncer}
|
||||||
onChannelClick=${this.handleChannelClick}
|
onChannelClick=${this.handleChannelClick}
|
||||||
onNickClick=${this.handleNickClick}/>
|
onNickClick=${this.handleNickClick}
|
||||||
|
onAuthClick=${() => this.handleAuthClick(activeBuffer.server)}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
${memberList}
|
${memberList}
|
||||||
|
|
51
components/auth-form.js
Normal file
51
components/auth-form.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import { html, Component } from "../lib/index.js";
|
||||||
|
|
||||||
|
export default class NetworkForm extends Component {
|
||||||
|
state = {
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
|
||||||
|
if (props.username) {
|
||||||
|
this.state.username = props.username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(event) {
|
||||||
|
let target = event.target;
|
||||||
|
let value = target.type == "checkbox" ? target.checked : target.value;
|
||||||
|
this.setState({ [target.name]: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.props.onSubmit(this.state.username, this.state.password);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<form onChange=${this.handleChange} onSubmit=${this.handleSubmit}>
|
||||||
|
<label>
|
||||||
|
Username:<br/>
|
||||||
|
<input type="username" name="username" value=${this.state.username} required/>
|
||||||
|
</label>
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Password:<br/>
|
||||||
|
<input type="password" name="password" value=${this.state.password} required autofocus/>
|
||||||
|
</label>
|
||||||
|
<br/><br/>
|
||||||
|
|
||||||
|
<button>Login</button>
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import { html, Component } from "../lib/index.js";
|
||||||
import linkify from "../lib/linkify.js";
|
import linkify from "../lib/linkify.js";
|
||||||
import * as irc from "../lib/irc.js";
|
import * as irc from "../lib/irc.js";
|
||||||
import { strip as stripANSI } from "../lib/ansi.js";
|
import { strip as stripANSI } from "../lib/ansi.js";
|
||||||
import { BufferType, getNickURL, getChannelURL, getMessageURL } from "../state.js";
|
import { BufferType, ServerStatus, getNickURL, getChannelURL, getMessageURL } from "../state.js";
|
||||||
import * as store from "../store.js";
|
import * as store from "../store.js";
|
||||||
import Membership from "./membership.js";
|
import Membership from "./membership.js";
|
||||||
|
|
||||||
|
@ -457,6 +457,26 @@ class ProtocolHandlerNagger extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AuthNagger({ server, onClick }) {
|
||||||
|
let accDesc = "an account on this server";
|
||||||
|
if (server.isupport.has("NETWORK")) {
|
||||||
|
accDesc = "a " + server.isupport.get("NETWORK") + " account";
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="logline">
|
||||||
|
<${Timestamp}/>
|
||||||
|
${" "}
|
||||||
|
You are unauthenticated on this server, <a href="#" onClick=${handleClick}>login</a> if you have ${accDesc}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
class DateSeparator extends Component {
|
class DateSeparator extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -508,6 +528,9 @@ export default class Buffer extends Component {
|
||||||
let name = server.isupport.get("NETWORK");
|
let name = server.isupport.get("NETWORK");
|
||||||
children.push(html`<${ProtocolHandlerNagger} bouncerName=${name}/>`);
|
children.push(html`<${ProtocolHandlerNagger} bouncerName=${name}/>`);
|
||||||
}
|
}
|
||||||
|
if (buf.type == BufferType.SERVER && server.status == ServerStatus.REGISTERED && server.supportsSASLPlain && !server.account) {
|
||||||
|
children.push(html`<${AuthNagger} server=${server} onClick=${this.props.onAuthClick}/>`);
|
||||||
|
}
|
||||||
|
|
||||||
let onChannelClick = this.props.onChannelClick;
|
let onChannelClick = this.props.onChannelClick;
|
||||||
let onNickClick = this.props.onNickClick;
|
let onNickClick = this.props.onNickClick;
|
||||||
|
|
3
state.js
3
state.js
|
@ -256,6 +256,7 @@ export const State = {
|
||||||
isupport: new Map(),
|
isupport: new Map(),
|
||||||
users: new irc.CaseMapMap(null, irc.CaseMapping.RFC1459),
|
users: new irc.CaseMapMap(null, irc.CaseMapping.RFC1459),
|
||||||
account: null,
|
account: null,
|
||||||
|
supportsSASLPlain: false,
|
||||||
});
|
});
|
||||||
return [id, { servers }];
|
return [id, { servers }];
|
||||||
},
|
},
|
||||||
|
@ -346,6 +347,8 @@ export const State = {
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
case "CAP":
|
||||||
|
return updateServer({ supportsSASLPlain: client.supportsSASL("PLAIN") });
|
||||||
case irc.RPL_LOGGEDIN:
|
case irc.RPL_LOGGEDIN:
|
||||||
return updateServer({ account: msg.params[2] });
|
return updateServer({ account: msg.params[2] });
|
||||||
case irc.RPL_LOGGEDOUT:
|
case irc.RPL_LOGGEDOUT:
|
||||||
|
|
Loading…
Reference in a new issue