password change
This commit is contained in:
parent
0507381e20
commit
f0166a1779
@ -1,9 +1,11 @@
|
|||||||
const { connect } = require('preact-redux');
|
|
||||||
const preact = require('preact');
|
const preact = require('preact');
|
||||||
|
const { Component } = require('preact')
|
||||||
|
const { connect } = require('preact-redux');
|
||||||
|
const linkState = require('linkstate').default;
|
||||||
|
|
||||||
const SpawnButton = require('./spawn.button');
|
const SpawnButton = require('./spawn.button');
|
||||||
|
|
||||||
const { postData } = require('./../utils');
|
const { postData, errorToast, infoToast } = require('../utils');
|
||||||
const actions = require('../actions');
|
const actions = require('../actions');
|
||||||
|
|
||||||
const addState = connect(
|
const addState = connect(
|
||||||
@ -15,10 +17,22 @@ const addState = connect(
|
|||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
|
|
||||||
|
function setPassword(current, password) {
|
||||||
|
postData('/account/password', { current, password })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) return errorToast(data.error_message);
|
||||||
|
infoToast('Password changed. Reloading...')
|
||||||
|
setTimeout(() => window.location.reload(), 5000);
|
||||||
|
})
|
||||||
|
.catch(error => errorToast(error));
|
||||||
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
postData('/account/logout').then(() => window.location.reload(true));
|
postData('/account/logout').then(() => window.location.reload(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function sendConstructSpawn(name) {
|
function sendConstructSpawn(name) {
|
||||||
return ws.sendMtxConstructSpawn(name);
|
return ws.sendMtxConstructSpawn(name);
|
||||||
}
|
}
|
||||||
@ -27,22 +41,41 @@ const addState = connect(
|
|||||||
account,
|
account,
|
||||||
ping,
|
ping,
|
||||||
logout,
|
logout,
|
||||||
|
setPassword,
|
||||||
sendConstructSpawn,
|
sendConstructSpawn,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
function AccountStatus(args) {
|
class AccountStatus extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
setPassword: { current: '', password: '', confirm: ''},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(args) {
|
||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
ping,
|
ping,
|
||||||
logout,
|
logout,
|
||||||
|
setPassword,
|
||||||
sendConstructSpawn,
|
sendConstructSpawn,
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
if (!account) return null;
|
if (!account) return null;
|
||||||
|
|
||||||
|
const passwordsEqual = () =>
|
||||||
|
this.state.setPassword.password === this.state.setPassword.confirm;
|
||||||
|
|
||||||
|
const setPasswordDisabled = () => {
|
||||||
|
const { current, password, confirm } = this.state.setPassword;
|
||||||
|
return !(passwordsEqual() && password && current && confirm);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section class='account'>
|
<section class='account'>
|
||||||
<div>
|
<div>
|
||||||
@ -53,7 +86,7 @@ function AccountStatus(args) {
|
|||||||
</dl>
|
</dl>
|
||||||
<button onClick={() => logout()}>Logout</button>
|
<button onClick={() => logout()}>Logout</button>
|
||||||
</div>
|
</div>
|
||||||
<form>
|
<div>
|
||||||
<label for="email">Update Email:</label>
|
<label for="email">Update Email:</label>
|
||||||
<input
|
<input
|
||||||
class="login-input"
|
class="login-input"
|
||||||
@ -69,29 +102,39 @@ function AccountStatus(args) {
|
|||||||
placeholder="new email"
|
placeholder="new email"
|
||||||
/>
|
/>
|
||||||
<button>Update</button>
|
<button>Update</button>
|
||||||
</form>
|
</div>
|
||||||
<form>
|
<div>
|
||||||
<label for="current">Change Password:</label>
|
<label for="current">Change Password:</label>
|
||||||
<input
|
<input
|
||||||
class="login-input"
|
class="login-input"
|
||||||
type="password"
|
type="password"
|
||||||
name="current"
|
name="current"
|
||||||
|
value={this.state.setPassword.current}
|
||||||
|
onInput={linkState(this, 'setPassword.current')}
|
||||||
placeholder="current"
|
placeholder="current"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
class="login-input"
|
class="login-input"
|
||||||
type="password"
|
type="password"
|
||||||
name="new"
|
name="new"
|
||||||
|
value={this.state.setPassword.password}
|
||||||
|
onInput={linkState(this, 'setPassword.password')}
|
||||||
placeholder="new password"
|
placeholder="new password"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
class="login-input"
|
class="login-input"
|
||||||
type="password"
|
type="password"
|
||||||
name="confirm"
|
name="confirm"
|
||||||
|
value={this.state.setPassword.confirm}
|
||||||
|
onInput={linkState(this, 'setPassword.confirm')}
|
||||||
placeholder="confirm"
|
placeholder="confirm"
|
||||||
/>
|
/>
|
||||||
<button>Change</button>
|
<button
|
||||||
</form>
|
disabled={setPasswordDisabled()}
|
||||||
|
onClick={() => setPassword(this.state.setPassword.current, this.state.setPassword.password)}>
|
||||||
|
Set Password
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="list">
|
<div class="list">
|
||||||
<figure>
|
<figure>
|
||||||
<figcaption>spawn new construct</figcaption>
|
<figcaption>spawn new construct</figcaption>
|
||||||
@ -102,6 +145,7 @@ function AccountStatus(args) {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = addState(AccountStatus);
|
module.exports = addState(AccountStatus);
|
||||||
|
|||||||
@ -213,6 +213,15 @@ function errorToast(message) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function infoToast(message) {
|
||||||
|
toast.info({
|
||||||
|
position: 'topRight',
|
||||||
|
drag: false,
|
||||||
|
close: false,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function convertItem(v) {
|
function convertItem(v) {
|
||||||
if (['Red', 'Green', 'Blue'].includes(v)) {
|
if (['Red', 'Green', 'Blue'].includes(v)) {
|
||||||
return (
|
return (
|
||||||
@ -233,6 +242,7 @@ module.exports = {
|
|||||||
postData,
|
postData,
|
||||||
convertItem,
|
convertItem,
|
||||||
errorToast,
|
errorToast,
|
||||||
|
infoToast,
|
||||||
NULL_UUID,
|
NULL_UUID,
|
||||||
STATS,
|
STATS,
|
||||||
COLOURS,
|
COLOURS,
|
||||||
|
|||||||
@ -117,7 +117,7 @@ pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result<A
|
|||||||
Ok(Account { id, name, balance, subscribed })
|
Ok(Account { id, name, balance, subscribed })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_token(tx: &mut Transaction, id: Uuid) -> Result<String, Error> {
|
pub fn new_token(tx: &mut Transaction, id: Uuid) -> Result<String, MnmlHttpError> {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let token: String = iter::repeat(())
|
let token: String = iter::repeat(())
|
||||||
.map(|()| rng.sample(Alphanumeric))
|
.map(|()| rng.sample(Alphanumeric))
|
||||||
@ -136,11 +136,73 @@ pub fn new_token(tx: &mut Transaction, id: Uuid) -> Result<String, Error> {
|
|||||||
.query(query, &[&token, &id])?;
|
.query(query, &[&token, &id])?;
|
||||||
|
|
||||||
result.iter().next()
|
result.iter().next()
|
||||||
.ok_or(format_err!("account not updated {:?}", id))?;
|
.ok_or(MnmlHttpError::Unauthorized)?;
|
||||||
|
|
||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_password(tx: &mut Transaction, id: Uuid, current: &String, password: &String) -> Result<String, MnmlHttpError> {
|
||||||
|
if password.len() < PASSWORD_MIN_LEN {
|
||||||
|
return Err(MnmlHttpError::PasswordUnacceptable);
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = "
|
||||||
|
SELECT id, password
|
||||||
|
FROM accounts
|
||||||
|
WHERE id = $1
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&id])?;
|
||||||
|
|
||||||
|
let row = match result.iter().next() {
|
||||||
|
Some(row) => row,
|
||||||
|
None => {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let garbage: String = iter::repeat(())
|
||||||
|
.map(|()| rng.sample(Alphanumeric))
|
||||||
|
.take(64)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// verify garbage to prevent timing attacks
|
||||||
|
verify(garbage.clone(), &garbage).ok();
|
||||||
|
return Err(MnmlHttpError::AccountNotFound);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let id: Uuid = row.get(0);
|
||||||
|
let db_pw: String = row.get(1);
|
||||||
|
|
||||||
|
if !verify(current, &db_pw)? {
|
||||||
|
return Err(MnmlHttpError::PasswordNotMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rounds = 8;
|
||||||
|
let password = hash(&password, rounds)?;
|
||||||
|
|
||||||
|
let query = "
|
||||||
|
UPDATE accounts
|
||||||
|
SET password = $1, updated_at = now()
|
||||||
|
WHERE id = $2
|
||||||
|
RETURNING id, name;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&password, &id])?;
|
||||||
|
|
||||||
|
let row = match result.iter().next() {
|
||||||
|
Some(row) => row,
|
||||||
|
None => return Err(MnmlHttpError::DbError),
|
||||||
|
};
|
||||||
|
|
||||||
|
let name: String = row.get(1);
|
||||||
|
|
||||||
|
info!("password updated name={:?} id={:?}", name, id);
|
||||||
|
|
||||||
|
new_token(tx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn credit(tx: &mut Transaction, id: Uuid, credits: i64) -> Result<String, Error> {
|
pub fn credit(tx: &mut Transaction, id: Uuid, credits: i64) -> Result<String, Error> {
|
||||||
let query = "
|
let query = "
|
||||||
UPDATE accounts
|
UPDATE accounts
|
||||||
|
|||||||
@ -221,7 +221,7 @@ fn login(req: &mut Request) -> IronResult<Response> {
|
|||||||
|
|
||||||
match account::login(&mut tx, ¶ms.name, ¶ms.password) {
|
match account::login(&mut tx, ¶ms.name, ¶ms.password) {
|
||||||
Ok(a) => {
|
Ok(a) => {
|
||||||
let token = account::new_token(&mut tx, a.id).or(Err(MnmlHttpError::ServerError))?;
|
let token = account::new_token(&mut tx, a.id)?;
|
||||||
tx.commit().or(Err(MnmlHttpError::ServerError))?;
|
tx.commit().or(Err(MnmlHttpError::ServerError))?;
|
||||||
Ok(token_res(token))
|
Ok(token_res(token))
|
||||||
},
|
},
|
||||||
@ -239,7 +239,7 @@ fn logout(req: &mut Request) -> IronResult<Response> {
|
|||||||
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
|
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
|
||||||
let mut tx = db.transaction().or(Err(MnmlHttpError::DbError))?;
|
let mut tx = db.transaction().or(Err(MnmlHttpError::DbError))?;
|
||||||
|
|
||||||
account::new_token(&mut tx, a.id).or(Err(MnmlHttpError::Unauthorized))?;
|
account::new_token(&mut tx, a.id)?;
|
||||||
|
|
||||||
tx.commit().or(Err(MnmlHttpError::ServerError))?;
|
tx.commit().or(Err(MnmlHttpError::ServerError))?;
|
||||||
|
|
||||||
@ -252,6 +252,33 @@ fn logout(req: &mut Request) -> IronResult<Response> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Clone,Deserialize)]
|
||||||
|
struct SetPassword {
|
||||||
|
current: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_password(req: &mut Request) -> IronResult<Response> {
|
||||||
|
let state = req.get::<Read<State>>().unwrap();
|
||||||
|
let params = match req.get::<bodyparser::Struct<SetPassword>>() {
|
||||||
|
Ok(Some(b)) => b,
|
||||||
|
_ => return Err(IronError::from(MnmlHttpError::BadRequest)),
|
||||||
|
};
|
||||||
|
|
||||||
|
match req.extensions.get::<account::Account>() {
|
||||||
|
Some(a) => {
|
||||||
|
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
|
||||||
|
let mut tx = db.transaction().or(Err(MnmlHttpError::DbError))?;
|
||||||
|
|
||||||
|
let token = account::set_password(&mut tx, a.id, ¶ms.current, ¶ms.password)?;
|
||||||
|
|
||||||
|
tx.commit().or(Err(MnmlHttpError::ServerError))?;
|
||||||
|
|
||||||
|
Ok(token_res(token))
|
||||||
|
},
|
||||||
|
None => Err(IronError::from(MnmlHttpError::Unauthorized)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const MAX_BODY_LENGTH: usize = 1024 * 1024 * 10;
|
const MAX_BODY_LENGTH: usize = 1024 * 1024 * 10;
|
||||||
|
|
||||||
@ -269,6 +296,8 @@ pub fn start(pool: PgPool) {
|
|||||||
router.post("/api/account/login", login, "login");
|
router.post("/api/account/login", login, "login");
|
||||||
router.post("/api/account/logout", logout, "logout");
|
router.post("/api/account/logout", logout, "logout");
|
||||||
router.post("/api/account/register", register, "register");
|
router.post("/api/account/register", register, "register");
|
||||||
|
router.post("/api/account/password", set_password, "set_password");
|
||||||
|
router.post("/api/account/email", logout, "email");
|
||||||
|
|
||||||
// payments
|
// payments
|
||||||
router.post("/api/payments/stripe", stripe, "stripe");
|
router.post("/api/payments/stripe", stripe, "stripe");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user