mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 06:39:48 +00:00
Add ability to change password (#1532)
* Got password reset form mostly done * Hooked up the rest of the password-change code. * Added a completion state * Fix weird comment that got updated by accident
This commit is contained in:
parent
13292448c9
commit
68ae6934cb
@ -31,7 +31,7 @@ export async function login(rawEmail, rawPassphrase) {
|
||||
// Fetch Salt and Submit A To Server //
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
|
||||
|
||||
const { saltKey, saltAuth } = await fetch.post('/auth/login-s', { email }, null);
|
||||
const { saltKey, saltAuth } = await _getAuthSalts(email);
|
||||
const authSecret = await crypt.deriveKey(passphrase, email, saltKey);
|
||||
const secret1 = await crypt.srpGenKey();
|
||||
const c = new srp.Client(
|
||||
@ -108,6 +108,49 @@ export async function login(rawEmail, rawPassphrase) {
|
||||
_callCallbacks();
|
||||
}
|
||||
|
||||
export async function changePasswordWithToken(rawNewPassphrase, confirmationCode) {
|
||||
// Sanitize inputs
|
||||
const newPassphrase = _sanitizePassphrase(rawNewPassphrase);
|
||||
const newEmail = getEmail(); // Use the same one
|
||||
|
||||
// Fetch some things
|
||||
const { saltEnc, encSymmetricKey } = await _whoami();
|
||||
const { saltKey, saltAuth } = await _getAuthSalts(newEmail);
|
||||
|
||||
// Generate some secrets for the user base'd on password
|
||||
const newSecret = await crypt.deriveKey(newPassphrase, newEmail, saltEnc);
|
||||
const newAuthSecret = await crypt.deriveKey(newPassphrase, newEmail, saltKey);
|
||||
|
||||
const newVerifier = srp
|
||||
.computeVerifier(
|
||||
_getSrpParams(),
|
||||
Buffer.from(saltAuth, 'hex'),
|
||||
Buffer.from(newEmail, 'utf8'),
|
||||
Buffer.from(newAuthSecret, 'hex'),
|
||||
)
|
||||
.toString('hex');
|
||||
|
||||
// Re-encrypt existing keys with new secret
|
||||
const newEncSymmetricKeyJSON = crypt.encryptAES(newSecret, _getSymmetricKey());
|
||||
const newEncSymmetricKey = JSON.stringify(newEncSymmetricKeyJSON);
|
||||
|
||||
return fetch.post(
|
||||
`/auth/change-password`,
|
||||
{
|
||||
code: confirmationCode,
|
||||
newEmail: newEmail,
|
||||
encSymmetricKey: encSymmetricKey,
|
||||
newVerifier,
|
||||
newEncSymmetricKey,
|
||||
},
|
||||
getCurrentSessionId(),
|
||||
);
|
||||
}
|
||||
|
||||
export function sendPasswordChangeCode() {
|
||||
return fetch.post('/auth/send-password-code', null, getCurrentSessionId());
|
||||
}
|
||||
|
||||
export function getPublicKey() {
|
||||
return _getSessionData().publicKey;
|
||||
}
|
||||
@ -138,6 +181,14 @@ export function getFirstName() {
|
||||
return _getSessionData().firstName;
|
||||
}
|
||||
|
||||
export function getLastName() {
|
||||
return _getSessionData().firstName;
|
||||
}
|
||||
|
||||
export function getFullName() {
|
||||
return `${getFirstName()} ${getLastName()}`.trim();
|
||||
}
|
||||
|
||||
/** Check if we (think) we have a session */
|
||||
export function isLoggedIn() {
|
||||
return !!getCurrentSessionId();
|
||||
@ -197,10 +248,19 @@ export async function endTrial() {
|
||||
// Helper Functions //
|
||||
// ~~~~~~~~~~~~~~~~ //
|
||||
|
||||
function _getSymmetricKey() {
|
||||
const sessionData = _getSessionData();
|
||||
return sessionData.symmetricKey;
|
||||
}
|
||||
|
||||
function _whoami(sessionId = null) {
|
||||
return fetch.get('/auth/whoami', sessionId || getCurrentSessionId());
|
||||
}
|
||||
|
||||
function _getAuthSalts(email) {
|
||||
return fetch.post('/auth/login-s', { email }, getCurrentSessionId());
|
||||
}
|
||||
|
||||
function _getSessionData() {
|
||||
const sessionId = getCurrentSessionId();
|
||||
if (!sessionId || !window) {
|
||||
|
@ -10,6 +10,7 @@ type Props = {|
|
||||
onClick?: Function,
|
||||
className?: string,
|
||||
children?: React.Node,
|
||||
disabled?: boolean,
|
||||
|};
|
||||
|
||||
@autobind
|
||||
@ -31,6 +32,7 @@ class Link extends React.PureComponent<Props> {
|
||||
href,
|
||||
children,
|
||||
className,
|
||||
disabled,
|
||||
...other
|
||||
} = this.props;
|
||||
return button ? (
|
||||
@ -42,6 +44,7 @@ class Link extends React.PureComponent<Props> {
|
||||
href={href}
|
||||
onClick={this._handleClick}
|
||||
className={(className || '') + ' theme--link'}
|
||||
disabled={disabled}
|
||||
{...other}>
|
||||
{children}
|
||||
</a>
|
||||
|
@ -82,7 +82,7 @@ class SettingsModal extends PureComponent {
|
||||
render() {
|
||||
const { settings } = this.props;
|
||||
const { currentTabIndex } = this.state;
|
||||
const email = session.isLoggedIn() ? session.getEmail() : null;
|
||||
const email = session.isLoggedIn() ? session.getFullName() : null;
|
||||
|
||||
return (
|
||||
<Modal ref={this._setModalRef} tall freshState {...this.props}>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import autobind from 'autobind-decorator';
|
||||
import * as sync from '../../../sync-legacy/index';
|
||||
import Link from '../base/link';
|
||||
@ -6,23 +7,109 @@ import LoginModal from '../modals/login-modal';
|
||||
import { hideAllModals, showModal } from '../modals/index';
|
||||
import PromptButton from '../base/prompt-button';
|
||||
import * as session from '../../../account/session';
|
||||
import HelpTooltip from '../help-tooltip';
|
||||
|
||||
type Props = {};
|
||||
|
||||
type State = {
|
||||
code: string,
|
||||
password: string,
|
||||
password2: string,
|
||||
showChangePassword: boolean,
|
||||
codeSent: boolean,
|
||||
error: string,
|
||||
finishedResetting: boolean,
|
||||
};
|
||||
|
||||
@autobind
|
||||
class Account extends PureComponent {
|
||||
class Account extends React.PureComponent<Props, State> {
|
||||
state = {
|
||||
code: '',
|
||||
password: '',
|
||||
password2: '',
|
||||
codeSent: false,
|
||||
showChangePassword: false,
|
||||
error: '',
|
||||
finishedResetting: false,
|
||||
};
|
||||
|
||||
async _handleShowChangePasswordForm(e: SyntheticEvent<HTMLInputElement>) {
|
||||
this.setState(state => ({
|
||||
showChangePassword: !state.showChangePassword,
|
||||
finishedResetting: false,
|
||||
}));
|
||||
}
|
||||
|
||||
_handleChangeCode(e: SyntheticEvent<HTMLInputElement>) {
|
||||
this.setState({ code: e.currentTarget.value });
|
||||
}
|
||||
|
||||
_handleChangePassword(e: SyntheticEvent<HTMLInputElement>) {
|
||||
this.setState({ password: e.currentTarget.value });
|
||||
}
|
||||
|
||||
_handleChangePassword2(e: SyntheticEvent<HTMLInputElement>) {
|
||||
this.setState({ password2: e.currentTarget.value });
|
||||
}
|
||||
|
||||
async _handleSubmitPasswordChange(e: SyntheticEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
this.setState({ error: '' });
|
||||
|
||||
const { password, password2, code } = this.state;
|
||||
|
||||
let error = '';
|
||||
if (password !== password2) {
|
||||
error = 'Passwords did not match';
|
||||
} else if (!code) {
|
||||
error = 'Code was not provided';
|
||||
}
|
||||
|
||||
if (error) {
|
||||
this.setState({ error });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await session.changePasswordWithToken(password, code);
|
||||
} catch (err) {
|
||||
this.setState({ error: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ error: '', finishedResetting: true, showChangePassword: false });
|
||||
}
|
||||
|
||||
async _handleLogout() {
|
||||
await sync.logout();
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
_handleLogin(e) {
|
||||
static _handleLogin(e: SyntheticEvent<HTMLButtonElement>) {
|
||||
e.preventDefault();
|
||||
hideAllModals();
|
||||
showModal(LoginModal);
|
||||
}
|
||||
|
||||
renderUpgrade() {
|
||||
async _sendCode() {
|
||||
try {
|
||||
await session.sendPasswordChangeCode();
|
||||
} catch (err) {
|
||||
this.setState({ error: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ codeSent: true });
|
||||
}
|
||||
|
||||
async _handleSendCode(e: SyntheticEvent<HTMLAnchorElement>) {
|
||||
e.preventDefault();
|
||||
await this._sendCode();
|
||||
}
|
||||
|
||||
static renderUpgrade() {
|
||||
return (
|
||||
<div>
|
||||
<React.Fragment>
|
||||
<div className="notice pad surprise">
|
||||
<h1 className="no-margin-top">Try Insomnia Plus!</h1>
|
||||
<p>
|
||||
@ -48,34 +135,111 @@ class Account extends PureComponent {
|
||||
</div>
|
||||
<p>
|
||||
Or{' '}
|
||||
<a href="#" onClick={this._handleLogin} className="theme--link">
|
||||
<a href="#" onClick={Account._handleLogin} className="theme--link">
|
||||
Login
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderAccount() {
|
||||
const {
|
||||
code,
|
||||
password,
|
||||
password2,
|
||||
codeSent,
|
||||
showChangePassword,
|
||||
error,
|
||||
finishedResetting,
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="no-margin-top">Welcome {session.getFirstName()}!</h2>
|
||||
<p>
|
||||
You are currently logged in as <code className="code--compact">{session.getEmail()}</code>
|
||||
</p>
|
||||
<br />
|
||||
<Link button href="https://insomnia.rest/app/" className="btn btn--clicky">
|
||||
Manage Account
|
||||
</Link>
|
||||
<PromptButton className="margin-left-sm btn btn--clicky" onClick={this._handleLogout}>
|
||||
Sign Out
|
||||
</PromptButton>
|
||||
</div>
|
||||
<React.Fragment>
|
||||
<div>
|
||||
<h2 className="no-margin-top">Welcome {session.getFirstName()}!</h2>
|
||||
<p>
|
||||
You are currently logged in as{' '}
|
||||
<code className="code--compact">{session.getEmail()}</code>
|
||||
</p>
|
||||
<br />
|
||||
<Link button href="https://insomnia.rest/app/" className="btn btn--clicky">
|
||||
Manage Account
|
||||
</Link>
|
||||
<PromptButton className="space-left btn btn--clicky" onClick={this._handleLogout}>
|
||||
Sign Out
|
||||
</PromptButton>
|
||||
<button
|
||||
className="space-left btn btn--clicky"
|
||||
onClick={this._handleShowChangePasswordForm}>
|
||||
Change Password
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{finishedResetting && (
|
||||
<p className="notice surprise">Your password was changed successfully</p>
|
||||
)}
|
||||
|
||||
{showChangePassword && (
|
||||
<form onSubmit={this._handleSubmitPasswordChange} className="pad-top">
|
||||
<hr />
|
||||
{error && <p className="notice error">{error}</p>}
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>
|
||||
New Password
|
||||
<input
|
||||
type="password"
|
||||
placeholder="•••••••••••••••••"
|
||||
onChange={this._handleChangePassword}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>
|
||||
Confirm Password
|
||||
<input
|
||||
type="password"
|
||||
placeholder="•••••••••••••••••"
|
||||
onChange={this._handleChangePassword2}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>
|
||||
Confirmation Code{' '}
|
||||
<HelpTooltip>A confirmation code has been sent to your email address</HelpTooltip>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={code}
|
||||
placeholder="aa8b0d1ea9"
|
||||
onChange={this._handleChangeCode}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="row-spaced row--top">
|
||||
<div>
|
||||
{codeSent ? 'A code was sent to your email' : 'Looking for a code?'}{' '}
|
||||
<Link href="#" onClick={this._handleSendCode}>
|
||||
Email Me a Code
|
||||
</Link>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn--clicky"
|
||||
disabled={!code || !password || password !== password2}>
|
||||
Submit Change
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return session.isLoggedIn() ? this.renderAccount() : this.renderUpgrade();
|
||||
return session.isLoggedIn() ? this.renderAccount() : Account.renderUpgrade();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -410,6 +410,10 @@ blockquote {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.row--top {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.valign-middle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user