mirror of https://github.com/calzoneman/sync.git
Work on knexifying password resets
This commit is contained in:
parent
791a712a68
commit
ae7098085c
|
@ -0,0 +1,143 @@
|
|||
const assert = require('assert');
|
||||
const PasswordResetDB = require('../../lib/db/password-reset').PasswordResetDB;
|
||||
const testDB = require('../testutil/db').testDB;
|
||||
|
||||
const passwordResetDB = new PasswordResetDB(testDB);
|
||||
|
||||
function cleanup() {
|
||||
return testDB.knex.table('password_reset').del();
|
||||
}
|
||||
|
||||
describe('PasswordResetDB', () => {
|
||||
describe('#insert', () => {
|
||||
beforeEach(cleanup);
|
||||
|
||||
const params = {
|
||||
ip: '1.2.3.4',
|
||||
name: 'testing',
|
||||
email: 'test@example.com',
|
||||
hash: 'abcdef',
|
||||
expire: 5678
|
||||
};
|
||||
|
||||
it('adds a new password reset', () => {
|
||||
return passwordResetDB.insert(params).then(() => {
|
||||
return testDB.knex.table('password_reset')
|
||||
.where({ name: 'testing' })
|
||||
.select();
|
||||
}).then(rows => {
|
||||
assert.strictEqual(rows.length, 1);
|
||||
assert.deepStrictEqual(rows[0], params);
|
||||
});
|
||||
});
|
||||
|
||||
it('overwrites an existing reset for the same name', () => {
|
||||
return passwordResetDB.insert(params).then(() => {
|
||||
params.ip = '5.6.7.8';
|
||||
params.email = 'somethingelse@example.com';
|
||||
params.hash = 'qwertyuiop';
|
||||
params.expire = 9999;
|
||||
|
||||
return passwordResetDB.insert(params);
|
||||
}).then(() => {
|
||||
return testDB.knex.table('password_reset')
|
||||
.where({ name: 'testing' })
|
||||
.select();
|
||||
}).then(rows => {
|
||||
assert.strictEqual(rows.length, 1);
|
||||
assert.deepStrictEqual(rows[0], params);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#get', () => {
|
||||
const reset = {
|
||||
ip: '1.2.3.4',
|
||||
name: 'testing',
|
||||
email: 'test@example.com',
|
||||
hash: 'abcdef',
|
||||
expire: 5678
|
||||
};
|
||||
|
||||
beforeEach(() => cleanup().then(() => {
|
||||
return testDB.knex.table('password_reset').insert(reset);
|
||||
}));
|
||||
|
||||
it('gets a password reset by hash', () => {
|
||||
return passwordResetDB.get(reset.hash).then(result => {
|
||||
assert.deepStrictEqual(result, reset);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when no reset exists for the input', () => {
|
||||
return passwordResetDB.get('lalala').then(() => {
|
||||
assert.fail('Expected not found error');
|
||||
}).catch(error => {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
'No password reset found for hash lalala'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#delete', () => {
|
||||
const reset = {
|
||||
ip: '1.2.3.4',
|
||||
name: 'testing',
|
||||
email: 'test@example.com',
|
||||
hash: 'abcdef',
|
||||
expire: 5678
|
||||
};
|
||||
|
||||
beforeEach(() => cleanup().then(() => {
|
||||
return testDB.knex.table('password_reset').insert(reset);
|
||||
}));
|
||||
|
||||
it('deletes a password reset by hash', () => {
|
||||
return passwordResetDB.delete(reset.hash).then(() => {
|
||||
return testDB.knex.table('password_reset')
|
||||
.where({ name: 'testing' })
|
||||
.select();
|
||||
}).then(rows => {
|
||||
assert.strictEqual(rows.length, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#cleanup', () => {
|
||||
const now = Date.now();
|
||||
|
||||
const reset1 = {
|
||||
ip: '1.2.3.4',
|
||||
name: 'testing',
|
||||
email: 'test@example.com',
|
||||
hash: 'abcdef',
|
||||
expire: now - 25 * 60 * 60 * 1000
|
||||
};
|
||||
|
||||
const reset2 = {
|
||||
ip: '5.6.7.8',
|
||||
name: 'testing2',
|
||||
email: 'test@example.com',
|
||||
hash: 'abcdef',
|
||||
expire: now
|
||||
};
|
||||
|
||||
beforeEach(() => cleanup().then(() => {
|
||||
return testDB.knex.table('password_reset')
|
||||
.insert([reset1, reset2]);
|
||||
}));
|
||||
|
||||
it('cleans up old password resets', () => {
|
||||
return passwordResetDB.cleanup().then(() => {
|
||||
return testDB.knex.table('password_reset')
|
||||
.whereIn('name', ['testing1', 'testing2'])
|
||||
.select();
|
||||
}).then(rows => {
|
||||
assert.strictEqual(rows.length, 1);
|
||||
assert.deepStrictEqual(rows[0], reset2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -484,22 +484,6 @@ module.exports = {
|
|||
});
|
||||
},
|
||||
|
||||
generatePasswordReset: function (ip, name, email, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
callback("generatePasswordReset is not implemented", null);
|
||||
},
|
||||
|
||||
recoverPassword: function (hash, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
callback("recoverPassword is not implemented", null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve a list of channels owned by a user
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import { createMySQLDuplicateKeyUpdate } from '../util/on-duplicate-key-update';
|
||||
|
||||
const ONE_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
class PasswordResetDB {
|
||||
constructor(db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
insert(params) {
|
||||
// TODO: validate params?
|
||||
return this.db.runTransaction(tx => {
|
||||
const insert = tx.table('password_reset').insert(params);
|
||||
// TODO: Support other DBMS besides MySQL
|
||||
// Annoyingly, upsert/on duplicate key update are non-standard
|
||||
// Alternatively, maybe this table shouldn't be an upsert table?
|
||||
const update = tx.raw(createMySQLDuplicateKeyUpdate(
|
||||
['ip', 'hash', 'email', 'expire']
|
||||
));
|
||||
|
||||
return tx.raw(insert.toString() + update.toString());
|
||||
});
|
||||
}
|
||||
|
||||
get(hash) {
|
||||
return this.db.runTransaction(tx => {
|
||||
return tx.table('password_reset').where({ hash }).select()
|
||||
.then(rows => {
|
||||
if (rows.length === 0) {
|
||||
throw new Error(`No password reset found for hash ${hash}`);
|
||||
}
|
||||
|
||||
return rows[0];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
delete(hash) {
|
||||
return this.db.runTransaction(tx => {
|
||||
return tx.table('password_reset').where({ hash }).del();
|
||||
});
|
||||
}
|
||||
|
||||
cleanup(threshold = ONE_DAY) {
|
||||
return this.db.runTransaction(tx => {
|
||||
return tx.table('password_reset')
|
||||
.where('expire', '<', Date.now() - ONE_DAY)
|
||||
.del();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { PasswordResetDB };
|
|
@ -0,0 +1,7 @@
|
|||
export function createMySQLDuplicateKeyUpdate(columns) {
|
||||
const prefix = ' on duplicate key update ';
|
||||
const updates = columns.map(col => `\`${col}\` = values(\`${col}\`)`)
|
||||
.join(', ');
|
||||
|
||||
return prefix + updates;
|
||||
}
|
Loading…
Reference in New Issue