password reset works now

This commit is contained in:
or-else
2018-10-01 19:31:50 +03:00
parent 60968b84c6
commit 13f492285e
2 changed files with 123 additions and 47 deletions
+5
View File
@@ -324,6 +324,11 @@ li.right:hover .menuTrigger {
inset 0 -0.25rem 0.25rem -0.25rem rgba(0,0,0,0.15); inset 0 -0.25rem 0.25rem -0.25rem rgba(0,0,0,0.15);
} }
/* Element which can take focus but shows no outline */
.group-focus {
outline: none;
}
/* END generic text colors */ /* END generic text colors */
/* END of basic elements */ /* END of basic elements */
+118 -47
View File
@@ -193,6 +193,21 @@ function base64DecodedLen(n) {
return Math.floor(n / 4) * 3; return Math.floor(n / 4) * 3;
} }
// Re-encode string to standard base64 encoding with padding.
// The string may be base64-URL encoded without the padding.
function base64ReEncode(str) {
if (str) {
str = str.replace("-", "+").replace("_", "/");
try {
str = btoa(atob(str));
} catch(err) {
console.log("failed to base64 re-decode string");
str = null;
}
}
return str;
}
// Convert uploaded image into a base64-encoded string possibly scaling // Convert uploaded image into a base64-encoded string possibly scaling
// linear dimensions or constraining to a square. // linear dimensions or constraining to a square.
function imageFileScaledToBase64(file, width, height, forceSquare, onSuccess, onError) { function imageFileScaledToBase64(file, width, height, forceSquare, onSuccess, onError) {
@@ -752,6 +767,7 @@ class InPlaceEdit extends React.Component {
this.handleKeyDown = this.handleKeyDown.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleStartEditing = this.handleStartEditing.bind(this); this.handleStartEditing = this.handleStartEditing.bind(this);
this.handleEditingFinished = this.handleEditingFinished.bind(this); this.handleEditingFinished = this.handleEditingFinished.bind(this);
this.handlePasswordFinished = this.handlePasswordFinished.bind(this);
} }
static getDerivedStateFromProps(nextProps, prevState) { static getDerivedStateFromProps(nextProps, prevState) {
@@ -795,8 +811,17 @@ class InPlaceEdit extends React.Component {
} }
} }
handlePasswordFinished(value) {
this.setState({active: false});
if (value && (value !== this.props.value)) {
this.props.onFinished(value);
}
}
render() { render() {
if (!this.state.active) { if (this.state.active) {
var fieldType = this.props.type || "text";
} else {
var spanText = this.props.type == "password" ? "••••••••" : this.state.value; var spanText = this.props.type == "password" ? "••••••••" : this.state.value;
var spanClass = "in-place-edit" + var spanClass = "in-place-edit" +
(this.props.readOnly ? " disabled" : ""); (this.props.readOnly ? " disabled" : "");
@@ -810,15 +835,25 @@ class InPlaceEdit extends React.Component {
} }
return ( return (
this.state.active ? this.state.active ?
<input type={this.props.type || "text"} (fieldType == "password" ?
value={this.state.value} <VisiblePassword
placeholder={this.props.placeholder} value={this.state.value}
required={this.props.required ? 'required' : ''} placeholder={this.props.placeholder}
autoComplete={this.props.autoComplete} required={this.props.required ? 'required' : ''}
autoFocus autoComplete={this.props.autoComplete}
onChange={this.handeTextChange} autoFocus={true}
onKeyDown={this.handleKeyDown} onFinished={this.handlePasswordFinished}/>
onBlur={this.handleEditingFinished} /> :
<input type={fieldType}
value={this.state.value}
placeholder={this.props.placeholder}
required={this.props.required ? 'required' : ''}
autoComplete={this.props.autoComplete}
autoFocus
onChange={this.handeTextChange}
onKeyDown={this.handleKeyDown}
onBlur={this.handleEditingFinished} />
)
: :
<span className={spanClass} onClick={this.handleStartEditing}> <span className={spanClass} onClick={this.handleStartEditing}>
<span className="content">{spanText}</span> <span className="content">{spanText}</span>
@@ -855,24 +890,37 @@ class VisiblePassword extends React.PureComponent {
} }
handleKeyDown(e) { handleKeyDown(e) {
if (e.keyCode === 27) { if (e.keyCode == 27) {
// Escape pressed // Escape pressed
this.setState({value: this.props.value, active: false}); this.setState({value: this.props.value, visible: false});
} else if (e.keyCode === 13) { if (this.props.onFinished) {
this.props.onFinished();
}
} else if (e.keyCode == 13) {
// Enter pressed // Enter pressed
this.handleEditingFinished(); this.handleEditingFinished();
} }
} }
handleEditingFinished() { handleEditingFinished(e) {
if (this.props.onFinished) { if (e) {
this.props.onFinished(this.state.value); let currentTarget = e.currentTarget;
setTimeout(() => {
if (!currentTarget.contains(document.activeElement)) {
if (this.props.onFinished) {
this.props.onFinished(this.state.value);
}
}
}, 0);
} else if (this.props.onFinished) {
this.props.onFinished(this.state.value.trim());
} }
} }
render() { render() {
return ( return (
<div> <div tabIndex="-1" className="group-focus"
onBlur={this.handleEditingFinished}>
<input className="with-visibility" <input className="with-visibility"
type={this.state.visible ? "text" : "password"} type={this.state.visible ? "text" : "password"}
value={this.state.value} value={this.state.value}
@@ -881,8 +929,7 @@ class VisiblePassword extends React.PureComponent {
autoFocus={this.props.autoFocus ? 'autoFocus' : ''} autoFocus={this.props.autoFocus ? 'autoFocus' : ''}
autoComplete={this.props.autoComplete} autoComplete={this.props.autoComplete}
onChange={this.handeTextChange} onChange={this.handeTextChange}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown} />
onBlur={this.handleEditingFinished} />
<span onClick={this.handleVisibility}> <span onClick={this.handleVisibility}>
<i className="material-icons clickable light-gray"> <i className="material-icons clickable light-gray">
{this.state.visible ? 'visibility' : 'visibility_off'} {this.state.visible ? 'visibility' : 'visibility_off'}
@@ -1531,14 +1578,20 @@ class PasswordResetView extends React.PureComponent {
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
this.handleEmailChange = this.handleEmailChange.bind(this); this.handleEmailChange = this.handleEmailChange.bind(this);
this.handlePasswordChange = this.handlePasswordChange.bind(this);
}
componentDidMount() {
let parsed = parseUrlHash(window.location.hash);
this.setState({token: parsed.params.token, scheme: parsed.params.scheme});
} }
handleSubmit(e) { handleSubmit(e) {
e.preventDefault(); e.preventDefault();
if (this.props.token) { if (this.state.token) {
this.props.onSubmit(null, this.state.password.trim()); this.props.onReset(this.state.scheme, this.state.password.trim(), this.state.token);
} else { } else {
this.props.onSubmit("email", this.state.email.trim()); this.props.onRequest("email", this.state.email.trim());
} }
} }
@@ -1551,10 +1604,17 @@ class PasswordResetView extends React.PureComponent {
} }
render() { render() {
let buttonText = this.props.token ? "Reset" : "Send request"; let reset = (this.state.token && this.state.scheme);
return ( return (
<form id="password-reset-form" onSubmit={this.handleSubmit}> <form id="password-reset-form" onSubmit={this.handleSubmit}>
{!this.props.token ? {reset ?
<VisiblePassword
placeholder="Enter new password"
autoComplete="new-password"
value={this.state.password}
required={true} autoFocus={true}
onChange={this.handlePasswordChange} />
:
<React.Fragment> <React.Fragment>
<label htmlFor="inputEmail">Send a password reset email:</label> <label htmlFor="inputEmail">Send a password reset email:</label>
<input type="email" id="inputEmail" <input type="email" id="inputEmail"
@@ -1564,16 +1624,9 @@ class PasswordResetView extends React.PureComponent {
onChange={this.handleEmailChange} onChange={this.handleEmailChange}
required autoFocus /> required autoFocus />
</React.Fragment> </React.Fragment>
:
<VisiblePassword
placeholder="Enter new password"
autoComplete="new-password"
value={this.state.password}
required={true} autoFocus={true}
onChange={this.handlePasswordChange} />
} }
<div className="dialog-buttons"> <div className="dialog-buttons">
<button className="blue" type="submit">{buttonText}</button> <button className="blue" type="submit">{reset ? "Reset" : "Send request"}</button>
</div> </div>
</form> </form>
); );
@@ -1832,7 +1885,8 @@ class SidepanelView extends React.Component {
view === 'reset' ? view === 'reset' ?
<PasswordResetView <PasswordResetView
onSubmit={this.props.onPasswordReset} /> : onRequest={this.props.onPasswordResetRequest}
onReset={this.props.onResetPassword} /> :
null} null}
</div> </div>
); );
@@ -1890,8 +1944,8 @@ class EditAccountView extends React.Component {
this.state = { this.state = {
fullName: me.public ? me.public.fn : undefined, fullName: me.public ? me.public.fn : undefined,
avatar: makeImageUrl(me.public ? me.public.photo : null), avatar: makeImageUrl(me.public ? me.public.photo : null),
auth: defacs.auth, auth: defacs ? defacs.auth : null,
anon: defacs.anon, anon: defacs ? defacs.anon : null,
tags: fnd.tags(), tags: fnd.tags(),
previousOnTags: fnd.onTagsUpdated previousOnTags: fnd.onTagsUpdated
}; };
@@ -4474,6 +4528,7 @@ class TinodeWeb extends React.Component {
this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this); this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);
this.handleValidateCredentialsRequest = this.handleValidateCredentialsRequest.bind(this); this.handleValidateCredentialsRequest = this.handleValidateCredentialsRequest.bind(this);
this.handlePasswordResetRequest = this.handlePasswordResetRequest.bind(this); this.handlePasswordResetRequest = this.handlePasswordResetRequest.bind(this);
this.handleResetPassword = this.handleResetPassword.bind(this);
} }
getBlankState() { getBlankState() {
@@ -4571,11 +4626,11 @@ class TinodeWeb extends React.Component {
this.tinode.enableLogging(true, true); this.tinode.enableLogging(true, true);
this.tinode.onConnect = this.handleConnected; this.tinode.onConnect = this.handleConnected;
this.tinode.onDisconnect = this.handleDisconnect; this.tinode.onDisconnect = this.handleDisconnect;
let token, firebasePushToken;
if (localStorage.getObject("keep-logged-in")) {
token = localStorage.getObject("auth-token");
}
let token = localStorage.getObject("keep-logged-in") ?
localStorage.getObject("auth-token") : undefined;
let parsedNav = parseUrlHash(window.location.hash);
if (token) { if (token) {
// When reading from storage, date is returned as string. // When reading from storage, date is returned as string.
token.expires = new Date(token.expires); token.expires = new Date(token.expires);
@@ -4584,12 +4639,11 @@ class TinodeWeb extends React.Component {
// Socket error // Socket error
this.handleError(err.message, "err"); this.handleError(err.message, "err");
}); });
var parsed = parseUrlHash(window.location.hash); delete parsedNav.params.info;
delete parsed.params.info; delete parsedNav.params.tab;
delete parsed.params.tab; parsedNav.path[0] = '';
parsed.path[0] = ''; navigateTo(composeUrlHash(parsedNav.path, parsedNav.params));
navigateTo(composeUrlHash(parsed.path, parsed.params)); } else if (!parsedNav.params.token) {
} else {
navigateTo(""); navigateTo("");
} }
@@ -5365,7 +5419,7 @@ class TinodeWeb extends React.Component {
// If already connected, connnect() will return a resolved promise. // If already connected, connnect() will return a resolved promise.
this.tinode.connect() this.tinode.connect()
.then(() => { .then(() => {
this.tinode.requestResetAuthSecret("basic", method, value) return this.tinode.requestResetAuthSecret("basic", method, value)
}) })
.catch((err) => { .catch((err) => {
// Socket error // Socket error
@@ -5373,6 +5427,22 @@ class TinodeWeb extends React.Component {
}); });
} }
handleResetPassword(scheme, newPassword, token) {
token = base64ReEncode(token);
if (!token) {
this.handleError("Invalid security token", "err");
} else {
this.tinode.connect()
.then(() => {
return this.tinode.updateAccountBasic(null, null, newPassword, {token: token});
})
.catch((err) => {
// Socket error
this.handleError(err.message, "err");
});
}
}
render() { render() {
return ( return (
<div id="app-container"> <div id="app-container">
@@ -5430,7 +5500,8 @@ class TinodeWeb extends React.Component {
onCancel={this.handleSidepanelCancel} onCancel={this.handleSidepanelCancel}
onError={this.handleError} onError={this.handleError}
onValidateCredentials={this.handleValidateCredentialsRequest} onValidateCredentials={this.handleValidateCredentialsRequest}
onPasswordReset={this.handlePasswordResetRequest} onPasswordResetRequest={this.handlePasswordResetRequest}
onResetPassword={this.handleResetPassword}
onInitFind={this.tnInitFind} onInitFind={this.tnInitFind}
searchResults={this.state.searchResults} searchResults={this.state.searchResults}