Skip to content

Commit

Permalink
Merge pull request #28 from two-factor/postgres
Browse files Browse the repository at this point in the history
Postgres boilerplate added
  • Loading branch information
iangeckeler committed Jul 8, 2019
2 parents 935dc05 + 52562e4 commit 028b18a
Show file tree
Hide file tree
Showing 16 changed files with 428 additions and 128 deletions.
57 changes: 56 additions & 1 deletion ReadMe.md
Original file line number Diff line number Diff line change
@@ -1 +1,56 @@
This is the two factor twilio library.
# What is Two-Auth?

Two-Auth simplifies the process of implementing two-factor SMS authentication for your application. Two-Auth provides a simplified wrapper for Twilio’s verify API 2.0.

Two-Auth comes out of the box with *one* constructor function and *three* primary methods to `create` your registered user, `send` your user a verification code, and `verify` your user's code.

## Installation

In your terminal, type:

$ npm install --save two-auth

If you have not already, make sure to sign up for a Twilio account to receive your API credentials. You can sign up for Twilio here: https://www.twilio.com/try-twilio.

## Initialization

In your application's backend, likely in an Express middleware controller (or wherever you manage authentication), require 'two-auth.' Then invoke the twoAuth function with your API credentials: ***your*** **Twilio Account SID** and ***your*** **Twilio Auth Token**.

const twoAuth = require('two-auth');
const client = twoAuth(*ACC_SID*, *AUTH_TOKEN*);

> Optionally: you may pass a third parameter Mongo database connection URI so that your Twilio SID, your application's Twilio registered user IDs, their phone numbers are persistent inside your Mongo database. Initialize like so: `twoAuth(*ACC_SID*, *AUTH_TOKEN*, *MONGO_DB_URI*)`. `twoAuth` stores your SID, registered user IDs and phone numbers inside a collection on your passed in Mongo database under the name `two auth users`
The function will return an instance of a Two-Auth `client`. That `client` will have the `create`, `send`, and `verify` methods.

## Two-Auth Methods

### `create()`

Provide two-auth with a user ID and a phone number associated with that user.

client.create(*USER_ID*, *PHONE_NUMBER*);

> Warning: Two-Auth currently only supports US phone numbers.
`create` registers a new verification service with Twilio, which will allow your application to later send and verify codes to and from that phone number and user.

### `send()`

Once *your* user reaches the point in your app's login where you would like them to input the sms code:

client.send(*USER_ID*);

> Make sure that the user ID or username you pass as an argument is the same as the user ID you passed to `client.create()`
`send` then routes through Twilio's API and sends an SMS containing the six-digit verification code to the phone number you associated with the user ID when you registered *your* user when you invoked `create`.

### `verify()`

Once your user inputs their six digit code, pass it into the verification method:

client.verify(*USER_ID*, *SIX_DIGIT_CODE*)

> Make sure that the *code* you pass is a `string`! NOT a `number`.
`verify` will properly identify and `return` `true` if the code is valid, `false` if the code is invalid.
2 changes: 1 addition & 1 deletion __tests__/functions/create.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const create = require("../functions/create");
const create = require("../../functions/create");
describe("tests the create function", () => {
class FakeClient {
constructor(isError) {
Expand Down
50 changes: 25 additions & 25 deletions __tests__/functions/test-send.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const send = require('./../functions/send');
const send = require("../../functions/send");

describe('send unit tests', () => {
describe("send unit tests", () => {
const sid = 1;
const phone = 2;
let mockVerificationCreate = jest.fn();
Expand All @@ -9,63 +9,63 @@ describe('send unit tests', () => {
users: {
test: {
sid,
phone,
phone
}
},
send,
client: {
verify: {
services: () => ({
verifications: {
create: (obj) => {
create: obj => {
mockVerificationCreate();
return new Promise((resolve) => {
return new Promise(resolve => {
resolve(obj);
});
},
},
}),
},
},
}
}
})
}
}
};

beforeEach(() => {
mockVerificationCreate.mockClear();
})
});

it('send should throw error upon sending to nonexistent user', () => {
it("send should throw error upon sending to nonexistent user", () => {
try {
client.send('nonexistent');
client.send("nonexistent");
} catch (err) {
expect(err).toBeInstanceOf(Error);
}
});

it('send should throw error upon nonexistent sid', () => {
it("send should throw error upon nonexistent sid", () => {
try {
client.send('test');
client.send("test");
} catch (err) {
expect(err).toBeInstanceOf(Error);
}
});

it('send should throw error upon nonexistent phone number', () => {
it("send should throw error upon nonexistent phone number", () => {
try {
client.send('test');
client.send("test");
} catch (err) {
expect(err).toBeInstanceOf(Error);
}
})
});

it('send should create a verification', () => {
client.send('test');
client.send('test');
it("send should create a verification", () => {
client.send("test");
client.send("test");
expect(mockVerificationCreate.mock.calls.length).toBe(2);
})
});

it('send should be passing an object to the verification.create call containing phone number and channel', (done) => {
client.send('test').then((res) => {
expect(res).toEqual({ to: phone, channel: 'sms' });
it("send should be passing an object to the verification.create call containing phone number and channel", done => {
client.send("test").then(res => {
expect(res).toEqual({ to: phone, channel: "sms" });
done();
});
});
Expand Down
82 changes: 41 additions & 41 deletions __tests__/functions/verify.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,51 @@
const verify = require('../functions/verify')
//On client check verify
const verify = require("../../functions/verify");
//On client check verify

describe('#verify', () => {
class FakeClient {
describe("#verify", () => {
class FakeClient {
constructor(isError) {
this.client = {
verify: {
services: () => ({
verificationChecks: {
create: ({to, code}) => {
return new Promise((resolve,reject) => {
if(!isError) resolve({status: 'approved'})
else reject({status: false })
})
}
}
})
verify: {
services: () => ({
verificationChecks: {
create: ({ to, code }) => {
return new Promise((resolve, reject) => {
if (!isError) resolve({ status: "approved" });
else reject({ status: false });
});
}
}
})
}
};
this.verify = verify;
this.users = { Zep: { sid: "Zep3246", phone: "3479087000" } };
}
this.verify = verify
this.users = {'Zep': {'sid':'Zep3246', 'phone':'3479087000'}}
}
}

it('throws an error if this.users is empty', () => {
const fakeClient = new FakeClient(false)
// FakeClient.users = {}
return expect((fakeClient.verify('ian'))).rejects.toBeInstanceOf(Error)
})
it("throws an error if this.users is empty", () => {
const fakeClient = new FakeClient(false);
// FakeClient.users = {}
return expect(fakeClient.verify("ian")).rejects.toBeInstanceOf(Error);
});

it('throws an error if sid of the user not found', () => {
const fakeClient = new FakeClient(false)
fakeClient.users['Zep'].sid = null
// FakeClient.users = {}
return expect((fakeClient.verify('Zep'))).rejects.toBeInstanceOf(Error)
})
it("throws an error if sid of the user not found", () => {
const fakeClient = new FakeClient(false);
fakeClient.users["Zep"].sid = null;
// FakeClient.users = {}
return expect(fakeClient.verify("Zep")).rejects.toBeInstanceOf(Error);
});

it('throws an error if phone of the user not found', () => {
const fakeClient = new FakeClient(false)
fakeClient.users['Zep'].phone = null
// FakeClient.users = {}
return expect((fakeClient.verify('Zep'))).rejects.toBeInstanceOf(Error)
})
it("throws an error if phone of the user not found", () => {
const fakeClient = new FakeClient(false);
fakeClient.users["Zep"].phone = null;
// FakeClient.users = {}
return expect(fakeClient.verify("Zep")).rejects.toBeInstanceOf(Error);
});

it('status approved if the code matches', () => {
const fakeClient = new FakeClient(false)
return expect((fakeClient.verify('Zep'))).resolves.toBeTruthy()
})
})
it("status approved if the code matches", () => {
const fakeClient = new FakeClient(false);

return expect(fakeClient.verify("Zep")).resolves.toBeTruthy();
});
});
13 changes: 0 additions & 13 deletions __tests__/mongoose/mongoose-create.js

This file was deleted.

20 changes: 0 additions & 20 deletions __tests__/mongoose/mongoose-send.js

This file was deleted.

Empty file.
73 changes: 69 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,57 @@ const create = require("./functions/create.js");
const send = require("./functions/send.js");
const verify = require("./functions/verify.js");

// import mongoose functions
const mongooseCreate = require("./mongoose/create");
const mongooseSend = require("./mongoose/send");
const mongooseVerify = require("./mongoose/verify");

const userSchema = require("./mongoose/userSchema");

//import postgres functions
const generatePool = require("./postgres/configure");
const createTable = require("./postgres/createtable");
const postgresCreate = require("./postgres/create");
const postgresSend = require("./postgres/send");
const postgresVerify = require("./postgres/verify");

const connect = (AccSID, AuthToken, mongoURI = null) => {
return new Client(AccSID, AuthToken, mongoURI);
};

// EXAMPLE CONFIG OBJECT
// options = {
// appName: "",
// connectionURI: null,
// isPostgres: null
// }
class Client {
constructor(AccSID, AuthToken, mongoURI = null) {
constructor(
AccSID,
AuthToken,
options = {
appName: "",
connectionURI: null,
isPostgres: false
}
) {
if (typeof options === "string")
throw new Error(
"Options config must be an object, as specified in the documentation."
);
if (!options.hasOwnProperty("isPostgres")) {
options.isPostgres = false;
}
if (!options.hasOwnProperty("appName")) {
options.appName = "";
}
this.AccSID = AccSID;
this.AuthToken = AuthToken;
this.client = twilio(this.AccSID, this.AuthToken);
this.users = {};

if (mongoURI !== null) {
if (options.connectionURI !== null && options.isPostgres === false) {
mongoose
.connect(mongoURI)
.connect(options.connectionURI)
.then(db => {
console.log("Two Factor successfully connected to Mongo");
})
Expand All @@ -35,6 +66,40 @@ class Client {
this.create = mongooseCreate;
this.send = mongooseSend;
this.verify = mongooseVerify;
} else if (options.connectionURI !== null && options.isPostgres === true) {
// connect the database and assign a reference to it to our client object
const pgPool = generatePool(options.connectionURI);
let tableCreated = false;
this.pgConnect = function() {
return new Promise((resolve, reject) => {
// connection using created pool
pgPool.connect(function(err, database, done) {
if (err) reject(new Error("Error connecting to Postgres Pool."));
if (!database) {
throw new Error("Could not find Database at Connection URI.");
}
// if table not created yet, create it and modify closure to reflect
if (tableCreated === false) {
database
.query(createTable)
.then(res => {
resolve({ database, done });
tableCreated = true;
})
.catch(e =>
reject(new Error("Error connecting to Postgres Pool."))
);
} else {
resolve({ database, done });
}
});
});
};

// assign the postgres functions to our client object
this.create = postgresCreate;
this.send = postgresSend;
this.verify = postgresVerify;
} else {
this.create = create;
this.send = send;
Expand Down

0 comments on commit 028b18a

Please sign in to comment.