diff --git a/packages/code-provider-twilio/README.md b/packages/code-provider-twilio/README.md new file mode 100644 index 000000000..ad02388fd --- /dev/null +++ b/packages/code-provider-twilio/README.md @@ -0,0 +1,35 @@ +# @accounts/code-provider-twilio + +## Install + +``` +yarn add @accounts/code-provider-twilio +``` + +## Usage + +```js +import { AccountsServer } from '@accounts/server'; +import { AccountsCode } from '@accounts/code'; +import { CodeProviderTwilio } from '@accounts/code-provider-twilio'; + +const codeProvider = new CodeProviderTwilio({ + sid: 'TWILIO_SID', + secret: 'TWILIO_SECRET', + phoneNumber: 'TWILIO_FROM_PHONE_NUMBER', +}); + +export const accountsCode = new AccountsCode({ + codeProvider, + // options +}); + +const accountsServer = new AccountsServer( + { + // options + }, + { + code: accountsCode, + } +); +``` diff --git a/packages/code-provider-twilio/package.json b/packages/code-provider-twilio/package.json new file mode 100644 index 000000000..4cf2ca69c --- /dev/null +++ b/packages/code-provider-twilio/package.json @@ -0,0 +1,27 @@ +{ + "name": "@accounts/code-provider-twilio", + "version": "0.19.0", + "license": "MIT", + "main": "lib/index.js", + "typings": "lib/index.d.ts", + "scripts": { + "clean": "rimraf lib", + "start": "tsc --watch", + "precompile": "yarn clean", + "compile": "tsc", + "prepublishOnly": "yarn compile" + }, + "jest": { + "testEnvironment": "node", + "preset": "ts-jest" + }, + "dependencies": { + "twilio": "^3.33.3" + }, + "devDependencies": { + "@accounts/code": "^0.19.0" + }, + "peerDependencies": { + "@accounts/code": "^0.19.0" + } +} diff --git a/packages/code-provider-twilio/src/code-provider-twilio.ts b/packages/code-provider-twilio/src/code-provider-twilio.ts new file mode 100644 index 000000000..c39ad7b1b --- /dev/null +++ b/packages/code-provider-twilio/src/code-provider-twilio.ts @@ -0,0 +1,50 @@ +import { Twilio } from 'twilio'; + +import { CodeProvider } from '@accounts/code'; + +export type MessageCreator = (code: string) => string; + +export interface TwilioSmsCodeProviderOptions { + sid: string; + secret: string; + phoneNumber?: string; + messagingServiceSid?: string; + messageCreator?: MessageCreator; +} + +export default class TwilioSmsCodeProvider implements CodeProvider { + private messageCreator: MessageCreator; + private phoneNumber?: string; + private messagingServiceSid?: string; + private twilio: Twilio; + + constructor({ + sid, + secret, + phoneNumber, + messagingServiceSid, + messageCreator = code => `This is your authentication code: ${code}`, + }: TwilioSmsCodeProviderOptions) { + this.twilio = new Twilio(sid, secret); + this.phoneNumber = phoneNumber; + this.messagingServiceSid = messagingServiceSid; + this.messageCreator = messageCreator; + } + + public async sendToClient(serviceId: string, code: string): Promise { + const options: any = { + body: this.messageCreator(code), + to: serviceId, + }; + + if (this.phoneNumber) { + options.from = this.phoneNumber; + } else if (this.messagingServiceSid) { + options.messagingServiceSid = this.messagingServiceSid; + } else { + throw new Error('Not enough twilio credentials'); + } + + await this.twilio.messages.create(options); + } +} diff --git a/packages/code-provider-twilio/src/index.ts b/packages/code-provider-twilio/src/index.ts new file mode 100644 index 000000000..c5e82924f --- /dev/null +++ b/packages/code-provider-twilio/src/index.ts @@ -0,0 +1,4 @@ +import CodeProviderTwilio from './code-provider-twilio'; + +export default CodeProviderTwilio; +export { CodeProviderTwilio }; diff --git a/packages/code-provider-twilio/tsconfig.json b/packages/code-provider-twilio/tsconfig.json new file mode 100644 index 000000000..4ec56d0f8 --- /dev/null +++ b/packages/code-provider-twilio/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "importHelpers": true + }, + "exclude": ["node_modules", "__tests__", "lib"] +} diff --git a/packages/code/README.md b/packages/code/README.md new file mode 100644 index 000000000..4a7bb4e63 --- /dev/null +++ b/packages/code/README.md @@ -0,0 +1,30 @@ +# @accounts/code + +## Install + +``` +yarn add @accounts/code +``` + +## Usage + +```js +import { AccountsServer } from '@accounts/server'; +import { AccountsCode } from '@accounts/code'; + +const codeProvider = '...'; + +export const accountsCode = new AccountsCode({ + codeProvider, + // options +}); + +const accountsServer = new AccountsServer( + { + // options + }, + { + code: accountsCode, + } +); +``` diff --git a/packages/code/__tests__/accounts-code.ts b/packages/code/__tests__/accounts-code.ts new file mode 100644 index 000000000..1916d10e0 --- /dev/null +++ b/packages/code/__tests__/accounts-code.ts @@ -0,0 +1,195 @@ +import { AccountsCode, CodeProvider, ErrorMessages } from '../src'; + +function createCodeProvider(success: boolean): CodeProvider { + return { + sendToClient: jest.fn(() => (success ? Promise.resolve() : Promise.reject())), + }; +} + +const errors: ErrorMessages = { + userNotFound: '0', + codeExpired: '1', + codeWasNotFound: '2', + wrongCode: '3', + failedToProvideCode: '4', +}; + +describe('AccountsCode', () => { + describe('preparation', () => { + it('throws error when no user is found', async () => { + const code = new AccountsCode({ codeProvider: createCodeProvider(true), errors }); + const findUserByServiceId = jest.fn(() => Promise.resolve()); + code.setStore({ findUserByServiceId } as any); + + try { + await code.prepareAuthentication('1234'); + } catch (e) { + expect(e.message).toEqual(errors.userNotFound); + } + + expect.assertions(1); + }); + + it('throws error when failed to send code to the client', async () => { + const code = new AccountsCode({ codeProvider: createCodeProvider(false), errors }); + + const user = { + id: '123', + }; + + const findUserByServiceId = jest.fn(() => Promise.resolve(user)); + const setService = jest.fn(() => Promise.resolve()); + code.setStore({ findUserByServiceId, setService } as any); + + try { + await code.prepareAuthentication('1234'); + } catch (e) { + expect(e.message).toEqual(errors.failedToProvideCode); + } + + expect.assertions(1); + }); + + it('completes successfully', async () => { + const code = new AccountsCode({ codeProvider: createCodeProvider(true), errors }); + + const user = { + id: '123', + }; + + const findUserByServiceId = jest.fn(() => Promise.resolve(user)); + const setService = jest.fn(() => Promise.resolve()); + code.setStore({ findUserByServiceId, setService } as any); + + const res = await code.prepareAuthentication('1234'); + + expect(res).toBeUndefined(); + expect(setService).toHaveBeenCalledTimes(1); + expect(setService).toHaveBeenCalledWith('123', code.serviceName, { + id: '1234', + code: expect.any(String), + expiry: expect.any(Number), + }); + }); + }); + + describe('Authentication', () => { + const code = new AccountsCode({ codeProvider: createCodeProvider(true), errors }); + + it('throws error when no user is found', async () => { + const findUserByServiceId = jest.fn(() => Promise.resolve(null)); + code.setStore({ findUserByServiceId } as any); + + try { + await code.authenticate({ serviceId: '1234', code: '2233' }); + } catch (e) { + expect(e.message).toEqual(errors.userNotFound); + } + + expect.assertions(1); + }); + + it('throws error when no code is found', async () => { + const findUserByServiceId = jest.fn(() => + Promise.resolve({ + id: '123', + }) + ); + code.setStore({ findUserByServiceId } as any); + + try { + await code.authenticate({ serviceId: '1234', code: '2233' }); + } catch (e) { + expect(e.message).toEqual(errors.codeWasNotFound); + } + + expect.assertions(1); + }); + + it('throws error when no code is found 2', async () => { + const findUserByServiceId = jest.fn(() => + Promise.resolve({ + id: '123', + services: { + [code.serviceName]: {}, + }, + }) + ); + code.setStore({ findUserByServiceId } as any); + + try { + await code.authenticate({ serviceId: '1234', code: '2233' }); + } catch (e) { + expect(e.message).toEqual(errors.codeWasNotFound); + } + + expect.assertions(1); + }); + + it('throws error when code is wrong', async () => { + const findUserByServiceId = jest.fn(() => + Promise.resolve({ + id: '123', + services: { + [code.serviceName]: { + code: '1111', + }, + }, + }) + ); + code.setStore({ findUserByServiceId } as any); + + try { + await code.authenticate({ serviceId: '1234', code: '2233' }); + } catch (e) { + expect(e.message).toEqual(errors.wrongCode); + } + + expect.assertions(1); + }); + + it('throws error when code is expired', async () => { + const findUserByServiceId = jest.fn(() => + Promise.resolve({ + id: '123', + services: { + [code.serviceName]: { + code: 'vKw3G1T1mUWhSqSeLkCOXW5NvFk4f12M/GsBXUDVuwI=', + expiry: Date.now() - 10000, + }, + }, + }) + ); + code.setStore({ findUserByServiceId } as any); + + try { + await code.authenticate({ serviceId: '1234', code: '2233' }); + } catch (e) { + expect(e.message).toEqual(errors.codeExpired); + } + + expect.assertions(1); + }); + + it('authenticates successfully and returns user', async () => { + const user = { + id: '123', + services: { + [code.serviceName]: { + code: 'vKw3G1T1mUWhSqSeLkCOXW5NvFk4f12M/GsBXUDVuwI=', + expiry: Date.now() + 10000, + }, + }, + }; + const findUserByServiceId = jest.fn(() => Promise.resolve(user)); + const setService = jest.fn(() => Promise.resolve()); + code.setStore({ findUserByServiceId, setService } as any); + + const res = await code.authenticate({ serviceId: '1234', code: '2233' }); + + expect(res).toEqual(user); + expect(setService).toHaveBeenCalledTimes(1); + expect(setService).toHaveBeenCalledWith('123', code.serviceName, { id: '1234' }); + }); + }); +}); diff --git a/packages/code/package.json b/packages/code/package.json new file mode 100644 index 000000000..ec779b6d7 --- /dev/null +++ b/packages/code/package.json @@ -0,0 +1,36 @@ +{ + "name": "@accounts/code", + "version": "0.19.0", + "license": "MIT", + "main": "lib/index.js", + "typings": "lib/index.d.ts", + "scripts": { + "clean": "rimraf lib", + "start": "tsc --watch", + "precompile": "yarn clean", + "compile": "tsc", + "prepublishOnly": "yarn compile", + "testonly": "jest --coverage", + "coverage": "jest --coverage" + }, + "jest": { + "testEnvironment": "node", + "preset": "ts-jest" + }, + "dependencies": { + "@accounts/types": "^0.19.0", + "lodash": "^4.17.15", + "randomstring": "^1.1.5", + "tslib": "1.10.0" + }, + "devDependencies": { + "@accounts/server": "^0.19.0", + "@types/randomstring": "^1.1.6", + "@types/jest": "24.0.16", + "jest": "24.8.0", + "rimraf": "2.6.3" + }, + "peerDependencies": { + "@accounts/server": "^0.19.0" + } +} diff --git a/packages/code/src/accounts-code.ts b/packages/code/src/accounts-code.ts new file mode 100644 index 000000000..db2bb4611 --- /dev/null +++ b/packages/code/src/accounts-code.ts @@ -0,0 +1,108 @@ +import { AuthenticationService, DatabaseInterface, User } from '@accounts/types'; +import { AccountsServer } from '@accounts/server'; + +import { CodeGenerator, CodeProvider, CodeHash, ErrorMessages, CodeLoginType } from './types'; +import SimpleCodeHash from './utils/simple-code-hash'; +import RandomstringCodeGenerator from './utils/randomstring-code-generator'; +import { errors } from './errors'; + +const DEFAULT_EXPIRY = 20 * 60 * 1000; // 20 minutes in milliseconds + +export interface AccountsCodeOptions { + codeGenerator?: CodeGenerator; + codeProvider: CodeProvider; + codeHash?: CodeHash; + expiry?: number; + errors?: ErrorMessages; +} + +interface DBService { + id: string; + code?: string; + expiry?: number; +} + +const defaultOptions = { + codeGenerator: new RandomstringCodeGenerator({ length: 4, charset: 'numeric' }), + codeHash: new SimpleCodeHash() as CodeHash, + expiry: DEFAULT_EXPIRY, + errors, +}; + +export default class CodeService implements AuthenticationService { + public serviceName = 'code'; + public server!: AccountsServer; + private db!: DatabaseInterface; + private options: AccountsCodeOptions & typeof defaultOptions; + + constructor(options: AccountsCodeOptions) { + this.options = { ...defaultOptions, ...options }; + } + + public setStore(store: DatabaseInterface) { + this.db = store; + } + + public async updateUserServiceId(userId: string, serviceId: string): Promise { + const dbService: DBService = { + id: serviceId, + }; + + await this.db.setService(userId, this.serviceName, dbService); + } + + public async prepareAuthentication(serviceId: string) { + const user = await this.db.findUserByServiceId(this.serviceName, serviceId); + + if (!user) { + throw new Error(this.options.errors.userNotFound); + } + + const newCode = await this.options.codeGenerator.generate(); + const hashedCode = this.options.codeHash.hash(newCode); + + const dbService: DBService = { + id: serviceId, + code: hashedCode, + expiry: Date.now() + this.options.expiry, + }; + + await this.db.setService(user.id, this.serviceName, dbService); + + try { + await this.options.codeProvider.sendToClient(serviceId, newCode); + } catch (e) { + throw new Error(this.options.errors.failedToProvideCode); + } + } + + public async authenticate({ serviceId, code }: CodeLoginType): Promise { + const user = await this.db.findUserByServiceId(this.serviceName, serviceId); + + if (!user) { + throw new Error(this.options.errors.userNotFound); + } + + if (!user.services || !(user.services as any)[this.serviceName]) { + throw new Error(this.options.errors.codeWasNotFound); + } + + const { code: serviceCode, expiry }: DBService = (user.services as any)[this.serviceName]; + + if (!serviceCode) { + throw new Error(this.options.errors.codeWasNotFound); + } + + if (!this.options.codeHash.verify(code, serviceCode)) { + throw new Error(this.options.errors.wrongCode); + } + + if (expiry && expiry < Date.now()) { + throw new Error(this.options.errors.codeExpired); + } + + await this.db.setService(user.id, this.serviceName, { id: serviceId }); + + return user; + } +} diff --git a/packages/code/src/errors.ts b/packages/code/src/errors.ts new file mode 100644 index 000000000..6d615253a --- /dev/null +++ b/packages/code/src/errors.ts @@ -0,0 +1,9 @@ +import { ErrorMessages } from './types'; + +export const errors: ErrorMessages = { + userNotFound: 'User not found', + codeExpired: 'Code is expired', + codeWasNotFound: 'Code not found', + wrongCode: 'Wrong code', + failedToProvideCode: 'Failed to deliver code to the client', +}; diff --git a/packages/code/src/index.ts b/packages/code/src/index.ts new file mode 100644 index 000000000..dc09c99ad --- /dev/null +++ b/packages/code/src/index.ts @@ -0,0 +1,5 @@ +import AccountsCode from './accounts-code'; +export * from './types'; + +export default AccountsCode; +export { AccountsCode }; diff --git a/packages/code/src/types/code-generator.ts b/packages/code/src/types/code-generator.ts new file mode 100644 index 000000000..05f807b61 --- /dev/null +++ b/packages/code/src/types/code-generator.ts @@ -0,0 +1,3 @@ +export interface CodeGenerator { + generate(): Promise; +} diff --git a/packages/code/src/types/code-hash.ts b/packages/code/src/types/code-hash.ts new file mode 100644 index 000000000..ebd264d2d --- /dev/null +++ b/packages/code/src/types/code-hash.ts @@ -0,0 +1,5 @@ +export interface CodeHash { + hash(code: string): string; + + verify(code: string, hash: string): boolean; +} diff --git a/packages/code/src/types/code-login-type.ts b/packages/code/src/types/code-login-type.ts new file mode 100644 index 000000000..4b194c893 --- /dev/null +++ b/packages/code/src/types/code-login-type.ts @@ -0,0 +1,4 @@ +export interface CodeLoginType { + serviceId: string; + code: string; +} diff --git a/packages/code/src/types/code-provider.ts b/packages/code/src/types/code-provider.ts new file mode 100644 index 000000000..6bec16b3d --- /dev/null +++ b/packages/code/src/types/code-provider.ts @@ -0,0 +1,3 @@ +export interface CodeProvider { + sendToClient(serviceId: string, code: string): Promise; +} diff --git a/packages/code/src/types/error-messages.ts b/packages/code/src/types/error-messages.ts new file mode 100644 index 000000000..4bcb6b892 --- /dev/null +++ b/packages/code/src/types/error-messages.ts @@ -0,0 +1,7 @@ +export interface ErrorMessages { + userNotFound: string; + codeWasNotFound: string; + wrongCode: string; + codeExpired: string; + failedToProvideCode: string; +} diff --git a/packages/code/src/types/index.ts b/packages/code/src/types/index.ts new file mode 100644 index 000000000..6de31d741 --- /dev/null +++ b/packages/code/src/types/index.ts @@ -0,0 +1,5 @@ +export { ErrorMessages } from './error-messages'; +export { CodeGenerator } from './code-generator'; +export { CodeProvider } from './code-provider'; +export { CodeHash } from './code-hash'; +export { CodeLoginType } from './code-login-type'; diff --git a/packages/code/src/utils/randomstring-code-generator.ts b/packages/code/src/utils/randomstring-code-generator.ts new file mode 100644 index 000000000..b9e97a27a --- /dev/null +++ b/packages/code/src/utils/randomstring-code-generator.ts @@ -0,0 +1,17 @@ +import * as randomstring from 'randomstring'; + +import { CodeGenerator } from '../types'; + +export type RandomstringOptions = randomstring.GenerateOptions; + +export default class RandomstringCodeGenerator implements CodeGenerator { + private options?: RandomstringOptions; + + constructor(options?: RandomstringOptions) { + this.options = options; + } + + public async generate(): Promise { + return randomstring.generate(this.options); + } +} diff --git a/packages/code/src/utils/simple-code-hash.ts b/packages/code/src/utils/simple-code-hash.ts new file mode 100644 index 000000000..e9a83750b --- /dev/null +++ b/packages/code/src/utils/simple-code-hash.ts @@ -0,0 +1,22 @@ +import * as crypto from 'crypto'; + +import { CodeHash } from '../types'; + +export default class SimpleCodeHash implements CodeHash { + public hash(code: string): string { + return this._hash(code); + } + + public verify(code: string, hash: string): boolean { + const hashedCode = this._hash(code); + + return hashedCode === hash; + } + + private _hash(code: string): string { + const hash = crypto.createHash('sha256'); + hash.update(code); + + return hash.digest('base64'); + } +} diff --git a/packages/code/tsconfig.json b/packages/code/tsconfig.json new file mode 100644 index 000000000..4ec56d0f8 --- /dev/null +++ b/packages/code/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "importHelpers": true + }, + "exclude": ["node_modules", "__tests__", "lib"] +} diff --git a/website/docs/strategies/code.md b/website/docs/strategies/code.md new file mode 100644 index 000000000..ecc69c79a --- /dev/null +++ b/website/docs/strategies/code.md @@ -0,0 +1,81 @@ +--- +title: Code +--- + +[Github](https://github.com/accounts-js/accounts/tree/master/packages/code) | +[npm](https://www.npmjs.com/package/@accounts/code) + +The `@accounts/code` package provide a secure system for a code based login strategy. The code is being created prior to the login request and sent to the client via a provider (SMS provider for example) + +## Install + +``` +# With yarn +yarn add @accounts/code + +# Or if you use npm +npm install @accounts/code --save +``` + +A provider is also needed. You can use `accounts` providers or create your own. + +## Usage + +```javascript +import AccountsServer from '@accounts/server'; +import AccountsCode from '@accounts/code'; + +// We create a new code instance with a code provider and extra configuration +const accountsCode = new AccountsCode(provider, ...config); + +// We pass the code instance the AccountsServer service list +const accountsServer = new AccountsServer(...config, { + code: accountsCode, +}); +``` + +## Providers + +### twilio + +#### Install + +``` +# With yarn +yarn add @accounts/code-provider-twilio + +# Or if you use npm +npm install @accounts/code-provider-twilio --save +``` + +#### Usage + +```js +import { AccountsServer } from '@accounts/server'; +import { AccountsCode } from '@accounts/code'; +import { CodeProviderTwilio } from '@accounts/code-provider-twilio'; + +const codeProvider = new CodeProviderTwilio({ + sid: 'TWILIO_SID', + secret: 'TWILIO_SECRET', + phoneNumber: 'TWILIO_FROM_PHONE_NUMBER', +}); + +export const accountsCode = new AccountsCode({ + codeProvider, + // options +}); + +const accountsServer = new AccountsServer( + { + // options + }, + { + code: accountsCode, + } +); +``` + +## Create a custom provider + +In order to create a custom code provider you need to implement the `CodeProvider` interface from `@accounts/code`. diff --git a/yarn.lock b/yarn.lock index fd94449ba..b54721c21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2274,7 +2274,7 @@ "@types/express" "*" "@types/node" "*" -"@types/express@*", "@types/express@4.17.1": +"@types/express@*", "@types/express@4.17.1", "@types/express@^4.16.1": version "4.17.1" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.1.tgz#4cf7849ae3b47125a567dfee18bfca4254b88c5c" integrity sha512-VfH/XCP0QbQk5B5puLqTLEeFgR8lfCJHZJKkInZ9mkYd+u8byX0kztXEQxEk4wZXJs8HI+7km2ALXjn4YKcX9w== @@ -2356,6 +2356,13 @@ resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89" integrity sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA== +"@types/jest@24.0.16": + version "24.0.16" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.16.tgz#8d3e406ec0f0dc1688d6711af3062ff9bd428066" + integrity sha512-JrAiyV+PPGKZzw6uxbI761cHZ0G7QMOHXPhtSpcl08rZH6CswXaaejckn3goFKmF7M3nzEoJ0lwYCbqLMmjziQ== + dependencies: + "@types/jest-diff" "*" + "@types/jest@24.0.18": version "24.0.18" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.18.tgz#9c7858d450c59e2164a8a9df0905fc5091944498" @@ -2404,6 +2411,11 @@ "@types/koa-compose" "*" "@types/node" "*" +"@types/lodash@4.14.136": + version "4.14.136" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.136.tgz#413e85089046b865d960c9ff1d400e04c31ab60f" + integrity sha512-0GJhzBdvsW2RUccNHOBkabI8HZVdOXmXbXhuKlDEd5Vv12P7oAVGfomGp3Ne21o5D/qu1WmthlNKFaoZJJeErA== + "@types/lodash@4.14.138", "@types/lodash@^4.14.138": version "4.14.138" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.138.tgz#34f52640d7358230308344e579c15b378d91989e" @@ -2432,6 +2444,14 @@ "@types/bson" "*" "@types/node" "*" +"@types/mongoose@5.5.11": + version "5.5.11" + resolved "https://registry.yarnpkg.com/@types/mongoose/-/mongoose-5.5.11.tgz#8562bb84b4f3f41aebec27f263607bfe2183729e" + integrity sha512-Z1W2V3zrB+SeDGI6G1G5XR3JJkkMl4ni7a2Kmq10abdY0wapbaTtUT2/31N+UTPEzhB0KPXUgtQExeKxrc+hxQ== + dependencies: + "@types/mongodb" "*" + "@types/node" "*" + "@types/mongoose@5.5.17": version "5.5.17" resolved "https://registry.yarnpkg.com/@types/mongoose/-/mongoose-5.5.17.tgz#1f8eb3799368ae266758d2df1bd1a7cfca0f6875" @@ -2486,6 +2506,11 @@ dependencies: "@types/react" "*" +"@types/randomstring@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@types/randomstring/-/randomstring-1.1.6.tgz#45cdc060a6f043d610bcd46503a6887db2a209c3" + integrity sha512-XRIZIMTxjcUukqQcYBdpFWGbcRDyNBXrvTEtTYgFMIbBNUVt+9mCKsU+jUUDLeFO/RXopUgR5OLiBqbY18vSHQ== + "@types/range-parser@*": version "1.2.3" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" @@ -3500,6 +3525,11 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array-uniq@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.2.tgz#5fcc373920775723cfd64d65c64bef53bf9eba6d" + integrity sha1-X8w3OSB3VyPP1k1lxkvvU7+eum0= + array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -5637,6 +5667,11 @@ depd@~2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +deprecate@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/deprecate/-/deprecate-1.0.0.tgz#661490ed2428916a6c8883d8834e5646f4e4a4a8" + integrity sha1-ZhSQ7SQokWpsiIPYg05WRvTkpKg= + deprecated-decorator@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" @@ -10341,6 +10376,11 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== +moment@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" + integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== + mongodb-core@3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-3.2.7.tgz#a8ef1fe764a192c979252dacbc600dc88d77e28f" @@ -11645,6 +11685,11 @@ pnp-webpack-plugin@1.5.0: dependencies: ts-pnp "^1.1.2" +pop-iterate@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pop-iterate/-/pop-iterate-1.0.1.tgz#ceacfdab4abf353d7a0f2aaa2c1fc7b3f9413ba3" + integrity sha1-zqz9q0q/NT16DyqqLB/Hs/lBO6M= + popper.js@^1.14.1: version "1.15.0" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2" @@ -12581,6 +12626,15 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +q@2.0.x: + version "2.0.3" + resolved "https://registry.yarnpkg.com/q/-/q-2.0.3.tgz#75b8db0255a1a5af82f58c3f3aaa1efec7d0d134" + integrity sha1-dbjbAlWhpa+C9Yw/Oqoe/sfQ0TQ= + dependencies: + asap "^2.0.0" + pop-iterate "^1.0.1" + weak-map "^1.0.5" + q@^1.1.2, q@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -12661,6 +12715,13 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" +randomstring@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/randomstring/-/randomstring-1.1.5.tgz#6df0628f75cbd5932930d9fe3ab4e956a18518c3" + integrity sha1-bfBij3XL1ZMpMNn+OrTpVqGFGMM= + dependencies: + array-uniq "1.0.2" + range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -13460,6 +13521,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +rootpath@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/rootpath/-/rootpath-0.1.2.tgz#5b379a87dca906e9b91d690a599439bef267ea6b" + integrity sha1-Wzeah9ypBum5HWkKWZQ5vvJn6ms= + rsvp@^4.8.4: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -13593,6 +13659,11 @@ schema-utils@^2.0.0, schema-utils@^2.0.1: ajv "^6.10.2" ajv-keywords "^3.4.1" +scmp@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/scmp/-/scmp-2.0.0.tgz#247110ef22ccf897b13a3f0abddb52782393cd6a" + integrity sha1-JHEQ7yLM+JexOj8KvdtSeCOTzWo= + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -14810,6 +14881,22 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +twilio@^3.33.3: + version "3.33.3" + resolved "https://registry.yarnpkg.com/twilio/-/twilio-3.33.3.tgz#ad610c19914ba8f9950b672c62eddd69b6349b54" + integrity sha512-xw/NjPXJ7JoeDSC1Eq6rkxlRdgJhiLeE8BM2KeI14g9ir5Hb1ui9YKYacqnnv/Hf9ZgLvtz4O/HWXzRItg7kqA== + dependencies: + "@types/express" "^4.16.1" + deprecate "1.0.0" + jsonwebtoken "^8.5.1" + lodash "^4.17.11" + moment "^2.24.0" + q "2.0.x" + request "^2.88.0" + rootpath "0.1.2" + scmp "2.0.0" + xmlbuilder "9.0.1" + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -15262,6 +15349,11 @@ wcwidth@^1.0.0: dependencies: defaults "^1.0.3" +weak-map@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/weak-map/-/weak-map-1.0.5.tgz#79691584d98607f5070bd3b70a40e6bb22e401eb" + integrity sha1-eWkVhNmGB/UHC9O3CkDmuyLkAes= + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -15757,6 +15849,11 @@ xml2js@^0.4.17: util.promisify "~1.0.0" xmlbuilder "~11.0.0" +xmlbuilder@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.1.tgz#91cd70897755363eba57c12ddeeab4a341a61f65" + integrity sha1-kc1wiXdVNj66V8Et3uq0o0GmH2U= + xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"