Advertisement
Guest User

Untitled

a guest
Oct 1st, 2017
100
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.86 KB | None | 0 0
  1. /* @flow */
  2. import PromiseQueue from 'promise-queue-observable';
  3.  
  4. import { config as AWSConfig, CognitoIdentityCredentials, CognitoSyncManager } from 'aws-sdk';
  5. import 'amazon-cognito-js';
  6.  
  7. import {
  8. CognitoUserPool,
  9. CognitoUser,
  10. AuthenticationDetails,
  11. CognitoUserAttribute,
  12. } from 'amazon-cognito-identity-js';
  13.  
  14. export type CognitoAuthenticator$Config = {
  15. log: boolean | 'warn' | 'error' | 'debug',
  16. };
  17.  
  18. const tryJSONParse = (val: mixed): Object =>
  19. typeof val === 'object'
  20. ? val
  21. : // $FlowIgnore
  22. do {
  23. let v;
  24. try {
  25. v = JSON.parse(val);
  26. } catch (e) {
  27. v = val;
  28. } finally {
  29. v; // eslint-disable-line
  30. }
  31. };
  32.  
  33. export const toAWSAttribute = {
  34. userLocale: 'locale',
  35. userEmail: 'email',
  36. firstName: 'name',
  37. lastName: 'family_name',
  38. username: 'preferred_username',
  39. userPhoneNumber: 'phone_number',
  40. userAddress: 'address',
  41. phoneVerified: 'phone_number_verified',
  42. emailVerified: 'email_verified',
  43. dealerIdentityID: 'custom:dealerIdentityID',
  44. company: 'custom: company',
  45. latitude: 'custom:company_latitude',
  46. longitude: 'custom:company_longitude',
  47. };
  48.  
  49. export const fromAWSAttribute = {
  50. email: 'userEmail',
  51. name: 'firstName',
  52. family_name: 'lastName',
  53. locale: 'userLocale',
  54. preferred_username: 'username',
  55. phone_number: 'userPhoneNumber',
  56. email_verified: 'emailVerified',
  57. phone_number_verified: 'phoneVerified',
  58. address: 'userAddress',
  59. 'custom:dealerIdentityID': 'dealerIdentityID',
  60. 'custom:company': 'company',
  61. 'custom:company_latitude': 'companyLat',
  62. 'custom:company_longitude': 'companyLon',
  63. };
  64.  
  65. let Authenticator;
  66.  
  67. const handleSynchronizeUserData = (dataManager /* , initialRecords */) =>
  68. new Promise((resolve, reject) => {
  69. dataManager.synchronize({
  70. onSuccess(dataset, newRecords) {
  71. // if (observer) {
  72. // observer.publish('data_sync', 'success', dataset, newRecords);
  73. // }
  74. resolve(['success', dataset, newRecords]);
  75. },
  76. onFailure(error) {
  77. // console.error('DataSync Error: ', error.message);
  78. // if (observer) {
  79. // observer.publish('data_sync', 'error', error.message);
  80. // }
  81. resolve(['error', error]);
  82. },
  83. onConflict(dataset, conflicts, callback) {
  84. console.warn('Dataset Conflict! ', conflicts);
  85. // if (observer) {
  86. // observer.publish('data_sync', 'conflict', conflicts, callback);
  87. // }
  88. resolve(['conflict', dataset, conflicts, callback]);
  89. },
  90. observersetDeleted(dataset, datasetName, callback) {
  91. // console.info('Dataset Deleted: ', datasetName);
  92. // observer.publish('data_sync', 'deleted', datasetName);
  93. resolve(['deleted', dataset, datasetName, callback]);
  94. return callback(true);
  95. },
  96. observersetMerged(dataset, datasetNames, callback) {
  97. // console.info('Dataset Merged: ', datasetNames);
  98. // observer.publish('data_sync', 'merged', datasetNames);
  99. resolve(['merged', dataset, datasetNames, callback]);
  100. return callback(true);
  101. },
  102. });
  103. });
  104.  
  105. const handleFormatDataRecords = records =>
  106. records.reduce((p, c) => {
  107. p[c.key] = tryJSONParse(c.value);
  108. return p;
  109. }, {});
  110.  
  111. const handleGetAllRecords = dataManager =>
  112. new Promise((resolve, reject) => {
  113. handleSynchronizeUserData(dataManager)
  114. .then(([result, ...args]) =>
  115. dataManager.getAllRecords((err, rawRecords) => {
  116. if (err) {
  117. console.error('Failed to Get All User Records: ', err.message);
  118. return reject('Failed to Get User Data Records');
  119. }
  120.  
  121. const dealerIdentityID = dataManager.getIdentityId();
  122.  
  123. const records = {
  124. dealerIdentityID,
  125. data: handleFormatDataRecords(rawRecords),
  126. manager: dataManager,
  127. sync: () => handleSynchronizeUserData(dataManager),
  128. getAll: () => handleGetAllRecords(dataManager),
  129. };
  130.  
  131. return resolve(records);
  132. }),
  133. )
  134. .catch(err => {
  135. console.error('Failed to Get User Recors: ', err);
  136. });
  137. });
  138.  
  139. const getCredentials = (Username: string, Password: string): Class<AuthenticationDetails> =>
  140. new AuthenticationDetails({
  141. Username,
  142. Password,
  143. });
  144.  
  145. const parseUserAttributes = attributes =>
  146. attributes.reduce((p, c = {}) => {
  147. const { Name, Value } = c;
  148. if (fromAWSAttribute[Name]) {
  149. p[fromAWSAttribute[Name]] = Value;
  150. } else {
  151. p[Name] = Value;
  152. }
  153. return p;
  154. }, {});
  155.  
  156. const formatUserAttributes = attributes => {
  157. const formatted = [];
  158. for (const name of Object.keys(attributes)) {
  159. formatted.push(
  160. new CognitoUserAttribute({
  161. Name: toAWSAttribute[name] || name,
  162. Value: attributes[name],
  163. }),
  164. );
  165. }
  166. return formatted;
  167. };
  168.  
  169. const getSessionIDToken = (session): string => session.getIdToken().getJwtToken();
  170.  
  171. function publishConfirmedRegistrationCode(user, code: string, observer) {
  172. // console.info('Submitting User Registration Code: ', code);
  173. return user.confirmRegistration(String(code), true, (err, result) => {
  174. if (err) {
  175. console.error('Failed to Publish Confirmed Registration Code: ', err.message);
  176. observer.publish('error', err.message);
  177. } else {
  178. observer.publish('success', result);
  179. }
  180. observer.cancel();
  181. });
  182. }
  183.  
  184. class CognitoAuthenticator {
  185. constructor(config?: CognitoAuthenticator$Config = {}) {
  186. this.config = {
  187. region: 'us-west-2',
  188. ...config,
  189. };
  190. AWSConfig.region = this.config.region;
  191. this.setDefaults();
  192. }
  193.  
  194. setDefaults = (): void => {
  195. this.state = {
  196. pool: undefined,
  197. mfa: undefined,
  198. };
  199. // AWS Classes / Instances
  200. this.aws = {
  201. user: undefined,
  202. userPool: undefined,
  203. session: undefined,
  204. identity: undefined,
  205. sync: undefined,
  206. dataManager: undefined,
  207. };
  208. };
  209.  
  210. setPool = (params): Class<CognitoAuthenticator> => {
  211. const { ClientId, UserPoolId } = params;
  212. this.state.pool = params;
  213. if (ClientId && UserPoolId) {
  214. this.aws.userPool = new CognitoUserPool(params);
  215. }
  216. return this;
  217. };
  218.  
  219. setIdentity = (params): Class<CognitoAuthenticator> => {
  220. this.state.identity = params;
  221. return this;
  222. };
  223.  
  224. refreshUserFromStorage = (): Class<CognitoAuthenticator> => {
  225. if (this.aws.userPool) {
  226. const user = this.aws.userPool.getCurrentUser();
  227. if (user) {
  228. this.aws.user = user;
  229. }
  230. }
  231. return this;
  232. };
  233.  
  234. setUser = (Username: string): Class<CognitoAuthenticator> => {
  235. if (!this.aws.userPool) {
  236. throw new Error('You must setup the User Pool before setting the user!');
  237. } else {
  238. this.aws.user = new CognitoUser({
  239. Username,
  240. Pool: this.aws.userPool,
  241. });
  242. }
  243. return this;
  244. };
  245.  
  246. setSession = (session): Class<CognitoAuthenticator> => {
  247. if (!session.isValid()) {
  248. throw new Error('Tried to set an invalid Session on Authenticator');
  249. }
  250. this.aws.session = session;
  251. return this;
  252. };
  253.  
  254. setPassword = (prevPassword: string, newPassword: string): Promise<*> =>
  255. new Promise((resolve, reject) =>
  256. this.refreshSessionIfNeeded().then(() =>
  257. this.getUser().changePassword(prevPassword, newPassword, (err, result) => {
  258. if (err) {
  259. console.error('Failed to Set User Password: ', err.message);
  260. return reject(err.message);
  261. }
  262. return resolve(result);
  263. }),
  264. ),
  265. );
  266.  
  267. getPool = (required?: boolean = true) => {
  268. if (this.aws.userPool) {
  269. return this.aws.userPool;
  270. }
  271. if (required) {
  272. throw new Error('Attempted to Get User Pool before it was setup!');
  273. }
  274. };
  275.  
  276. getUser = (required?: boolean = true) => {
  277. if (this.aws.user) {
  278. return this.aws.user;
  279. }
  280. if (required) {
  281. throw new Error('Attempted to Get User before it was setup!');
  282. }
  283. };
  284.  
  285. getUsername = (required?: boolean = true) => {
  286. if (this.aws.user) {
  287. return this.aws.user.getUsername();
  288. }
  289. if (required) {
  290. throw new Error('Attempted to Get Username before it was setup!');
  291. }
  292. };
  293.  
  294. getSession = (required?: boolean = true) => {
  295. if (this.aws.session && typeof this.aws.session.isValid === 'function') {
  296. return this.aws.session;
  297. }
  298. if (required) {
  299. throw new Error('Attempted to Get User Session before it was setup!');
  300. }
  301. };
  302.  
  303. getRefreshToken = (): string => this.getSession().getRefreshToken();
  304.  
  305. getAccessToken = (): string => getSessionIDToken(this.getSession());
  306.  
  307. logout = (): Class<CognitoAuthenticator> => {
  308. const user = this.getUser(false);
  309. if (user) {
  310. user.signOut();
  311. }
  312. const userPool = this.aws.userPool;
  313. const poolParams = this.state.pool;
  314.  
  315. this.setDefaults();
  316.  
  317. // dont reset the userPool on logout
  318. this.aws.userPool = userPool;
  319. this.state.pool = poolParams;
  320.  
  321. clearAWSCredentialsCache();
  322. return this;
  323. };
  324.  
  325. startSignup = (username: string, password: string, attributes: Object) => {
  326. const observer = new PromiseQueue();
  327.  
  328. this.getPool().signUp(username, password, attributes, null, (err, result) => {
  329. if (err) {
  330. console.error('Signup Error: ', err.message);
  331. observer.publish('error', err, this);
  332. } else {
  333. this.setUser(username);
  334. const callback = code => publishConfirmedRegistrationCode(this.getUser(), code, observer);
  335. observer.publish('verify', result, callback, this);
  336. }
  337. });
  338.  
  339. return { observer };
  340. };
  341.  
  342. startLogin = (username: string, password: string) => {
  343. // console.log('Start Login: ', username, password);
  344. const observer = new PromiseQueue();
  345.  
  346. const credentials = getCredentials(username, password);
  347.  
  348. clearAWSCredentialsCache();
  349.  
  350. const onSuccess = session => {
  351. observer.publish('success', this.setSession(session).getSession(), this);
  352. observer.cancel();
  353. };
  354.  
  355. const onFailure = err => {
  356. console.error('Failed to Login User: ', err.message);
  357. observer.publish('error', err, this);
  358. observer.cancel();
  359. };
  360.  
  361. const mfaRequired = data => {
  362. console.info('Multi-Factor Validation Required: ', data);
  363. const callback = code => {
  364. const user = this.getUser();
  365. user.sendMFACode(String(code), { onSuccess, onFailure, newPasswordRequired });
  366. };
  367. observer.publish('verify', data, callback, this);
  368. };
  369.  
  370. const newPasswordRequired = (userAttributes, requiredAttributes) => {
  371. const data = { userAttributes, requiredAttributes };
  372. // User was signed up by an admin and must provide new
  373. // password and required attributes, if any, to complete
  374. // authentication.
  375. console.log('New Password Required');
  376. // the api doesn't accept this field back
  377. delete userAttributes.email_verified;
  378.  
  379. const callback = newPassword => {
  380. this.getUser().completeNewPasswordChallenge(newPassword, userAttributes, {
  381. onSuccess,
  382. onFailure,
  383. mfaRequired,
  384. });
  385. };
  386.  
  387. observer.publish('reset_password', data, callback, this);
  388. };
  389.  
  390. this.setUser(username)
  391. .getUser()
  392. .authenticateUser(credentials, {
  393. onSuccess,
  394. onFailure,
  395. mfaRequired,
  396. newPasswordRequired,
  397. });
  398.  
  399. return { observer };
  400. };
  401.  
  402. startForgotPassword = (username: string) => {
  403. const observer = new PromiseQueue();
  404. this.setUser(username);
  405. const onSuccess = () => {
  406. // console.info('Forgot Password Success!');
  407. observer.publish('success', this);
  408. observer.cancel();
  409. };
  410.  
  411. const onFailure = err => {
  412. console.error('Failed to Reset Password: ', err.message);
  413. observer.publish('error', err, this);
  414. observer.cancel();
  415. };
  416.  
  417. const inputVerificationCode = data => {
  418. console.info('Forgot Info Sent, Verification Required: ', data);
  419. const callback = (code, newPassword) => {
  420. this.getUser().confirmPassword(String(code), newPassword, {
  421. onSuccess,
  422. onFailure,
  423. });
  424. };
  425. observer.publish('verify', data, callback);
  426. };
  427.  
  428. this.setUser(username)
  429. .getUser()
  430. .forgotPassword({
  431. onSuccess,
  432. onFailure,
  433. inputVerificationCode,
  434. });
  435.  
  436. return { observer };
  437. };
  438.  
  439. startSetUserAttributes = (attributes: Object) => {
  440. const observer = new PromiseQueue();
  441.  
  442. const onSuccess = result => {
  443. observer.publish('success', result);
  444. observer.cancel();
  445. };
  446.  
  447. const onFailure = err => {
  448. console.error('Failed to Set User Attributes: ', err.message);
  449. observer.publish('error', err);
  450. observer.cancel();
  451. return err;
  452. };
  453.  
  454. const inputVerificationCode = async result => {
  455. const deliveries = result.CodeDeliveryDetailsList;
  456. while (deliveries.length > 0) {
  457. const delivery = deliveries.shift();
  458. // eslint-disable-next-line
  459. await new Promise((resolve, reject) => {
  460. const callback = code => {
  461. this.getUser().verifyAttribute(delivery.AttributeName, String(code), {
  462. onSuccess: success => {
  463. console.log('Attribute Verified: ', success);
  464. if (deliveries.length === 0) {
  465. resolve(onSuccess(success));
  466. } else {
  467. resolve();
  468. }
  469. },
  470. onFailure: err => {
  471. reject(onFailure(err));
  472. },
  473. });
  474. };
  475. // simulate the same style that is used for standard verification of
  476. // attributes.
  477. observer.publish('verify', { CodeDeliveryDetails: delivery }, callback);
  478. // finish promise
  479. });
  480. // handle next delivery
  481. }
  482. };
  483.  
  484. // eslint-disable-next-line
  485. this.refreshSessionIfNeeded().then(() => {
  486. const formatted = formatUserAttributes(attributes);
  487. if (formatted.length === 0) {
  488. console.error('Formatted Attributes was Empty');
  489. observer.publish('error', 'Formatted Attributes was Empty');
  490. return observer.cancel();
  491. }
  492.  
  493. return this.getUser().updateAttributes(formatted, {
  494. onSuccess,
  495. onFailure,
  496. inputVerificationCode,
  497. });
  498. });
  499.  
  500. return { observer };
  501. };
  502.  
  503. startVerifyAttribute = (attribute?: string = 'email') => {
  504. const observer = new PromiseQueue();
  505.  
  506. const onSuccess = result => {
  507. observer.publish('success', result);
  508. observer.cancel();
  509. };
  510.  
  511. const onFailure = err => {
  512. observer.publish('error', err);
  513. observer.cancel();
  514. };
  515.  
  516. const inputVerificationCode = result => {
  517. const callback = code => {
  518. if (code) {
  519. return this.getUser().verifyAttribute(attribute, String(code), {
  520. onSuccess,
  521. onFailure,
  522. });
  523. }
  524. return observer.cancel();
  525. };
  526. observer.publish('verify', result, callback);
  527. };
  528.  
  529. this.refreshSessionIfNeeded()
  530. .then(() =>
  531. this.getUser().getAttributeVerificationCode(attribute, {
  532. onSuccess,
  533. onFailure,
  534. inputVerificationCode,
  535. }),
  536. )
  537. .catch(err => observer.publish('error', err));
  538.  
  539. return { observer };
  540. };
  541.  
  542. refreshUserAttributes = (): Promise<*> =>
  543. new Promise((resolve, reject) =>
  544. this.refreshSessionIfNeeded().then(() =>
  545. this.getUser().getUserAttributes((err, attributes) => {
  546. if (err) {
  547. console.error('[getUserAttributes] Failed: ', err.message);
  548. return reject(err.message);
  549. }
  550. resolve(parseUserAttributes(attributes));
  551. }),
  552. ),
  553. );
  554.  
  555. refreshUserSession = (force?: boolean = false): Promise<*> =>
  556. new Promise((resolve, reject) =>
  557. this.getUser().getSession((err, session) => {
  558. if (err) {
  559. console.error('Failed to Refresh User Session from Library: ', err.message);
  560. return reject('Refresh Session Failed');
  561. }
  562. if (force || !session.isValid()) {
  563. // manually set the session then attempt to refresh it
  564. this.aws.session = session;
  565. return resolve(this.refreshSession());
  566. }
  567. resolve(this.setSession(session).getSession());
  568. }),
  569. );
  570.  
  571. refreshSessionIfNeeded = (): Promise<*> =>
  572. new Promise(resolve => {
  573. const session = this.getSession();
  574. if (session.isValid()) {
  575. return resolve(session);
  576. }
  577. return resolve(this.refreshSession());
  578. });
  579.  
  580. refreshSession = (): Promise<*> =>
  581. new Promise((resolve, reject) => {
  582. this.getUser().refreshSession(this.getRefreshToken(), (err, session) => {
  583. if (err) {
  584. console.error('Failed to Refresh User Session: ', err.message);
  585. return reject(err.message);
  586. } else if (!session.isValid()) {
  587. console.error('Refreshed Session is Not Valid!');
  588. return reject('Failed to Refresh Session');
  589. }
  590. return resolve(this.setSession(session).getSession());
  591. });
  592. });
  593.  
  594. refreshUserDevices = (limit?: number = 30, page = null): Promise<*> =>
  595. new Promise((resolve, reject) =>
  596. this.refreshSessionIfNeeded().then(() =>
  597. this.getUser().listDevices(limit, page, {
  598. onSuccess: result => resolve(result),
  599. onFailure: err => {
  600. console.error('Failed to Refresh User Devices: ', err.message);
  601. return reject(err.message);
  602. },
  603. }),
  604. ),
  605. );
  606.  
  607. refreshAccessTokensIfNeeded = (): Promise<string> =>
  608. this.refreshSessionIfNeeded().then(session => getSessionIDToken(session));
  609.  
  610. refreshAccessTokens = (): Promise<string> =>
  611. this.refreshSession().then(session => getSessionIDToken(session));
  612.  
  613. refreshIdentityPool = () => {
  614. const identity = this.state.identity;
  615. if (!identity) {
  616. throw new Error(
  617. 'Tried to refresh Identity Pool before setting its params with setIdentity()!',
  618. );
  619. }
  620. return this.refreshSession()
  621. .then(session => {
  622. const token = getSessionIDToken(session);
  623. const userPool = this.getPool();
  624. const { region } = this.config;
  625. this.aws.identity = new CognitoIdentityCredentials({
  626. ...identity,
  627. Logins: {
  628. [`cognito-idp.${region}.amazonaws.com/${userPool.getUserPoolId()}`]: token,
  629. },
  630. });
  631. AWSConfig.credentials = this.aws.identity;
  632. return AWSConfig.credentials.refreshPromise();
  633. })
  634. .catch(err => {
  635. console.error('Failed to Refresh Identity Pool: ', err.message);
  636. throw err;
  637. });
  638. };
  639.  
  640. refreshIdentityPoolIfNeeded = () => {
  641. const identity = this.state.identity;
  642. if (!identity) {
  643. throw new Error(
  644. 'Tried to refresh Identity Pool before setting its params with setIdentity()!',
  645. );
  646. }
  647. return this.refreshSessionIfNeeded()
  648. .then(session => {
  649. const token = getSessionIDToken(session);
  650. const userPool = this.getPool();
  651. const { region } = this.config;
  652. this.aws.identity = new CognitoIdentityCredentials({
  653. ...identity,
  654. Logins: {
  655. [`cognito-idp.${region}.amazonaws.com/${userPool.getUserPoolId()}`]: token,
  656. },
  657. });
  658. AWSConfig.credentials = this.aws.identity;
  659. return AWSConfig.credentials.refreshPromise();
  660. })
  661. .catch(err => {
  662. console.error('Failed to Refresh Identity Pool: ', err.message);
  663. throw err;
  664. });
  665. };
  666.  
  667. removeUserDevice = (deviceKey: string): Promise<string> =>
  668. new Promise((resolve, reject) =>
  669. this.refreshSessionIfNeeded().then(() =>
  670. this.getUser().forgetSpecificDevice(deviceKey, {
  671. onFailure: err => reject(err.message),
  672. onSuccess: () => resolve(deviceKey),
  673. }),
  674. ),
  675. );
  676.  
  677. refreshUserDataSync = (dataset?: string = 'settings', force?: boolean = false): Promise<*> =>
  678. new Promise((resolve, reject) => {
  679. const getAWSCredentials = () =>
  680. AWSConfig.credentials.get(() => {
  681. this.aws.sync = new CognitoSyncManager();
  682. return this.aws.sync.openOrCreateDataset(dataset, (err, dataManager) => {
  683. if (err) {
  684. console.error('Failed to Open User Data Set: ', dataset, err.message);
  685. return reject('Failed to Open UserData');
  686. }
  687. console.log(dataManager);
  688. this.aws.dataManager = dataManager;
  689. return resolve(handleGetAllRecords(dataManager));
  690. });
  691. });
  692. if (force) {
  693. return this.refreshIdentityPool().then(() => getAWSCredentials());
  694. }
  695. return this.refreshIdentityPoolIfNeeded().then(() => getAWSCredentials());
  696. });
  697. }
  698.  
  699. export function clearAWSCredentialsCache(): void {
  700. if (AWSConfig.credentials && AWSConfig.credentials.clearCachedId) {
  701. AWSConfig.credentials.clearCachedId();
  702. }
  703. }
  704.  
  705. export function createCognitoAuthenticator(
  706. config?: CognitoAuthenticator$Config,
  707. ): Class<CognitoAuthenticator> {
  708. Authenticator = new CognitoAuthenticator(config);
  709. return Authenticator;
  710. }
  711.  
  712. export function getCognitoAuthenticator(
  713. config?: CognitoAuthenticator$Config,
  714. ): Class<CognitoAuthenticator> {
  715. if (!Authenticator && config) {
  716. return createCognitoAuthenticator(config);
  717. }
  718. return Authenticator;
  719. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement