Browse Source

Merge branch 'bookshelf-removal' of zedshaw/learnjsthehardway into master

master
Zed A. Shaw 2 months ago
parent
commit
931eabcaad
15 changed files with 395 additions and 406 deletions
  1. +11
    -10
      __tests__/models/auth.spec.js
  2. +13
    -20
      __tests__/models/user.spec.js
  3. +8
    -9
      __tests__/models/user_auth.spec.js
  4. +53
    -46
      __tests__/models/user_episode_state.spec.js
  5. +10
    -10
      __tests__/models/user_exercise_state.spec.js
  6. +46
    -47
      __tests__/models/user_module_state.spec.js
  7. +1
    -1
      __tests__/ui/login.spec.js
  8. +2
    -2
      __tests__/ui/modules.spec.js
  9. +6
    -6
      __tests__/ui/register.spec.js
  10. +4
    -1
      __tests__/utils.js
  11. +185
    -190
      lib/models.js
  12. +44
    -44
      scripts/services/auth.js
  13. +3
    -10
      src/routes/auth/verify.js
  14. +7
    -8
      src/routes/register/index.json.js
  15. +2
    -2
      src/routes/user/index.json.js

+ 11
- 10
__tests__/models/auth.spec.js View File

@@ -9,9 +9,10 @@ afterAll(() => {
it('Create auth works', async () => {
let user = t.fake_person();
let auth = await Auth.create(user.email, '127.0.0.1', false);
expect(auth.get('email')).toBe(user.email);
expect(auth.get('token')).toBeDefined();
expect(auth.get('registration')).toBe(false);

expect(auth.email).toBe(user.email);
expect(auth.token).toBeDefined();
expect(auth.registration).toBe(false);
auth.destroy();
});

@@ -20,12 +21,12 @@ it('Can find by email or token', async () => {
let user = t.fake_person();
let auth = await Auth.create(user.email, '127.0.0.1', true);
let found = await Auth.find_by_email(user.email);
expect(found.get("token")).toBe(auth.get("token"));
expect(found.get("email")).toBe(auth.get("email"));
expect(found.token).toBe(auth.token);
expect(found.email).toBe(auth.email);

let found2 = await Auth.find_by_token(auth.get("token"));
expect(found2.get("token")).toBe(found.get("token"));
expect(found2.get("email")).toBe(found.get("email"));
let found2 = await Auth.find_by_token(auth.token);
expect(found2.token).toBe(found.token);
expect(found2.email).toBe(found.email);

found2.destroy();
});
@@ -33,7 +34,7 @@ it('Can find by email or token', async () => {
it('Can destroy by email', async() => {
let user = t.fake_person();
let auth = await Auth.create(user.email, '127.0.0.1', true);
let del = await Auth.destroy_by_email(user.email, true);
let del = await Auth.destroy_by_email(user.email);
let found = await Auth.find_by_email(user.email);
expect(found).toBe(null);
expect(found).toBe(undefined);
});

+ 13
- 20
__tests__/models/user.spec.js View File

@@ -19,66 +19,59 @@ const make_user = async () => {

it('Can verify is_valid', async () => {
const faker = t.fake_person();
let user = await User.forge({
let user = await User.insert({
email: faker.email, full_name: faker.name,
send_emails: true, registered: true, verified: true,
tos_agree: true
}).save();
});

let found = await User.find_and_validate(faker.email);
expect(found.user).toBeDefined();
expect(found.user.get('email')).toBe(user.get('email'));
expect(found.user.email).toBe(user.email);
expect(found.is_valid).toBe(true);

user.destroy();
await user.destroy();
});

it('Can delete by an email', async () => {
const faker = t.fake_person();

let user = await User.forge({
let user = await User.insert({
email: faker.email, full_name: faker.name,
send_emails: true, registered: true, verified: true,
tos_agree: true
}).save();
})

let res = await User.delete_by_email(faker.email, false);
expect(res).toBeDefined();

let found = await User.find_and_validate(faker.email);
expect(found).toBeDefined();
expect(found.user).toBe(null);
expect(found.user).toBe(undefined);
expect(found.is_valid).toBe(false);
});

it('Can register a new user', async () => {
let {user, faker} = await make_user();

expect(user.get('email')).toBe(faker.email);
expect(user.email).toBe(faker.email);

let found = await User.find_and_validate(faker.email);
expect(found).toBeDefined();
expect(found.user).toBeDefined();
expect(found.user.get('email')).toBe(user.get('email'));
expect(found.user.email).toBe(user.email);

user.destroy();
});

it('Can update a user', async () => {
let {user, faker} = await make_user();
let update = await User.update_by_email(faker.email, faker.name + ' Updated', true);
let count = await User.update_by_email(faker.email, faker.name + ' Updated', true);
expect(count).toBe(1);

let found = await User.find_and_validate(faker.email);
expect(found).toBeDefined();
expect(found.user.get('full_name')).toBe(faker.name + ' Updated');
expect(found.user.get('send_emails')).toBe(true);
});


it('Can user the jsonb attributes', async () => {
let {user, faker} = await make_user();

user.set('finished_lesson', [1,2,3,4,5]);
user.save({method: 'update'});
expect(found.user.full_name).toBe(faker.name + ' Updated');
expect(found.user.send_emails).toBe(true);
});


+ 8
- 9
__tests__/models/user_auth.spec.js View File

@@ -6,22 +6,21 @@ afterAll(() => knex.destroy() );
it('Can reference a user', async() => {
let faker = t.fake_person();
let user = await User.register(faker.email, faker.name, false, true);
let auth = await Auth.forge({email: faker.email, ip_addr: '127.0.0.1', registration: false, user_id: user.id, token: 'asdfasdfasdf'}).save();
let auth = await Auth.insert({email: faker.email, ip_addr: '127.0.0.1', registration: false, user_id: user.id, token: 'asdfasdfasdf'});

// User has many Auth
let user2 = await User.forge({id: user.id}).fetch({withRelated: 'auths'});
let auth2 = await user2.related('auths');
let user2 = await User.first({id: user.id});
let auth2 = await user2.auths();
expect(user2).toBeDefined();
expect(await auth2.count()).toBe(1);
auth2 = await auth2.fetchOne(); // whoops, i broke the law
expect(auth2.id).toBeDefined();
expect(await auth2.length).toBe(1);
expect(auth2[0].id).toBeDefined();

// Auth has one User
let auth3 = await Auth.forge({id: auth.id}).fetch({withRelated: 'user'});
let auth3 = await Auth.first({id: auth.id});
expect(auth3.id).toBeDefined();
expect(auth3.id).toBe(auth2.id);
expect(auth3.id).toBe(auth2[0].id);

let user3 = await auth3.related('user');
let user3 = await auth3.user();
expect(user3.id).toBe(user2.id);
});


+ 53
- 46
__tests__/models/user_episode_state.spec.js View File

@@ -4,54 +4,61 @@ const {User, EpisodeState, knex} = require('@lib/models');
afterAll(() => knex.destroy() );

it('Can reference a user', async() => {
let faker = t.fake_person();
let user = await User.register(faker.email, faker.name, false, true);
expect(user).toBeDefined();

let ep1 = await EpisodeState.forge({user_id: user.id, url: '/test'}).save();
expect(ep1).toBeDefined();

// go from user to episode states
let u2 = await User.where({id: user.id}).fetch();
expect(u2).toBeDefined();
expect(u2.id).toBeDefined();
expect(u2.id).toBe(user.id);

let ep2 = await u2.related('episode_states').fetchOne();
expect(ep2).toBeDefined();
expect(ep2.id).toBeDefined();
expect(ep2.id).toBe(ep1.id);

// go from episode to user
let u3 = await ep2.related('user').fetch();
expect(u3).toBeDefined();
expect(u3.id).toBeDefined();
expect(u3.id).toBe(u2.id);
let faker = t.fake_person();
let user = await User.register(faker.email, faker.name, false, true);
expect(user).toBeDefined();
expect(user.id).toBeDefined();

let ep1 = await EpisodeState.insert({user_id: user.id, url: '/test'});
expect(ep1).toBeDefined();
expect(await ep1.user()).toBeDefined();

let ep1_user = await ep1.user();
ep1_user = await ep1.user();
expect(ep1_user.id).toBeDefined();

// go from user to episode states
let u2 = await User.first({id: user.id});
expect(u2).toBeDefined();
expect(u2.id).toBeDefined();
expect(u2.id).toBe(user.id);

let ep2 = (await u2.episode_states())[0];

expect(ep2).toBeDefined();
expect(ep2.id).toBeDefined();
expect(ep2.id).toBe(ep1.id);

// go from episode to user
let u3 = await ep2.user();
expect(u3).toBeDefined();
expect(u3.id).toBeDefined();
expect(u3.id).toBe(u2.id);
});

it('Can track user interactions', async () => {
let faker = t.fake_person();
let user = await User.register(faker.email, faker.name, false, true);
expect(user).toBeDefined();

let ep = await EpisodeState.forge({
user_id: user.id,
rating: 10,
feedback: 'This is really great',
url: '/live/1-the-beginning',
}).save();
expect(ep).toBeDefined();
expect(ep.id).toBeDefined();
expect(ep.get('url')).toBeDefined();
let ep2 = await EpisodeState.where({
user_id: user.id,
url: ep.get('url'),
}).fetch();
expect(ep2).toBeDefined();
expect(ep2.id).toBeDefined();
expect(ep2.get('rating')).toBe(ep.get('rating'));
let faker = t.fake_person();
let user = await User.register(faker.email, faker.name, false, true);
expect(user).toBeDefined();
let ep = await EpisodeState.insert({
user_id: user.id,
rating: 10,
feedback: 'This is really great',
url: '/live/1-the-beginning',
});
expect(ep).toBeDefined();
expect(ep.id).toBeDefined();
expect(ep.url).toBeDefined();
let ep2 = await EpisodeState.first({
user_id: user.id,
url: ep.url,
});
expect(ep2).toBeDefined();
expect(ep2.id).toBeDefined();
expect(ep2.rating).toBe(ep.rating);
});


+ 10
- 10
__tests__/models/user_exercise_state.spec.js View File

@@ -8,32 +8,32 @@ it('Can reference a user', async() => {
let user = await User.register(faker.email, faker.name, false, true);
expect(user).toBeDefined();

let mod = await ModuleState.forge({user_id: user.id, url: '/modules/test'}).save();
let mod = await ModuleState.insert({user_id: user.id, url: '/modules/test'});
expect(mod).toBeDefined();
expect(mod.id).toBeDefined();

let ex1 = await ExerciseState.forge({
let ex1 = await ExerciseState.insert({
user_id: user.id,
module_state_id: mod.id,
url: '/modules/test/1-ex1',
}).save();
});
expect(ex1).toBeDefined();
expect(ex1.id).toBeDefined();
expect(ex1.get('url')).toBeDefined();
expect(ex1.url).toBeDefined();

// go from user to episode states
let u2 = await User.where({id: user.id}).fetch();
let u2 = await User.first({id: user.id});
expect(u2).toBeDefined();
expect(u2.id).toBeDefined();
expect(u2.id).toBe(user.id);

let ex2 = await u2.related('exercise_states').fetchOne();
expect(ex2).toBeDefined();
expect(ex2.id).toBeDefined();
expect(ex2.id).toBe(ex1.id);
let ex2 = await u2.exercise_states();
expect(ex2[0]).toBeDefined();
expect(ex2[0].id).toBeDefined();
expect(ex2[0].id).toBe(ex1.id);

// go from exisode to user
let u3 = await ex2.related('user').fetch();
let u3 = await ex2[0].user();
expect(u3).toBeDefined();
expect(u3.id).toBeDefined();
expect(u3.id).toBe(u2.id);

+ 46
- 47
__tests__/models/user_module_state.spec.js View File

@@ -4,55 +4,54 @@ const {User, ModuleState, ExerciseState, knex} = require('@lib/models');
afterAll(() => knex.destroy() );

it('Can reference a user', async() => {
let faker = t.fake_person();
let user = await User.register(faker.email, faker.name, false, true);
expect(user).toBeDefined();
let mod1 = await ModuleState.forge({user_id: user.id, url: '/modules/test'}).save();
expect(mod1).toBeDefined();
// go from user to module states
let u2 = await User.where({id: user.id}).fetch();
expect(u2).toBeDefined();
expect(u2.id).toBeDefined();
expect(u2.id).toBe(user.id);
let mod2 = await u2.related('module_states').fetchOne();
expect(mod2).toBeDefined();
expect(mod2.id).toBeDefined();
expect(mod2.id).toBe(mod1.id);
// go from module to user
let u3 = await mod2.related('user').fetch();
expect(u3).toBeDefined();
expect(u3.id).toBeDefined();
expect(u3.id).toBe(u2.id);
let faker = t.fake_person();
let user = await User.register(faker.email, faker.name, false, true);
expect(user).toBeDefined();
let mod1 = await ModuleState.insert({user_id: user.id, url: '/modules/test'});
expect(mod1).toBeDefined();
// go from user to module states
let u2 = await User.first({id: user.id});
expect(u2).toBeDefined();
expect(u2.id).toBeDefined();
expect(u2.id).toBe(user.id);
let mod2 = await u2.module_states();
expect(mod2[0]).toBeDefined();
expect(mod2[0].id).toBeDefined();
expect(mod2[0].id).toBe(mod1.id);
// go from module to user
let u3 = await mod2[0].user();
expect(u3).toBeDefined();
expect(u3.id).toBeDefined();
expect(u3.id).toBe(u2.id);
});

it('Can reference lessons', async() => {
let faker = t.fake_person();
let user = await User.register(faker.email, faker.name, false, true);
expect(user).toBeDefined();

let mod1 = await ModuleState.forge({user_id: user.id, url: '/modules/test'}).save();
expect(mod1).toBeDefined();
expect(mod1.id).toBeDefined();

let ex1 = await ExerciseState.forge({
user_id: user.id,
module_state_id: mod1.id,
url: '/modules/test/1-test'
}).save();
expect(ex1).toBeDefined();
expect(ex1.id).toBeDefined();

let mods = await user.related('module_states').fetchOne();
expect(mods).toBeDefined();
expect(mods.id).toBe(mod1.id);

let ex2 = await mods.related('exercises').fetchOne();
expect(ex2).toBeDefined();
expect(ex2.id).toBe(ex1.id);

let faker = t.fake_person();
let user = await User.register(faker.email, faker.name, false, true);
expect(user).toBeDefined();

let mod1 = await ModuleState.insert({user_id: user.id, url: '/modules/test'});
expect(mod1).toBeDefined();
expect(mod1.id).toBeDefined();

let ex1 = await ExerciseState.insert({
user_id: user.id,
module_state_id: mod1.id,
url: '/modules/test/1-test'
});
expect(ex1).toBeDefined();
expect(ex1.id).toBeDefined();

let mods = await user.module_states();
expect(mods[0]).toBeDefined();
expect(mods[0].id).toBe(mod1.id);

let ex2 = await mods[0].exercise_states();
expect(ex2[0]).toBeDefined();
expect(ex2[0].id).toBe(ex1.id);
});


+ 1
- 1
__tests__/ui/login.spec.js View File

@@ -8,7 +8,7 @@ const host = require('@lib/secrets').root_url;

afterAll(async () => {
t.close()
await User.delete_by_email(main_user.email, false);
await User.delete_by_email(main_user.email);
knex.destroy();
});


+ 2
- 2
__tests__/ui/modules.spec.js View File

@@ -1,13 +1,13 @@
const t = require('@lib/testing');
const host = require('@lib/secrets').root_url;
const {User, Auth, knex} = require('@lib/models');
const {User, knex} = require('@lib/models');
const utils = require('../utils');

const main_user = t.fake_person();

afterAll(async () => {
t.close();
await User.delete_by_email(main_user.email, false);
await User.delete_by_email(main_user.email);
knex.destroy();
});


+ 6
- 6
__tests__/ui/register.spec.js View File

@@ -1,6 +1,6 @@

const t = require('@lib/testing');
const {User, Auth, knex} = require('@lib/models');
const {User, knex} = require('@lib/models');
const host = require('@lib/secrets').root_url;
const utils = require('../utils');

@@ -31,17 +31,17 @@ it('Can register for an account after soft login', async () => {
await page.click(t.sel('send-emails'));
await page.click(t.sel('account-submit'));

let updated = await User.find_and_validate(main_user.email, false);
while(updated.user.get('send_emails') == true) {
let updated = await User.find_and_validate(main_user.email);
while(updated.user.send_emails == true) {
console.log("User hasn't been set yet.", updated.user.full_name);
await t.sleep(1000); /// so dumb, but have to wait for this to finish
updated = await User.find_and_validate(main_user.email, false);
updated = await User.find_and_validate(main_user.email);
}

expect(updated.user).toBeDefined();
expect(updated.is_valid).toBe(true);
expect(updated.user.get('full_name')).toBe(main_user.name + ' Updated');
expect(updated.user.get('send_emails')).toBe(false);
expect(updated.user.full_name).toBe(main_user.name + ' Updated');
expect(updated.user.send_emails).toBe(false);
});

it('Can register off the index.', async () => {

+ 4
- 1
__tests__/utils.js View File

@@ -1,4 +1,5 @@
const t = require('@lib/testing');
const { log } = require('@lib/logging');
const {User, Auth, knex} = require('@lib/models');

/* Common operations common to this test suite. */
@@ -41,9 +42,11 @@ exports.register = async (main_user, page) => {
await page.click(t.sel('tos-agree'));
await page.click(t.sel('register-submit'));

log.debug("reg wait for dashboard-page");
await page.waitForSelector(t.sel('dashboard-page'));

let {user, is_valid} = await User.find_and_validate(main_user.email, true);
log.debug("reg find_and_validate");
let {user, is_valid} = await User.find_and_validate(main_user.email);

expect(is_valid).toBe(true);


+ 185
- 190
lib/models.js View File

@@ -8,212 +8,207 @@ const assert = require('assert');
const secrets = require('../lib/secrets');

let knex = require('knex')(knexfile[secrets.env]);
const bookshelf = require('bookshelf')(knex);

function fix_booleans(me, attr) {
for(let a of attr) {
// TODO: make sure this is the same with postgres or knex to figure it out
me.on('fetched', (model, response, options) => {
assert(a in model.attributes, `No attribute ${a} in ${model}`);
model.attributes[a] = model.attributes[a] == '1';
});

me.on('saved', (model, response, options) => {
assert(a in model.attributes, `No attribute ${a} in ${model}`);
model.attributes[a] = model.attributes[a] == '1';
});
class Model {
constructor(attr) {
assert(attr, "Must give attributes.");

for(let a in attr) {
this[a] = attr[a];
}

this.table_name = this.constructor.table_name;
}
}

async destroy() {
assert(this.table_name !== undefined, "You must set class variable table_name.");

exports.User = bookshelf.model('User',
{ // object methods
tableName: 'users',
initialize() {
this.constructor.__super__.initialize.apply(this, arguments);
fix_booleans(this, ['send_emails', 'tos_agree', 'verified', 'registered']);
},
auths() {
return this.hasMany('Auth');
},
exercise_states() {
return this.hasMany('ExerciseState');
},
episode_states() {
return this.hasMany('EpisodeState');
},
module_states() {
return this.hasMany('ModuleState');
}
},
{ // class methods
async find_and_validate(email, attr_only=false) {
assert(email, `Valid email required: ${email}`);

try {
let user = await this.where({email}).fetch({require: false});
// TODO: why am I getting by email then checking the email? do more validation
let is_valid = user ? user.get("email") === email && user.get('verified') : false;
log.debug("User validity check for", email, "returned", user, "with is_valid", is_valid);

if(attr_only) {
return {user: user === null ? null : user.attributes, is_valid};
} else {
return {user, is_valid};
}
} catch(error) {
log.error(error, "User.find_and_validate");
return {user: undefined, is_valid: false};
await knex(this.table_name).where({id: this.id}).del();
}

async related(model, attr, where, has_one=false) {
const safe_rel = '_' + attr;
const cached = this[safe_rel];

if(!cached) {
if(has_one) {
let res = await model.first(where);
this[safe_rel] = res;
} else {
let res = await model.all(where);
this[safe_rel] = res.map(r => new model(r));
}
},

async delete_by_email(email, require) {
assert(email);
return this[safe_rel];
} else {
return cached;
}
}

async has_one(model, where) {
return this.related(model, model.table_name, where, true);
}

try {
return this.where({email}).destroy({require});
} catch (error) {
log.error(error, "User.delete_by_email");
return undefined;
}
},

async register(email, full_name, send_emails, tos_agree) {
try {
return this.forge({
email: email,
verified: true,
full_name, send_emails, tos_agree, registered: true
}).save();
} catch (error) {
log.error(error);
throw error;
}
},

async update_by_email(email, full_name, send_emails) {
try {
log.debug("update received", {full_name, send_emails});
let user = await this.where({email}).save({
full_name, send_emails}, {method: "update"});
log.debug("User is after update", user)
return user;
} catch (error) {
log.error(error, "User.update_by_email");
}
},
async has_many(model, where) {
return this.related(model, model.table_name, where, false);
}

static async count(columns) {
return knex(this.table_name).count(columns);
}

static async insert(attr) {
assert(this.table_name !== undefined, "You must set class variable table_name.");
assert(attr, `You must give some attr to insert into ${this.table_name}`);

let res = await knex(this.table_name).returning('id').insert(attr);
assert(res, "Failed to get an id from the insert for User");

attr.id = res[0];

return new this(attr);
}

static async update(where, what) {
assert(where, "You must give a where options.");
return await knex(this.table_name).where(where).update(what);
}
);

static async first(where) {
assert(where, "You must give a where options.");
let attr = await knex(this.table_name).first().where(where);
return attr !== undefined ? new this(attr) : attr;
}

static async delete(where) {
assert(where, "You must give a where options.");
return knex(this.table_name).first().where(where).del();
}

static async all(where) {
assert(where, "You must give a where options.");
return knex(this.table_name).where(where);
}

static from_table(table_name) {
let m = class extends Model { };
m.table_name = table_name;
return m;
}
}


exports.Auth = bookshelf.model('Auth', {
tableName: 'auths',
initialize() {
this.constructor.__super__.initialize.apply(this, arguments);
fix_booleans(this, ['registration']);
},
class EpisodeState extends Model.from_table('episode_states') {
user() {
return this.belongsTo('User');
return this.has_one(User, {id: this.user_id});
}
},
{
async create(email, ip_addr, registration) {
assert(email, `Valid email required: ${email}`);
assert(ip_addr, "ip_addr required");
}

let token = uuid4();
class ModuleState extends Model.from_table('module_states') {

try {
return this.forge({ email, ip_addr, registration, token }).save();
} catch (error) {
log.error(error, "Auth.create");
throw error;
}
},
user() {
return this.has_one(User, {id: this.user_id});
}

async find_by_email(email) {
assert(email, `Valid email required: ${email}`);
exercise_states() {
return this.has_many(ExerciseState, {module_state_id: this.id});
}
}

// TODO: make a smaller query that just finds the token
try {
return new this({email}).fetch({require: false});
} catch (error) {
log.error(error,"Auth.find_by_email");
throw error;
}
},

async token_by_email(email) {
try {
assert(email, `Valid email required: ${email}`);
let auth = await this.find_by_email(email);
log.debug("Auth returned by token", auth);
return auth ? auth.get('token') : undefined;
} catch (error) {
log.error(error, "Auth.token_by_email");
throw error;
}
},

async find_by_token(token) {
console.log("TOKEN", token);
assert(token);
try {
// TODO: validate the token format
return new this().where({token}).fetch({require: false});
} catch (error) {
log.error(error, 'find_by_token');
throw error;
}
},

async destroy_by_email(email, require) {
assert(email);
try {
return new this().where({email}).destroy({require: require});
} catch (error) {
log.error(error, "Auth.destroy_by_email");
throw error;
}
}
class ExerciseState extends Model.from_table('exercise_states') {

user() {
return this.has_one(User, {id: this.user_id});
}
);

exports.ExerciseState = bookshelf.model('ExerciseState',
{ // object methods
tableName: 'exercise_states',
user() {
return this.belongsTo('User');
},

modules() {
return this.belongsTo('ModuleState');
},
},
);

exports.ModuleState = bookshelf.model('ModuleState',
{ // object methods
tableName: 'module_states',
user() {
return this.belongsTo('User');
},

exercises() {
return this.hasMany('ExerciseState');
},
},
);

exports.EpisodeState = bookshelf.model('EpisodeState',
{ // object methods
tableName: 'episode_states',

user() {
return this.belongsTo('User');
}
},
);

exports.knex = knex;
module_state() {
return this.has_many(ModuleState, {id: this.module_state_id}, true);
}
}

class User extends Model.from_table('users') {

async episode_states() {
return this.has_many(EpisodeState, {user_id: this.id});
}

async module_states() {
return this.has_many(ModuleState, {user_id: this.id});
}

async exercise_states() {
return this.has_many(ExerciseState, {user_id: this.id});
}

async auths() {
return this.has_many(Auth, {user_id: this.id});
}

static async find_and_validate(email, attr_only=false) {
assert(attr_only === false, "This parameter is no longer used.");
assert(email, `Valid email required: ${email}`);

let user = await knex('users').first().where({email});

// TODO: why am I getting by email then checking the email? do more validation
let is_valid = user ? user.email === email && user.verified : false;
log.debug("User validity check for", email, "returned", user, "with is_valid", is_valid);

return {user, is_valid};
}

static async delete_by_email(email) {
assert(email);
return User.delete({email});
}

static async register(email, full_name, send_emails, tos_agree) {
return User.insert({
email: email,
verified: true,
full_name, send_emails, tos_agree, registered: true
});
}

static async update_by_email(email, full_name, send_emails) {
return User.update({email}, {full_name, send_emails});
}
}


class Auth extends Model.from_table('auths') {
user() {
return this.has_one(User, {id: this.user_id});
}

static async create(email, ip_addr, registration) {
assert(email, `Valid email required: ${email}`);
assert(ip_addr, "ip_addr required");

return Auth.insert({email, ip_addr, registration, token: uuid4()});
}

static async find_by_email(email) {
assert(email, `Valid email required: ${email}`);
return Auth.first({email});
}

static async token_by_email(email) {
assert(email, `Valid email required: ${email}`);
let auth = await this.find_by_email(email);
log.debug("Auth returned by token", auth);
return auth ? auth.token : undefined;
}

static async find_by_token(token) {
assert(token, "Token required on find_by_token.");
return Auth.first({token});
}

static async destroy_by_email(email) {
assert(email, "Must give email.");
return Auth.delete({email});
}
}

module.exports = { knex, User, Auth, EpisodeState, ModuleState, ExerciseState };

+ 44
- 44
scripts/services/auth.js View File

@@ -35,72 +35,72 @@ const unsubscribe = new Queue('unsubscribe', secrets.bull_config);

auth_request.process(async (job) => {
try {
log.debug("Received job", job.id);
let authtoken;
let options;
log.debug("Received job", job.id);
let authtoken;
let options;

// lookup the person in the user's table, and if they aren't there then
// send a registration email instead of a login email
let {user, is_valid} = await User.find_and_validate(job.data.to)
log.debug("Processing auth_request user", user, "is_valid", is_valid);
// lookup the person in the user's table, and if they aren't there then
// send a registration email instead of a login email
let {user, is_valid} = await User.find_and_validate(job.data.to)
log.debug("Processing auth_request user", user, "is_valid", is_valid);

// TODO: lots of duplicate here
if(is_valid === true) {
log.debug("User", job.data.to, "is valid sending an authentication email.");
auth = await Auth.create(job.data.to, job.data.ip_addr, false);
authtoken = auth.get('token');
assert(auth, "No auth token returned on create for valid user.");
log.debug("Auth token is", authtoken);
options = gen_login_email(job.data.to, authtoken);
} else {
log.debug("User", job.data.to, "is NOT valid sending a REGISTRATION email.");
auth = await Auth.create(job.data.to, job.data.ip_addr, true);
authtoken = auth.get('token');
assert(auth, "No auth token returned on create for valid user.");
log.debug("Auth token is", authtoken);
options = gen_reg_email(job.data.to, authtoken);
}
// TODO: lots of duplicate here
if(is_valid === true) {
log.debug("User", job.data.to, "is valid sending an authentication email.");
auth = await Auth.create(job.data.to, job.data.ip_addr, false);
authtoken = auth.token;
assert(auth, "No auth token returned on create for valid user.");
log.debug("Auth token is", authtoken);
options = gen_login_email(job.data.to, authtoken);
} else {
log.debug("User", job.data.to, "is NOT valid sending a REGISTRATION email.");
auth = await Auth.create(job.data.to, job.data.ip_addr, true);
authtoken = auth.token;
assert(auth, "No auth token returned on create for valid user.");
log.debug("Auth token is", authtoken);
options = gen_reg_email(job.data.to, authtoken);
}

transporter.sendMail(options, (error, info) => {
if (error) {
log.error(error);
} else {
log.debug(info.envelope);
log.debug(info.messageId);
info.message.pipe(process.stdout);
}
});
transporter.sendMail(options, (error, info) => {
if (error) {
log.error(error);
} else {
log.debug(info.envelope);
log.debug(info.messageId);
info.message.pipe(process.stdout);
}
});
} catch (error) {
log.error(error, "auth_request");
log.error(error, "auth_request");
}
});

delete_auth.process(async (job) => {
try {
log.info("Received delete request", job.data.email);
await Auth.destroy_by_email(job.data.email, false)
log.info("Received delete request", job.data.email);
await Auth.destroy_by_email(job.data.email);
} catch (error) {
log.error(error, "delete_auth");
log.error(error, "delete_auth");
}
});

subscribe.process(async (job) => {
try {
log.info("Received subscribe for user", job.data.email);
log.debug("Job data for subscribe", job);
await train.subscribe(job.data.email);
log.info("Received subscribe for user", job.data.email);
log.debug("Job data for subscribe", job);
await train.subscribe(job.data.email);
} catch (error) {
log.error(error, "subscribe");
log.error(error, "subscribe");
}
});

unsubscribe.process(async (job) => {
try {
log.info("Received unsubscribe for user", job.data.email);
await train.unsubscribe(job.data.email);
log.info("Received unsubscribe for user", job.data.email);
await train.unsubscribe(job.data.email);
} catch (error) {
log.error(error, "unsubscribe");
done(error);
log.error(error, "unsubscribe");
done(error);
}
});


+ 3
- 10
src/routes/auth/verify.js View File

@@ -2,13 +2,6 @@ import { delete_authmail } from 'mq';
import { Auth, User} from '../../../lib/models';
import { log } from 'logging';


// temporary until I can swap it all out
export async function get_auth_token(token) {
let auth = await Auth.find_by_token(token);
return auth ? auth.attributes : undefined;
}

export async function post(req, res) {
const user = req.body;
const ip_addr = req.headers['x-forwarded-for'] || req.connection.localAddress;
@@ -18,11 +11,11 @@ export async function post(req, res) {

if(user.email === undefined) {
res.statusCode = 403;
res.end(JSON.stringify({message: "Email required."}));
log.debug("Email not given by user", user);
res.end(JSON.stringify({message: "Email required."}));
} else {
// get the user's auth token by email and token in the database
let expected_auth = await get_auth_token(user.token);
let expected_auth = await await Auth.find_by_token(user.token);
log.debug("Auth in the database is", expected_auth);

if(expected_auth === undefined) {
@@ -42,7 +35,7 @@ export async function post(req, res) {

if(user.registered) {
// look them up in the database and return that
let valid = await User.find_and_validate(user.email, true);
let valid = await User.find_and_validate(user.email);
// went through validating this auth, so we're kind of doing it again
if(valid.is_valid) {
log.debug("User", user, "test user", valid);

+ 7
- 8
src/routes/register/index.json.js View File

@@ -1,5 +1,5 @@
import { log } from 'logging'
const { User, Auth } = require('../../../lib/models');
const { User } = require('../../../lib/models');
import {subscribe_user} from 'mq';

async function register_user(user, full_name, send_emails, tos_agree)
@@ -10,9 +10,9 @@ async function register_user(user, full_name, send_emails, tos_agree)

let new_user = await User.register(user.email, full_name, send_emails, tos_agree);

if(new_user.get('send_emails')) {
if(new_user.send_emails) {
log.debug("Sending Queue the subscribe to train request.");
subscribe_user(new_user.get('email'));
subscribe_user(new_user.email);
}

return new_user;
@@ -35,13 +35,12 @@ export async function post(req, res) {
log.error(`User ${found.user.email} already exists.`, found.user.attributes);

// need to update their settings as well as set them registered
found.user.set('registered', true);
found.user.save({}, {method: "update"});

res.end(JSON.stringify({user: found.user.attributes}));
let count = User.update({id: found.user.id}, { registered: true});
log.debug("User update count after register is", count);
res.end(JSON.stringify({user: found.user}));
} else {
const updated_user = await register_user(user, full_name, send_emails, tos_agree);
res.end(JSON.stringify({user: updated_user.attributes}));
res.end(JSON.stringify({user: updated_user}));
}
} catch(error) {
log.error(error, "register post");

+ 2
- 2
src/routes/user/index.json.js View File

@@ -5,8 +5,8 @@ export async function post(req, res) {
if(req.session.user && req.session.user.verified) {
log.debug("Received user", req.body);
let new_user = await User.update_by_email(req.session.user.email, req.body.full_name, req.body.send_emails);
req.session.user.full_name = new_user.get('full_name');
req.session.user.send_emails = new_user.get('send_emails');
req.session.user.full_name = new_user.full_name;
req.session.user.send_emails = new_user.send_emails;
res.end(JSON.stringify({message: "OK"}));
} else {
log.error("Attempt to access API without auth.");

Loading…
Cancel
Save