├── .babelrc ├── .coveralls.yml ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── bootstrap-theme.js ├── default-theme.js ├── docs ├── api-expectations │ ├── destroy-account.md │ ├── email-sign-in.md │ ├── email-sign-up.md │ ├── oauth-sign-in.md │ ├── request-password-reset.md │ ├── sign-out.md │ ├── token-management.md │ ├── token-validation.md │ └── update-password.md ├── diagrams │ ├── diagram-destroy-account.graffle │ ├── diagram-email-sign-in.graffle │ ├── diagram-email-sign-up.graffle │ ├── diagram-oauth.graffle │ ├── diagram-password-reset.graffle │ ├── diagram-sign-out.graffle │ ├── diagram-token-validation.graffle │ └── diagram-update-password.graffle ├── gifs │ ├── destroy-account.gif │ ├── email-sign-in.gif │ ├── email-sign-up.gif │ ├── oauth-sign-in.gif │ ├── request-password-reset.gif │ ├── sign-out.gif │ └── update-password.gif ├── images │ ├── bs-destroy-account.png │ ├── bs-email-sign-in.png │ ├── bs-email-sign-up.png │ ├── bs-oauth-sign-in.png │ ├── bs-password-reset.png │ ├── bs-sign-out.png │ ├── bs-update-password.png │ ├── default-destroy-account.png │ ├── default-email-sign-in.png │ ├── default-email-sign-up.png │ ├── default-oauth-sign-in.png │ ├── default-password-reset.png │ ├── default-sign-out.png │ ├── default-update-password.png │ ├── diagram-destroy-account.jpg │ ├── diagram-email-sign-in.jpg │ ├── diagram-email-sign-up.jpg │ ├── diagram-oauth.jpg │ ├── diagram-password-reset.jpg │ ├── diagram-sign-out.jpg │ ├── diagram-token-validation.jpg │ ├── diagram-update-password.jpg │ ├── mui-destroy-account.png │ ├── mui-email-sign-in.png │ ├── mui-email-sign-up.png │ ├── mui-error-dialog.png │ ├── mui-inline-errors.png │ ├── mui-oauth-sign-in.png │ ├── mui-password-reset.png │ ├── mui-sign-out.png │ ├── mui-update-password.png │ └── redux-auth-logo.gif └── psd │ └── redux-auth-logo.psd ├── dummy ├── babel.server.js ├── config │ ├── default.json │ └── production.json ├── package.json ├── src │ ├── actions │ │ ├── demo-ui.js │ │ └── request-test-buttons.js │ ├── app.js │ ├── client.js │ ├── reducers │ │ ├── demo-ui.js │ │ └── request-test-buttons.js │ ├── server.js │ ├── styles │ │ └── main.scss │ └── views │ │ ├── Account.js │ │ ├── Main.js │ │ ├── SignIn.js │ │ └── partials │ │ ├── CodeSnippet.js │ │ ├── Container.js │ │ ├── ExampleWell.js │ │ ├── GlobalComponents.js │ │ ├── IndexPanel.js │ │ ├── RequestTestButton.js │ │ ├── RequestTestErrorModal.js │ │ └── RequestTestSuccessModal.js ├── webpack.client-watch.js └── webpack.client.js ├── index.js ├── material-ui-theme.js ├── package.json ├── src ├── actions │ ├── authenticate.js │ ├── configure.js │ ├── destroy-account.js │ ├── email-sign-in.js │ ├── email-sign-up.js │ ├── oauth-sign-in.js │ ├── request-password-reset.js │ ├── server.js │ ├── sign-out.js │ ├── ui.js │ ├── update-password-modal.js │ └── update-password.js ├── index.js ├── reducers │ ├── authenticate.js │ ├── configure.js │ ├── destroy-account.js │ ├── email-sign-in.js │ ├── email-sign-up.js │ ├── oauth-sign-in.js │ ├── request-password-reset.js │ ├── server.js │ ├── sign-out.js │ ├── ui.js │ ├── update-password-modal.js │ ├── update-password.js │ └── user.js ├── utils │ ├── client-settings.js │ ├── constants.js │ ├── fetch.js │ ├── handle-fetch-response.js │ ├── parse-endpoint-config.js │ ├── parse-url.js │ ├── popup.js │ ├── session-storage.js │ └── verify-auth.js └── views │ ├── TokenBridge.js │ ├── bootstrap │ ├── AuthGlobals.js │ ├── ButtonLoader.js │ ├── DestroyAccountButton.js │ ├── EmailSignInForm.js │ ├── EmailSignUpForm.js │ ├── ErrorList.js │ ├── Input.js │ ├── OAuthSignInButton.js │ ├── RequestPasswordResetForm.js │ ├── SignOutButton.js │ ├── UpdatePasswordForm.js │ ├── index.js │ └── modals │ │ ├── DestroyAccountErrorModal.js │ │ ├── DestroyAccountSuccessModal.js │ │ ├── EmailSignInErrorModal.js │ │ ├── EmailSignInSuccessModal.js │ │ ├── EmailSignUpErrorModal.js │ │ ├── EmailSignUpSuccessModal.js │ │ ├── FirstTimeLoginErrorModal.js │ │ ├── FirstTimeLoginSuccessModal.js │ │ ├── Modal.js │ │ ├── OAuthSignInErrorModal.js │ │ ├── OAuthSignInSuccessModal.js │ │ ├── PasswordResetSuccessModal.js │ │ ├── RequestPasswordResetErrorModal.js │ │ ├── RequestPasswordResetSuccessModal.js │ │ ├── SignOutErrorModal.js │ │ ├── SignOutSuccessModal.js │ │ ├── UpdatePasswordErrorModal.js │ │ └── UpdatePasswordSuccessModal.js │ ├── default │ ├── AuthGlobals.js │ ├── ButtonLoader.js │ ├── DestroyAccountButton.js │ ├── EmailSignInForm.js │ ├── EmailSignUpForm.js │ ├── ErrorList.js │ ├── Input.js │ ├── OAuthSignInButton.js │ ├── RequestPasswordResetForm.js │ ├── SignOutButton.js │ ├── UpdatePasswordForm.js │ ├── index.js │ └── modals │ │ ├── DestroyAccountErrorModal.js │ │ ├── DestroyAccountSuccessModal.js │ │ ├── EmailSignInErrorModal.js │ │ ├── EmailSignInSuccessModal.js │ │ ├── EmailSignUpErrorModal.js │ │ ├── EmailSignUpSuccessModal.js │ │ ├── FirstTimeLoginErrorModal.js │ │ ├── FirstTimeLoginSuccessModal.js │ │ ├── Modal.js │ │ ├── OAuthSignInErrorModal.js │ │ ├── OAuthSignInSuccessModal.js │ │ ├── PasswordResetSuccessModal.js │ │ ├── RequestPasswordResetErrorModal.js │ │ ├── RequestPasswordResetSuccessModal.js │ │ ├── SignOutErrorModal.js │ │ ├── SignOutSuccessModal.js │ │ ├── UpdatePasswordErrorModal.js │ │ └── UpdatePasswordSuccessModal.js │ └── material-ui │ ├── AuthGlobals.js │ ├── ButtonLoader.js │ ├── DestroyAccountButton.js │ ├── EmailSignInForm.js │ ├── EmailSignUpForm.js │ ├── ErrorList.js │ ├── Input.js │ ├── OAuthSignInButton.js │ ├── RequestPasswordResetForm.js │ ├── SignOutButton.js │ ├── UpdatePasswordForm.js │ ├── index.js │ └── modals │ ├── DestroyAccountErrorModal.js │ ├── DestroyAccountSuccessModal.js │ ├── EmailSignInErrorModal.js │ ├── EmailSignInSuccessModal.js │ ├── EmailSignUpErrorModal.js │ ├── EmailSignUpSuccessModal.js │ ├── FirstTimeLoginErrorModal.js │ ├── FirstTimeLoginSuccessModal.js │ ├── Modal.js │ ├── OAuthSignInErrorModal.js │ ├── OAuthSignInSuccessModal.js │ ├── PasswordResetSuccessModal.js │ ├── RequestPasswordResetErrorModal.js │ ├── RequestPasswordResetSuccessModal.js │ ├── SignOutErrorModal.js │ ├── SignOutSuccessModal.js │ ├── UpdatePasswordErrorModal.js │ └── UpdatePasswordSuccessModal.js ├── static └── favicon.ico ├── test ├── actions │ ├── client-config-test.js │ └── server-config-test.js ├── compiler.js ├── helper.js ├── runner.js └── views │ ├── button-loader-test.js │ ├── destroy-account-button-test.js │ ├── email-sign-in-test.js │ ├── email-sign-up-test.js │ ├── modals-test.js │ ├── oauth-sign-in-test.js │ ├── password-reset-test.js │ ├── password-update-modal-test.js │ ├── password-update-test.js │ └── sign-out-test.js └── webpack.release.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: ["es2015", "react", "stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/.coveralls.yml -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = crlf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "mocha": true, 7 | "jest": true 8 | }, 9 | "settings": { 10 | "ecmascript": 7, 11 | "jsx": true 12 | }, 13 | "plugins": [ 14 | "react" 15 | ], 16 | "rules": { 17 | "strict": 0, 18 | "quotes": 1, 19 | "no-unused-vars": 1, 20 | "camelcase": 0, 21 | "no-underscore-dangle": 0, 22 | "no-multi-spaces": 0, 23 | "key-spacing": 0 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | coverage/ 4 | dist/ 5 | build/ 6 | *.log 7 | *.map 8 | *.DS_Store 9 | npm-debug.log 10 | .tern-port 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6.1" 4 | script: npm run test-coverage 5 | before_install: if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v0.0.4](https://github.com/lynndylanhurley/redux-auth/tree/v0.0.3) (2016-07-16) 4 | [Full Changelog](https://github.com/lynndylanhurley/redux-auth/compare/v0.0.3...v0.0.4) 5 | 6 | **New Features** 7 | 8 | - EmailSignUpForm supports `next` function 9 | - OAuthSignUpButton supports `next` function 10 | - SignOutButton supports `next` function 11 | 12 | **Closed issues** 13 | 14 | - [Add `next` param for success redirects.](https://github.com/lynndylanhurley/redux-auth/issues/16) 15 | - [redirect after sign in](https://github.com/lynndylanhurley/redux-auth/issues/12) 16 | 17 | **Merged PRs** 18 | 19 | - [Fix duplicate props on UpdatePasswordForm](https://github.com/lynndylanhurley/redux-auth/pull/60) 20 | 21 | ## [v0.0.3](https://github.com/lynndylanhurley/redux-auth/tree/v0.0.3) (2016-07-16) 22 | [Full Changelog](https://github.com/lynndylanhurley/redux-auth/compare/v0.0.2...v0.0.3) 23 | 24 | **Closed issues** 25 | 26 | - [Client-only (non-universal) support](https://github.com/lynndylanhurley/redux-auth/issues/7) 27 | - [Update for react-router 2.0](https://github.com/lynndylanhurley/redux-auth/issues/15) 28 | - [Doesn't work in internet explorer 11](https://github.com/lynndylanhurley/redux-auth/issues/59) 29 | - [Update react & react-dom to 15.1.0](https://github.com/lynndylanhurley/redux-auth/issues/58) 30 | - [Material theme doesn't work with 0.15 version of material-ui](https://github.com/lynndylanhurley/redux-auth/issues/30) 31 | 32 | **Merged PRs** 33 | 34 | - [Transport bearer token in accordance to RFC 6750](https://github.com/lynndylanhurley/redux-auth/pull/51) 35 | - [Persist credentials](https://github.com/lynndylanhurley/redux-auth/pull/50) 36 | - [pass the click event to the callback](https://github.com/lynndylanhurley/redux-auth/pull/48) 37 | - [updated readme, to include json loader dependency](https://github.com/lynndylanhurley/redux-auth/pull/46) 38 | - [Add support for className property at oAuthSignInButton](https://github.com/lynndylanhurley/redux-auth/pull/45) 39 | - [fix(views/bootstrap): multiple declaration of className](https://github.com/lynndylanhurley/redux-auth/pull/38) 40 | - [Fix webpack config to allow default theme to function](https://github.com/lynndylanhurley/redux-auth/pull/37) 41 | - [Fix misleading info about configure method](https://github.com/lynndylanhurley/redux-auth/pull/35) 42 | - [Support react-router 2](https://github.com/lynndylanhurley/redux-auth/pull/29) 43 | - [Migrate to react-router@2 and react-router-redux](https://github.com/lynndylanhurley/redux-auth/pull/22) 44 | - [Fix semi colon](https://github.com/lynndylanhurley/redux-auth/pull/18) 45 | - [Change history package to 1.17.x to satisfy peer dependencies require...](https://github.com/lynndylanhurley/redux-auth/pull/17) 46 | - [Fix for form submission in Firefox](https://github.com/lynndylanhurley/redux-auth/pull/10) 47 | - [Fix brackets](https://github.com/lynndylanhurley/redux-auth/pull/8) 48 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /docs/api-expectations/destroy-account.md: -------------------------------------------------------------------------------- 1 | # Destroy Account 2 | 3 | * [Overview](#overview) 4 | * [Request](#request) 5 | * [Request Body](#request-body) 6 | * [Request Headers](#request-headers) 7 | * [Success Response](#success-response) 8 | * [Success Response Body](#success-response-body) 9 | * [Success Response Headers](#success-response-headers) 10 | * [Error Response](#error-response) 11 | * [Error Response Body](#error-response-body) 12 | * [Error Response Headers](#error-response-headers) 13 | 14 | ## Overview 15 | 16 | ![destroy account diagram](https://github.com/lynndylanhurley/redux-auth/raw/master/docs/images/diagram-destroy-account.jpg) 17 | 18 | 19 | ## Request 20 | 21 | ##### method 22 | ###### DELETE 23 | 24 | ### Request Body 25 | 26 | None. The API won't care about the body of this request. 27 | 28 | -- 29 | 30 | ### Request Headers 31 | 32 | ##### access-token 33 | ###### string 34 | The access token acts as a password for each request. 35 | 36 | ##### client 37 | ###### string 38 | The client token is used to identify device (browser client, phone, tablet, etc) of the current session. This allows us to maintain multiple concurrent sessions across devices / browsers. 39 | 40 | ##### uid 41 | ###### string 42 | The unique identifier for the current user. 43 | 44 | -- 45 | 46 | ##### Request Headers Example 47 | 48 | ~~~ 49 | access-token: bgINB4atOxd8SMNvtOTDxg 50 | client: V7EN7LSRYAbpE_-c5PvRSw 51 | uid: test@test.com 52 | ~~~ 53 | 54 | -- 55 | 56 | ## Success Response 57 | 58 | ### Success Response Body 59 | 60 | A json object containing the following keys. 61 | 62 | ##### status 63 | ###### string 64 | Will always be `"success"`. 65 | 66 | ##### message 67 | ###### string 68 | A message from the API that will be displayed to the user. 69 | 70 | -- 71 | 72 | ##### Success Response Body Example 73 | 74 | ~~~json 75 | { 76 | "message": "Account with uid 468037 has been destroyed." 77 | "status": "success" 78 | } 79 | ~~~ 80 | 81 | -- 82 | 83 | #### Success Response Headers 84 | 85 | None. The client won't care about any headers for this response. 86 | 87 | -- 88 | 89 | ## Error Response 90 | 91 | This will contain the response status, and an array of any errors that the server encountered in processing the request. 92 | 93 | ### Error Response Body 94 | 95 | ##### status 96 | ###### string 97 | Will always be `"error"`. 98 | 99 | ##### errors 100 | ###### array of strings 101 | A list of errors that will be displayed to the user. 102 | 103 | -- 104 | 105 | ##### Error Response Body Example 106 | ~~~json 107 | { 108 | "status":"error", 109 | "errors":["Unable to locate account for destruction."] 110 | } 111 | ~~~ 112 | 113 | -- 114 | 115 | ### Error Response Headers 116 | None. The client won't care about any headers for this response. 117 | -------------------------------------------------------------------------------- /docs/api-expectations/email-sign-in.md: -------------------------------------------------------------------------------- 1 | # Email Sign In 2 | 3 | * [Overview](#overview) 4 | * [Request](#request) 5 | * [Request Body](#request-body) 6 | * [Request Headers](#request-headers) 7 | * [Success Response](#success-response) 8 | * [Success Response Body](#success-response-body) 9 | * [Success Response Headers](#success-response-headers) 10 | * [Error Response](#error-response) 11 | * [Error Response Body](#error-response-body) 12 | * [Error Response Headers](#error-response-headers) 13 | 14 | ## Overview 15 | 16 | ![email sign in](https://github.com/lynndylanhurley/redux-auth/raw/master/docs/images/diagram-email-sign-in.jpg) 17 | 18 | ## Request 19 | 20 | ##### method 21 | ###### POST 22 | 23 | ### Request Body 24 | 25 | ##### email 26 | ###### string 27 | The email address of the user trying to sign in. 28 | 29 | ##### password 30 | ###### string 31 | The password of the user trying to sign in 32 | 33 | -- 34 | 35 | ##### Request Body Example 36 | 37 | ~~~json 38 | { 39 | "email": "test@test.com", 40 | "password": "secret123" 41 | } 42 | ~~~ 43 | 44 | -- 45 | 46 | ### Request Headers 47 | 48 | None. The API won't care about any headers for this request. 49 | 50 | ## Success Response 51 | 52 | ### Success Response Body 53 | 54 | This be an object containing the attributes that your API is configured to send describing your user. The attributes will be nested within a `data` object. At a minimum, this should contain the following attributes. 55 | 56 | ##### uid 57 | ###### string 58 | The unique identifier for the user's account. 59 | 60 | ##### provider 61 | ###### string 62 | The account provider type (email, github, facebook, etc.). 63 | 64 | -- 65 | 66 | ##### Success Response Body Example 67 | 68 | ~~~json 69 | "data": { 70 | "uid": "test@test.com", 71 | "provider": "email", 72 | "email": "test@test.com", 73 | "favorite_color": null, 74 | "id": 6 75 | } 76 | ~~~ 77 | 78 | -- 79 | 80 | #### Success Response Headers 81 | 82 | ##### access-token 83 | ###### string 84 | The access token acts as a password for each request. 85 | 86 | ##### client 87 | ###### string 88 | The client token is used to identify device (browser client, phone, tablet, etc) of the current session. This allows us to maintain multiple concurrent sessions across devices / browsers. 89 | 90 | ##### expiry 91 | ###### integer 92 | The time at which the token will expire. 93 | 94 | ##### uid 95 | ###### string 96 | The unique identifier for the current user. 97 | 98 | -- 99 | 100 | ##### Success Response Headers Example 101 | 102 | ~~~ 103 | access-token: bgINB4atOxd8SMNvtOTDxg 104 | client: V7EN7LSRYAbpE_-c5PvRSw 105 | expiry: 1450988710 106 | uid: test@test.com 107 | ~~~ 108 | 109 | -- 110 | 111 | ## Error Response 112 | 113 | This will an array containing any errors that the server encountered in processing the request. 114 | 115 | ### Error Response Body 116 | 117 | ##### errors 118 | ###### array of strings 119 | 120 | A list of errors that will be displayed to the user. 121 | 122 | -- 123 | 124 | ##### Error Response Body Example 125 | ~~~json 126 | { 127 | "errors": ["Invalid login credentials. Please try again."] 128 | } 129 | ~~~ 130 | 131 | -- 132 | 133 | ### Error Response Headers 134 | None. The client won't care about any headers for this response. 135 | -------------------------------------------------------------------------------- /docs/api-expectations/email-sign-up.md: -------------------------------------------------------------------------------- 1 | # Email Sign Up 2 | 3 | * [Overview](#overview) 4 | * [Request](#request) 5 | * [Request Body](#request-body) 6 | * [Request Headers](#request-headers) 7 | * [Success Response](#success-response) 8 | * [Success Response Body](#success-response-body) 9 | * [Success Response Headers](#success-response-headers) 10 | * [Error Response](#error-response) 11 | * [Error Response Body](#error-response-body) 12 | * [Error Response Headers](#error-response-headers) 13 | 14 | ## Overview 15 | 16 | ![email sign in](https://github.com/lynndylanhurley/redux-auth/raw/master/docs/images/diagram-email-sign-up.jpg) 17 | 18 | ## Request 19 | 20 | ##### method 21 | ###### POST 22 | 23 | ### Request Params 24 | 25 | ##### config_name 26 | ###### string 27 | The current endpoint config. Will be `default` unless you're using multiple user types. 28 | 29 | ### Request Body 30 | 31 | ##### email 32 | ###### string 33 | The email address of the user trying to sign up. 34 | 35 | ##### password 36 | ###### string 37 | The password of the user trying to sign up. 38 | 39 | ##### password_confirmation 40 | ###### string 41 | Should match the `password` field. 42 | 43 | ##### confirm_success_url 44 | ###### string 45 | The URL that the email confirmation link should lead back to. This field will be automatically filled out by this framework. 46 | 47 | -- 48 | 49 | ##### Request Body Example 50 | 51 | ~~~json 52 | { 53 | "email": "test@test.com", 54 | "password": "secret123", 55 | "password_confirmation": "secret123", 56 | "confirm_success_url": "https://redux-auth-demo.herokuapp.com/" 57 | } 58 | ~~~ 59 | 60 | -- 61 | 62 | ### Request Headers 63 | 64 | None. The API won't care about any headers for this request. 65 | 66 | ## Success Response 67 | 68 | ### Success Response Body 69 | 70 | A json object containing the following key. 71 | 72 | ##### status 73 | ###### string 74 | Will always be `"success"`. 75 | 76 | -- 77 | 78 | ##### Success Response Body Example 79 | 80 | ~~~json 81 | { 82 | "status": "success" 83 | } 84 | ~~~ 85 | 86 | -- 87 | 88 | #### Success Response Headers 89 | 90 | None. The client won't care about any headers for this response. 91 | 92 | -- 93 | 94 | ## Error Response 95 | 96 | This will contain the response status, and an array of any errors that the server encountered in processing the request. 97 | 98 | ### Error Response Body 99 | 100 | ##### status 101 | ###### string 102 | Will always be `"error"`. 103 | 104 | ##### errors 105 | ###### object containing arrays of strings for each invalid attribute 106 | An object containing keys for each field that has errors. The values of each key will be an array of errors for the given field. 107 | 108 | -- 109 | 110 | ##### Error Response Body Example 111 | ~~~json 112 | { 113 | status: "error", 114 | errors: { 115 | password_confirmation: ["doesn't match Password"], 116 | email: ["is not an email"] 117 | } 118 | } 119 | ~~~ 120 | 121 | -- 122 | 123 | ### Error Response Headers 124 | None. The client won't care about any headers for this response. 125 | 126 | -------------------------------------------------------------------------------- /docs/api-expectations/oauth-sign-in.md: -------------------------------------------------------------------------------- 1 | # OAuth Sign In 2 | 3 | * [Overview](#overview) 4 | * [Popup](#request) 5 | * [Popup Params](#popup-params) 6 | * [Success Popup Redirect](#success-popup-redirect) 7 | * [Success Popup Redirect Params](#success-popup-redirect-params) 8 | * [Error](#error) 9 | 10 | ## Overview 11 | ![oauth sign in](https://github.com/lynndylanhurley/redux-auth/raw/master/docs/images/diagram-oauth.jpg) 12 | 13 | ## Popup 14 | 15 | ### Popup URL Params 16 | 17 | ##### config_name 18 | ###### string 19 | Used by the client to track the endpoint configuration used in authentication. 20 | 21 | ##### resource_class 22 | ###### string 23 | Used by the server to track the user resource class. This is necessary to enable auth via multiple user classes. 24 | 25 | ##### auth_origin_url 26 | ###### string 27 | The base url of the site that initiated the popup. 28 | 29 | ##### Popup URL Example 30 | 31 | ~~~ 32 | https://devise-token-auth.dev/omniauth/github?auth_origin_url=http%3A%2F%2Flocalhost%3A8000%2F&config_name=default&resource_class=User 33 | ~~~ 34 | 35 | ## Success Popup Redirect 36 | 37 | The following steps will be taken after successful OAuth authentication. 38 | 39 | 1. After the user successfully authenticates on the external provider's site (github, facebook, etc.), the provider will redirect back to the API. 40 | 2. The API will find or create the user matching the provider user's UID, and then issue a new token. 41 | 3. The API will then redirect to the original URL that the popup was initiated from, including the auth credentials as URI params. 42 | 4. The popup initiator will detect these credentials and make a new request to the API to validate the auth token and pull the user's data. 43 | 44 | ### Success Popup Redirect Params 45 | 46 | ##### token 47 | ###### string 48 | The access token acts as a password for each request. 49 | 50 | ##### uid 51 | ###### string 52 | The unique identifier for the current user. 53 | 54 | ##### client 55 | ###### string 56 | The client token is used to identify device (browser client, phone, tablet, etc) of the current session. This allows us to maintain multiple concurrent sessions across devices / browsers. 57 | 58 | ##### expiry 59 | ###### integer 60 | The time at which the given `access-token` will expire. 61 | 62 | ##### Success Popup Redirect URL Example 63 | 64 | ~~~ 65 | https://redux-auth.herokuapp.com?token=bgINB4atOxd8SMNvtOTDxg&uid=test%40test.com&client=abc&expiry=1450988710 66 | ~~~ 67 | 68 | ## Error 69 | 70 | The only possible errors are for the user to close the window without authenticating, or for the subsequent token validation to fail. In both cases an error is shown to the client. See the token validation request docs for more info on the latter case. 71 | -------------------------------------------------------------------------------- /docs/api-expectations/request-password-reset.md: -------------------------------------------------------------------------------- 1 | # Request Password Reset 2 | 3 | * [Overview](#overview) 4 | * [Request](#request) 5 | * [Request Body](#request-body) 6 | * [Request Headers](#request-headers) 7 | * [Success Response](#success-response) 8 | * [Success Response Body](#success-response-body) 9 | * [Success Response Headers](#success-response-headers) 10 | * [Error Response](#error-response) 11 | * [Error Response Body](#error-response-body) 12 | * [Error Response Headers](#error-response-headers) 13 | 14 | ## Overview 15 | 16 | ![request password reset](https://github.com/lynndylanhurley/redux-auth/raw/master/docs/images/diagram-password-reset.jpg) 17 | 18 | ## Request 19 | 20 | ##### method 21 | ###### POST 22 | 23 | ### Request Params 24 | 25 | ##### config_name 26 | ###### string 27 | The current endpoint config. Will be `default` unless you're using multiple user types. 28 | 29 | ### Request Body 30 | 31 | ##### email 32 | ###### string 33 | The email address of the user trying to initiate the password reset. 34 | 35 | ##### redirect_url 36 | ###### string 37 | The URL that the email confirmation link should lead back to. This field will be automatically filled out by this framework. 38 | 39 | -- 40 | 41 | ##### Request Body Example 42 | 43 | ~~~json 44 | { 45 | "email": "test@test.com", 46 | "redirect_url": "https://redux-auth-demo.herokuapp.com/" 47 | } 48 | ~~~ 49 | 50 | -- 51 | 52 | ### Request Headers 53 | 54 | None. The API won't care about any headers for this request. 55 | 56 | ## Success Response 57 | 58 | ### Success Response Body 59 | 60 | A json object containing the following key. 61 | 62 | ##### success 63 | ###### boolean 64 | Will always be `true`. 65 | 66 | ##### message 67 | ###### string 68 | A success message that will be displayed to the user. 69 | 70 | -- 71 | 72 | ##### Success Response Body Example 73 | 74 | ~~~json 75 | { 76 | "success": true, 77 | "message": "An email has been sent to test@test.com containing instructions for resetting your password." 78 | } 79 | ~~~ 80 | 81 | -- 82 | 83 | #### Success Response Headers 84 | None. The client won't care about any headers for this response. 85 | 86 | -- 87 | 88 | ## Error Response 89 | This will contain the response status, and an array of any errors that the server encountered in processing the request. 90 | 91 | ### Error Response Body 92 | 93 | ##### success 94 | ###### boolean 95 | Will always be `false`. 96 | 97 | ##### errors 98 | ###### array of strings 99 | 100 | A list of errors that will be displayed to the user. 101 | 102 | -- 103 | 104 | ##### Error Response Body Example 105 | ~~~json 106 | { 107 | "success": false, 108 | "errors": ["Unable to find user with email 'bogus@test.com'."] 109 | } 110 | ~~~ 111 | 112 | -- 113 | 114 | ### Error Response Headers 115 | None. The client won't care about any headers for this response. 116 | -------------------------------------------------------------------------------- /docs/api-expectations/sign-out.md: -------------------------------------------------------------------------------- 1 | # Sign Out 2 | 3 | * [Overview](#overview) 4 | * [Request](#request) 5 | * [Request Body](#request-body) 6 | * [Request Headers](#request-headers) 7 | * [Success Response](#success-response) 8 | * [Success Response Body](#success-response-body) 9 | * [Success Response Headers](#success-response-headers) 10 | * [Error Response](#error-response) 11 | * [Error Response Body](#error-response-body) 12 | * [Error Response Headers](#error-response-headers) 13 | 14 | ## Overview 15 | 16 | ![sign out](https://github.com/lynndylanhurley/redux-auth/raw/master/docs/images/diagram-sign-out.jpg) 17 | 18 | ## Request 19 | 20 | ##### method 21 | ###### DELETE 22 | 23 | ### Request Body 24 | 25 | None. The API doesn't care about the body of this request. 26 | 27 | -- 28 | 29 | ### Request Headers 30 | 31 | ##### access-token 32 | ###### string 33 | The access token acts as a password for each request. 34 | 35 | ##### client 36 | ###### string 37 | The client token is used to identify device (browser client, phone, tablet, etc) of the current session. This allows us to maintain multiple concurrent sessions across devices / browsers. 38 | 39 | ##### uid 40 | ###### string 41 | The unique identifier for the current user. 42 | 43 | -- 44 | 45 | ##### Request Headers Example 46 | 47 | ~~~ 48 | access-token: bgINB4atOxd8SMNvtOTDxg 49 | client: V7EN7LSRYAbpE_-c5PvRSw 50 | uid: test@test.com 51 | ~~~ 52 | 53 | -- 54 | 55 | ## Success Response 56 | 57 | ### Success Response Body 58 | 59 | A successful response will simply return an object with a `success` attribute that is set to `true`. 60 | 61 | ##### success 62 | ###### boolean 63 | Will always be `true`. 64 | 65 | -- 66 | 67 | ##### Success Response Body Example 68 | 69 | ~~~json 70 | { 71 | "success": true 72 | } 73 | ~~~ 74 | 75 | -- 76 | 77 | #### Success Response Headers 78 | 79 | None. The client won't care about the headers for this response. 80 | 81 | -- 82 | 83 | ## Error Response 84 | 85 | This will be an array containing any errors that the server encountered in processing the request. 86 | 87 | ### Error Response Body 88 | 89 | ##### errors 90 | ###### array of strings 91 | 92 | A list of errors that will be displayed to the user. 93 | 94 | -- 95 | 96 | ##### Error Response Body Example 97 | ~~~json 98 | { 99 | "errors": ["User was not found or was not logged in."] 100 | } 101 | ~~~ 102 | 103 | -- 104 | 105 | ### Error Response Headers 106 | None. The client won't care about any headers for this response. 107 | -------------------------------------------------------------------------------- /docs/diagrams/diagram-destroy-account.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/diagrams/diagram-destroy-account.graffle -------------------------------------------------------------------------------- /docs/diagrams/diagram-email-sign-in.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/diagrams/diagram-email-sign-in.graffle -------------------------------------------------------------------------------- /docs/diagrams/diagram-email-sign-up.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/diagrams/diagram-email-sign-up.graffle -------------------------------------------------------------------------------- /docs/diagrams/diagram-oauth.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/diagrams/diagram-oauth.graffle -------------------------------------------------------------------------------- /docs/diagrams/diagram-password-reset.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/diagrams/diagram-password-reset.graffle -------------------------------------------------------------------------------- /docs/diagrams/diagram-sign-out.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/diagrams/diagram-sign-out.graffle -------------------------------------------------------------------------------- /docs/diagrams/diagram-token-validation.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/diagrams/diagram-token-validation.graffle -------------------------------------------------------------------------------- /docs/diagrams/diagram-update-password.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/diagrams/diagram-update-password.graffle -------------------------------------------------------------------------------- /docs/gifs/destroy-account.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/gifs/destroy-account.gif -------------------------------------------------------------------------------- /docs/gifs/email-sign-in.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/gifs/email-sign-in.gif -------------------------------------------------------------------------------- /docs/gifs/email-sign-up.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/gifs/email-sign-up.gif -------------------------------------------------------------------------------- /docs/gifs/oauth-sign-in.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/gifs/oauth-sign-in.gif -------------------------------------------------------------------------------- /docs/gifs/request-password-reset.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/gifs/request-password-reset.gif -------------------------------------------------------------------------------- /docs/gifs/sign-out.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/gifs/sign-out.gif -------------------------------------------------------------------------------- /docs/gifs/update-password.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/gifs/update-password.gif -------------------------------------------------------------------------------- /docs/images/bs-destroy-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/bs-destroy-account.png -------------------------------------------------------------------------------- /docs/images/bs-email-sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/bs-email-sign-in.png -------------------------------------------------------------------------------- /docs/images/bs-email-sign-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/bs-email-sign-up.png -------------------------------------------------------------------------------- /docs/images/bs-oauth-sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/bs-oauth-sign-in.png -------------------------------------------------------------------------------- /docs/images/bs-password-reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/bs-password-reset.png -------------------------------------------------------------------------------- /docs/images/bs-sign-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/bs-sign-out.png -------------------------------------------------------------------------------- /docs/images/bs-update-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/bs-update-password.png -------------------------------------------------------------------------------- /docs/images/default-destroy-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/default-destroy-account.png -------------------------------------------------------------------------------- /docs/images/default-email-sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/default-email-sign-in.png -------------------------------------------------------------------------------- /docs/images/default-email-sign-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/default-email-sign-up.png -------------------------------------------------------------------------------- /docs/images/default-oauth-sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/default-oauth-sign-in.png -------------------------------------------------------------------------------- /docs/images/default-password-reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/default-password-reset.png -------------------------------------------------------------------------------- /docs/images/default-sign-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/default-sign-out.png -------------------------------------------------------------------------------- /docs/images/default-update-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/default-update-password.png -------------------------------------------------------------------------------- /docs/images/diagram-destroy-account.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/diagram-destroy-account.jpg -------------------------------------------------------------------------------- /docs/images/diagram-email-sign-in.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/diagram-email-sign-in.jpg -------------------------------------------------------------------------------- /docs/images/diagram-email-sign-up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/diagram-email-sign-up.jpg -------------------------------------------------------------------------------- /docs/images/diagram-oauth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/diagram-oauth.jpg -------------------------------------------------------------------------------- /docs/images/diagram-password-reset.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/diagram-password-reset.jpg -------------------------------------------------------------------------------- /docs/images/diagram-sign-out.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/diagram-sign-out.jpg -------------------------------------------------------------------------------- /docs/images/diagram-token-validation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/diagram-token-validation.jpg -------------------------------------------------------------------------------- /docs/images/diagram-update-password.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/diagram-update-password.jpg -------------------------------------------------------------------------------- /docs/images/mui-destroy-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/mui-destroy-account.png -------------------------------------------------------------------------------- /docs/images/mui-email-sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/mui-email-sign-in.png -------------------------------------------------------------------------------- /docs/images/mui-email-sign-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/mui-email-sign-up.png -------------------------------------------------------------------------------- /docs/images/mui-error-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/mui-error-dialog.png -------------------------------------------------------------------------------- /docs/images/mui-inline-errors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/mui-inline-errors.png -------------------------------------------------------------------------------- /docs/images/mui-oauth-sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/mui-oauth-sign-in.png -------------------------------------------------------------------------------- /docs/images/mui-password-reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/mui-password-reset.png -------------------------------------------------------------------------------- /docs/images/mui-sign-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/mui-sign-out.png -------------------------------------------------------------------------------- /docs/images/mui-update-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/mui-update-password.png -------------------------------------------------------------------------------- /docs/images/redux-auth-logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/images/redux-auth-logo.gif -------------------------------------------------------------------------------- /docs/psd/redux-auth-logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lynndylanhurley/redux-auth/dc27f381cb7d16fcd4c812c7c1a2f9f921d73164/docs/psd/redux-auth-logo.psd -------------------------------------------------------------------------------- /dummy/babel.server.js: -------------------------------------------------------------------------------- 1 | //require("babel-polyfill"); 2 | 3 | require("babel-core/register")({ 4 | only: /src/, 5 | presets: ["es2015", "react", "stage-0"] 6 | }); 7 | 8 | /** 9 | * Define isomorphic constants. 10 | */ 11 | global.__CLIENT__ = false; 12 | global.__SERVER__ = true; 13 | 14 | if (process.env.NODE_ENV !== "production") { 15 | if (!require("piping")({hook: true, includeModules: false})) { 16 | return; 17 | } 18 | } 19 | 20 | try { 21 | require("./src/server"); 22 | } 23 | catch (error) { 24 | console.error(error.stack); 25 | } 26 | -------------------------------------------------------------------------------- /dummy/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiUrl": "http://devise-token-auth.dev" 3 | } 4 | -------------------------------------------------------------------------------- /dummy/config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiUrl": "http://devise-token-auth-demo.herokuapp.com" 3 | } 4 | -------------------------------------------------------------------------------- /dummy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-auth", 3 | "description": "Token authentication for redux with isomorphic support.", 4 | "version": "0.0.1-alpha1", 5 | "license": "BSD-3-Clause", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/lynndylanhurley/redux-auth.git" 9 | }, 10 | "homepage": "https://github.com/lynndylanhurley/redux-auth", 11 | "keywords": [ 12 | "react", 13 | "isomorphic", 14 | "universal", 15 | "starter", 16 | "boilerplate", 17 | "template", 18 | "webpack", 19 | "hapi", 20 | "transmit" 21 | ], 22 | "main": "babel.server.js", 23 | "scripts": { 24 | "start": "NODE_PATH=\"./src\" node --harmony ./babel.server", 25 | "build": "node ./node_modules/webpack/bin/webpack.js --verbose --colors --display-error-details --config webpack.client.js", 26 | "watch-client": "node ./node_modules/webpack/bin/webpack.js --verbose --colors --display-error-details --config webpack.client-watch.js && node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --config webpack.client-watch.js", 27 | "watch": "node ./node_modules/concurrently/src/main.js --kill-others \"npm run watch-client\" \"npm run start\"" 28 | }, 29 | "dependencies": { 30 | "babel-core": "6.3.13", 31 | "babel-polyfill": "6.3.13", 32 | "babel-preset-es2015": "6.3.13", 33 | "babel-preset-react": "6.3.13", 34 | "babel-preset-stage-0": "6.3.13", 35 | "babel-register": "6.3.13", 36 | "bootstrap": "^3.3.5", 37 | "bootstrap-sass": "^3.3.5", 38 | "bootstrap-webpack": "0.0.5", 39 | "classnames": "^2.1.5", 40 | "config": "^1.17.1", 41 | "cookie": "^0.2.2", 42 | "exports-loader": "^0.6.2", 43 | "extend": "^3.0.0", 44 | "h2o2": "4.0.1", 45 | "hapi": "9.3.1", 46 | "highlight.js": "^8.8.0", 47 | "immutable": "^3.7.5", 48 | "imports-loader": "^0.6.4", 49 | "inert": "3.0.1", 50 | "isomorphic-fetch": "2.1.1", 51 | "jquery-deparam": "^0.4.2", 52 | "less": "^2.5.3", 53 | "less-loader": "^2.2.1", 54 | "node-sass": "^3.3.3", 55 | "piping": "0.3.0", 56 | "query-string": "^2.4.2", 57 | "react": "^15.2.1", 58 | "react-bootstrap": "^0.29.5", 59 | "react-dom": "^15.2.1", 60 | "react-inline-css": "2.0.0", 61 | "react-loader": "^2.0.0", 62 | "react-redux": "^4.4.0", 63 | "react-router": "^2.5.2", 64 | "react-router-bootstrap": "^0.23.0", 65 | "react-router-redux": "^4.0.5", 66 | "react-select": "^1.0.0-beta13", 67 | "react-tap-event-plugin": "^1.0.0", 68 | "react-transmit": "3.0.8", 69 | "redux": "^3.3.1", 70 | "redux-immutablejs": "0.0.6", 71 | "redux-thunk": "^1.0.0", 72 | "serialize-javascript": "^1.1.2", 73 | "thunk": "0.0.1", 74 | "url-loader": "^0.5.6", 75 | "whatwg-fetch": "^0.9.0" 76 | }, 77 | "devDependencies": { 78 | "babel-loader": "6.1.0", 79 | "concurrently": "0.1.1", 80 | "css-loader": "^0.19.0", 81 | "extract-text-webpack-plugin": "0.9.1", 82 | "file-loader": "0.8.5", 83 | "json-loader": "0.5.4", 84 | "react-hot-loader": "1.3.0", 85 | "redux-devtools": "^2.1.5", 86 | "sass-loader": "3.1.2", 87 | "style-loader": "^0.12.4", 88 | "webpack": "1.12.9", 89 | "webpack-dev-server": "1.14.0" 90 | }, 91 | "engines": { 92 | "node": ">=0.10.32" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /dummy/src/actions/demo-ui.js: -------------------------------------------------------------------------------- 1 | export const UPDATE_DEMO_THEME = "UPDATE_DEMO_THEME"; 2 | export const UPDATE_DEMO_ENDPOINT = "UPDATE_DEMO_ENDPOINT"; 3 | 4 | export function updateDemoTheme(theme) { 5 | return { type: UPDATE_DEMO_THEME, theme }; 6 | } 7 | 8 | export function updateDemoEndpoint(endpoint) { 9 | return { type: UPDATE_DEMO_ENDPOINT, endpoint }; 10 | } 11 | -------------------------------------------------------------------------------- /dummy/src/actions/request-test-buttons.js: -------------------------------------------------------------------------------- 1 | import {fetch} from "../../../src/index"; 2 | 3 | export const REQUEST_TEST_START = "REQUEST_TEST_START"; 4 | export const REQUEST_TEST_COMPLETE = "REQUEST_TEST_COMPLETE"; 5 | export const REQUEST_TEST_ERROR = "REQUEST_TEST_ERROR"; 6 | export const DISMISS_REQUEST_TEST_SUCCESS_MODAL = "DISMISS_REQUEST_TEST_SUCCESS_MODAL"; 7 | export const DISMISS_REQUEST_TEST_ERROR_MODAL = "DISMISS_REQUEST_TEST_ERROR_MODAL"; 8 | 9 | export function dismissRequestTestSuccessModal() { 10 | return { type: DISMISS_REQUEST_TEST_SUCCESS_MODAL }; 11 | } 12 | export function dismissRequestTestErrorModal() { 13 | return { type: DISMISS_REQUEST_TEST_ERROR_MODAL }; 14 | } 15 | export function requestTestStart(key) { 16 | return { type: REQUEST_TEST_START, key }; 17 | } 18 | export function requestTestComplete(key) { 19 | return { type: REQUEST_TEST_COMPLETE, key }; 20 | } 21 | export function requestTestError(key) { 22 | return { type: REQUEST_TEST_ERROR, key }; 23 | } 24 | export function requestTest(url, key) { 25 | return dispatch => { 26 | dispatch(requestTestStart(key)); 27 | 28 | return fetch(url, { 29 | credentials: "include" 30 | }) 31 | .then(resp => { 32 | if (resp && resp.statusText === "OK") { 33 | dispatch(requestTestComplete(key)) 34 | } else { 35 | dispatch(requestTestError(key)); 36 | } 37 | 38 | return resp.json(); 39 | }) 40 | .then(json => { 41 | console.log("@-->resp json", json); 42 | return json; 43 | }) 44 | .catch(resp => { 45 | console.log("fail", resp); 46 | dispatch(requestTestError(key)) 47 | }); 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /dummy/src/client.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { initialize } from "./app"; 4 | 5 | 6 | /** 7 | * Fire-up React Router. 8 | */ 9 | const reactRoot = window.document.getElementById("react-root"); 10 | initialize().then(({provider}) => { 11 | ReactDOM.render(provider, reactRoot); 12 | }); 13 | 14 | 15 | /** 16 | * Detect whether the server-side render has been discarded due to an invalid checksum. 17 | */ 18 | if (process.env.NODE_ENV !== "production") { 19 | if (!reactRoot.firstChild || !reactRoot.firstChild.attributes || 20 | !reactRoot.firstChild.attributes["data-react-checksum"]) { 21 | console.error("Server-side React render was discarded. Make sure that your initial render does not contain any client-side code."); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /dummy/src/reducers/demo-ui.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/demo-ui"; 4 | 5 | const initialState = Immutable.fromJS({ 6 | theme: "materialUi", 7 | endpoint: "default" 8 | }); 9 | 10 | export default createReducer(initialState, { 11 | [A.UPDATE_DEMO_THEME]: (state, {theme}) => state.merge({theme}), 12 | [A.UPDATE_DEMO_ENDPOINT]: (state, {endpoint}) => state.merge({endpoint}) 13 | }); 14 | -------------------------------------------------------------------------------- /dummy/src/reducers/request-test-buttons.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/request-test-buttons"; 4 | 5 | const initialState = Immutable.fromJS({ 6 | showSuccessModal: false, 7 | showErrorModal: false, 8 | lastRequestUrl: null, 9 | buttons: {} 10 | }); 11 | 12 | export default createReducer(initialState, { 13 | [A.REQUEST_TEST_START]: (state, {key}) => state.mergeDeep({ 14 | buttons: { 15 | [key]: { 16 | loading: true 17 | } 18 | } 19 | }), 20 | 21 | [A.REQUEST_TEST_COMPLETE]: (state, {key}) => state.mergeDeep({ 22 | buttons: { 23 | [key]: { 24 | loading: false 25 | } 26 | }, 27 | showSuccessModal: true, 28 | lastRequestUrl: key 29 | }), 30 | 31 | [A.REQUEST_TEST_ERROR]: (state, {key}) => state.merge({ 32 | buttons: { 33 | [key]: { 34 | loading: false 35 | } 36 | }, 37 | showErrorModal: true, 38 | lastRequestUrl: key 39 | }), 40 | 41 | [A.DISMISS_REQUEST_TEST_SUCCESS_MODAL]: state => state.merge({ 42 | showSuccessModal: false, 43 | lastRequestUrl: null 44 | }), 45 | 46 | [A.DISMISS_REQUEST_TEST_ERROR_MODAL]: state => state.merge({ 47 | showErrorModal: false, 48 | lastRequestUrl: null 49 | }) 50 | }); 51 | -------------------------------------------------------------------------------- /dummy/src/server.js: -------------------------------------------------------------------------------- 1 | import {Server} from "hapi"; 2 | import h2o2 from "h2o2"; 3 | import inert from "inert"; 4 | import {renderToString} from "react-dom/server"; 5 | import {match} from "react-router"; 6 | import qs from "query-string"; 7 | import {initialize} from "./app"; 8 | import config from "config"; 9 | 10 | var hostname = process.env.HOSTNAME || "localhost"; 11 | global.__API_URL__ = config.get("apiUrl"); 12 | 13 | /** 14 | * base html template 15 | */ 16 | function getMarkup(webserver, provider) { 17 | var markup = renderToString(provider), 18 | styles = ""; 19 | 20 | if (process.env.NODE_ENV === "production") { 21 | styles = ``; 22 | } 23 | 24 | return ` 25 | 26 | 27 | Redux Auth – Isomorphic Example 28 | 31 | ${styles} 32 | 33 | 34 |
${markup}
35 | 36 | 37 | `; 38 | } 39 | 40 | /** 41 | * Start Hapi server on port 8000. 42 | */ 43 | const server = new Server(); 44 | 45 | server.connection({host: hostname, port: process.env.PORT || 8000}); 46 | 47 | server.register([ 48 | h2o2, 49 | inert 50 | ], function (err) { 51 | if (err) { 52 | throw err; 53 | } 54 | 55 | server.start(function () { 56 | console.info("==> ✅ Server is listening"); 57 | console.info("==> 🌎 Go to " + server.info.uri.toLowerCase()); 58 | }); 59 | }); 60 | 61 | 62 | /** 63 | * Attempt to serve static requests from the public folder. 64 | */ 65 | server.route({ 66 | method: "GET", 67 | path: "/{params*}", 68 | handler: { 69 | file: (request) => "static" + request.path 70 | } 71 | }); 72 | 73 | 74 | /** 75 | * Catch dynamic requests here to fire-up React Router. 76 | */ 77 | server.ext("onPreResponse", (request, reply) => { 78 | if (typeof request.response.statusCode !== "undefined") { 79 | return reply.continue(); 80 | } 81 | 82 | var query = qs.stringify(request.query); 83 | var location = request.path + (query.length ? "?" + query : ""); 84 | 85 | initialize({ 86 | isServer: true, 87 | cookies: request.headers.cookie, 88 | currentLocation: location, 89 | userAgent: request.headers["user-agent"] 90 | }) 91 | .then(({provider, blank, routes, history, location}) => { 92 | match({routes, history}, (error, redirectLocation, renderProps) => { 93 | if (redirectLocation) { 94 | reply.redirect(redirectLocation.pathname + redirectLocation.search); 95 | } else if (error || !renderProps) { 96 | reply.continue(); 97 | } else { 98 | var webserver = process.env.NODE_ENV === "production" ? "" : "//" + hostname + ":8080"; 99 | var output = (blank) ? "" : getMarkup(webserver, provider); 100 | 101 | reply(output); 102 | } 103 | }); 104 | }).catch(e => console.log("@-->server error", e, e.stack)); 105 | }); 106 | -------------------------------------------------------------------------------- /dummy/src/styles/main.scss: -------------------------------------------------------------------------------- 1 | $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/"; 2 | @import "~bootstrap-sass/assets/stylesheets/bootstrap"; 3 | @import "~highlight.js/styles/zenburn.css"; 4 | @import "~react-select/dist/react-select.css"; 5 | 6 | body { 7 | .code-snippet { 8 | margin-top: 10px; 9 | } 10 | 11 | .Select { 12 | margin-bottom: 10px; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /dummy/src/views/Account.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { PageHeader } from "react-bootstrap"; 3 | import { connect } from "react-redux"; 4 | import { SignOutButton } from "../../../src/views/bootstrap"; 5 | import { browserHistory } from "react-router"; 6 | 7 | class Account extends React.Component { 8 | render () { 9 | return ( 10 |
11 | Account page 12 |

This page should only visible to authenticated users.

13 | browserHistory.push("/")} /> 14 |
15 | ); 16 | } 17 | } 18 | 19 | export default connect(({auth}) => ({auth}))(Account); 20 | -------------------------------------------------------------------------------- /dummy/src/views/SignIn.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { PageHeader } from "react-bootstrap"; 3 | import { connect } from "react-redux"; 4 | import { EmailSignInForm } from "../../../src/views/bootstrap"; 5 | import { browserHistory } from "react-router"; 6 | 7 | class SignIn extends React.Component { 8 | render () { 9 | return ( 10 |
11 | Sign In 12 | 13 | browserHistory.push("/account")} 15 | endpoint={this.props.pageEndpoint} /> 16 |
17 | ); 18 | } 19 | } 20 | 21 | export default connect(({routes}) => ({routes}))(SignIn); 22 | -------------------------------------------------------------------------------- /dummy/src/views/partials/CodeSnippet.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import hljs from "highlight.js"; 3 | import $ from "jquery"; 4 | 5 | class CodeSnippet extends React.Component { 6 | static propTypes = { 7 | language: PropTypes.string, 8 | children: PropTypes.node.isRequired 9 | }; 10 | 11 | static defaultProps = { 12 | language: "javascript" 13 | }; 14 | 15 | state = { 16 | code: 17 | }; 18 | 19 | highlight ($target, rawCode) { 20 | let code = rawCode 21 | .replace(//g, ">") 23 | .replace(/ +/g, " ") 24 | .replace(/±/g, " "); 25 | let el = $(`${code}`)[0]; 26 | hljs.highlightBlock(el); 27 | $target.append(el); 28 | } 29 | 30 | componentDidMount() { 31 | let $target = $(this.refs.target); 32 | this.highlight($target, this.props.children); 33 | } 34 | 35 | componentDidUpdate () { 36 | let $target = $(this.refs.target); 37 | $target.html(""); 38 | this.highlight($target, this.props.children); 39 | } 40 | 41 | render () { 42 | return ( 43 |
44 | 45 |
46 |       
47 | ); 48 | } 49 | } 50 | 51 | export default CodeSnippet; 52 | -------------------------------------------------------------------------------- /dummy/src/views/partials/Container.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import { Grid, Navbar, NavItem, Nav, Brand } from "react-bootstrap"; 3 | import { LinkContainer } from "react-router-bootstrap"; 4 | 5 | if (!global.__SERVER__ && !global.__TEST__) { 6 | require("../../styles/main.scss"); 7 | } 8 | 9 | class Container extends React.Component { 10 | static propTypes = { 11 | children: PropTypes.node 12 | }; 13 | 14 | render () { 15 | return ( 16 |
17 | 18 | 19 | Redux Auth 20 | 21 | 29 | 30 | 31 | 32 | {this.props.children} 33 | 34 |
35 | ); 36 | } 37 | } 38 | 39 | export default Container; 40 | -------------------------------------------------------------------------------- /dummy/src/views/partials/ExampleWell.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import { Panel } from "react-bootstrap"; 3 | 4 | class ExampleWell extends React.Component { 5 | static propTypes = { 6 | children: PropTypes.node.isRequired 7 | }; 8 | 9 | render () { 10 | return ( 11 |
12 | 13 | 14 | {this.props.children} 15 | 16 |
17 | ); 18 | } 19 | } 20 | 21 | export default ExampleWell; 22 | -------------------------------------------------------------------------------- /dummy/src/views/partials/GlobalComponents.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import RequestTestSuccessModal from "./RequestTestSuccessModal"; 3 | import RequestTestErrorModal from "./RequestTestErrorModal"; 4 | import * as BSTheme from "../../../../src/views/bootstrap"; 5 | import * as DefaultTheme from "../../../../src/views/default"; 6 | import * as MUITheme from "../../../../src/views/material-ui"; 7 | import { connect } from "react-redux"; 8 | 9 | class GlobalComponents extends React.Component { 10 | render () { 11 | let Theme = MUITheme; 12 | 13 | switch(this.props.theme) { 14 | case "default": 15 | Theme = DefaultTheme; 16 | break; 17 | case "bootstrap": 18 | Theme = BSTheme; 19 | break; 20 | } 21 | 22 | return ( 23 |
24 | 25 | 26 | 27 |
28 | ); 29 | } 30 | } 31 | 32 | export default connect(({demoUi}) => { 33 | return ({ 34 | theme: demoUi.get("theme"), 35 | }) 36 | })(GlobalComponents); 37 | -------------------------------------------------------------------------------- /dummy/src/views/partials/IndexPanel.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import { Panel, Col } from "react-bootstrap"; 3 | 4 | class IndexPanel extends React.Component { 5 | static propTypes = { 6 | bsStyle: PropTypes.string, 7 | header: PropTypes.string, 8 | children: PropTypes.node 9 | }; 10 | 11 | static defaultProps = { 12 | bsStyle: "info", 13 | children: 14 | }; 15 | 16 | render () { 17 | return ( 18 | 19 | 20 | 21 | ); 22 | } 23 | } 24 | 25 | export default IndexPanel; 26 | -------------------------------------------------------------------------------- /dummy/src/views/partials/RequestTestButton.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import {Glyphicon} from "react-bootstrap"; 3 | import {ButtonLoader} from "../../../../src/views/bootstrap"; 4 | import {connect} from "react-redux"; 5 | import {requestTest} from "../../actions/request-test-buttons"; 6 | import {getApiUrl} from "../../../../src/utils/session-storage"; 7 | 8 | class RequestTestButton extends React.Component { 9 | static propTypes = { 10 | path: PropTypes.string.isRequired, 11 | endpointKey: PropTypes.string.isRequired 12 | }; 13 | 14 | static defaultProps = { 15 | endpointKey: "default" 16 | }; 17 | 18 | handleClick () { 19 | let url = getApiUrl() + this.props.path; 20 | this.props.dispatch(requestTest(url, this.props.path)); 21 | } 22 | 23 | render () { 24 | let text = "Will Fail", 25 | bsStyle = "danger", 26 | glyph = , 27 | loading = this.props.demoButtons.getIn(["buttons", this.props.path, "loading"]); 28 | 29 | if ( 30 | this.props.signedIn && ( 31 | this.props.currentEndpointKey === this.props.endpointKey || 32 | this.props.endpointKey === "any" 33 | ) 34 | ) { 35 | text = "Should Succeed"; 36 | bsStyle = "success"; 37 | glyph = ; 38 | } 39 | 40 | return ( 41 | 47 | {text} 48 | 49 | ); 50 | } 51 | } 52 | 53 | export default connect(({auth, demoButtons}) => { 54 | return { 55 | signedIn: auth.getIn(["user", "isSignedIn"]), 56 | currentEndpointKey: auth.getIn(["configure", "currentEndpointKey"]), 57 | demoButtons 58 | }; 59 | })(RequestTestButton); 60 | -------------------------------------------------------------------------------- /dummy/src/views/partials/RequestTestErrorModal.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import { Modal, Button } from "react-bootstrap"; 3 | import { connect } from "react-redux"; 4 | import { dismissRequestTestErrorModal } from "../../actions/request-test-buttons"; 5 | 6 | class RequestTestErrorModal extends React.Component { 7 | static propTypes = { 8 | show: PropTypes.bool, 9 | url: PropTypes.string 10 | }; 11 | 12 | static defaultProps = { 13 | show: false 14 | }; 15 | 16 | close () { 17 | this.props.dispatch(dismissRequestTestErrorModal()); 18 | } 19 | 20 | render () { 21 | return ( 22 | 24 | 25 | Ajax Request Error 26 | 27 | 28 | 29 |

30 | Request to {this.props.url} failed. 31 |

32 |
33 | 34 | 35 | 38 | 39 |
40 | ); 41 | } 42 | } 43 | 44 | export default connect(({demoButtons}) => ({ 45 | show: demoButtons.get("showErrorModal"), 46 | url: demoButtons.get("lastRequestUrl") 47 | }))(RequestTestErrorModal); 48 | -------------------------------------------------------------------------------- /dummy/src/views/partials/RequestTestSuccessModal.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from "react"; 2 | import { Modal, Button } from "react-bootstrap"; 3 | import { connect } from "react-redux"; 4 | import { dismissRequestTestSuccessModal } from "../../actions/request-test-buttons"; 5 | 6 | class RequestTestSuccessModal extends React.Component { 7 | static propTypes = { 8 | show: PropTypes.bool, 9 | url: PropTypes.string 10 | }; 11 | 12 | static defaultProps = { 13 | show: false 14 | }; 15 | 16 | close () { 17 | this.props.dispatch(dismissRequestTestSuccessModal()); 18 | } 19 | 20 | render () { 21 | return ( 22 | 24 | 25 | Ajax Request Success 26 | 27 | 28 | 29 |

30 | Request to {this.props.url} was successful. 31 |

32 |
33 | 34 | 35 | 38 | 39 |
40 | ); 41 | } 42 | } 43 | 44 | export default connect(({demoButtons}) => ({ 45 | show: demoButtons.get("showSuccessModal"), 46 | url: demoButtons.get("lastRequestUrl") 47 | }))(RequestTestSuccessModal); 48 | -------------------------------------------------------------------------------- /dummy/webpack.client-watch.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var config = require("./webpack.client.js"); 3 | 4 | var hostname = process.env.HOSTNAME || "localhost"; 5 | 6 | config.cache = true; 7 | config.debug = true; 8 | config.devtool = "eval-sourcemap"; 9 | 10 | config.entry.unshift( 11 | "webpack-dev-server/client?http://" + hostname + ":8080", 12 | "webpack/hot/only-dev-server" 13 | ); 14 | 15 | config.output.publicPath = "http://" + hostname + ":8080/dist/"; 16 | config.output.hotUpdateMainFilename = "update/[hash]/update.json"; 17 | config.output.hotUpdateChunkFilename = "update/[hash]/[id].update.js"; 18 | 19 | config.plugins = [ 20 | new webpack.DefinePlugin({__CLIENT__: true, __SERVER__: false}), 21 | new webpack.HotModuleReplacementPlugin(), 22 | new webpack.NoErrorsPlugin() 23 | ]; 24 | 25 | config.module.postLoaders = [ 26 | {test: /\.js$/, loaders: ["react-hot"], exclude: /node_modules/} 27 | ] 28 | 29 | config.devServer = { 30 | publicPath: "http://" + hostname + ":8080/dist/", 31 | contentBase: "./static", 32 | hot: true, 33 | inline: true, 34 | lazy: false, 35 | quiet: true, 36 | noInfo: false, 37 | headers: {"Access-Control-Allow-Origin": "*"}, 38 | stats: {colors: true}, 39 | host: hostname 40 | }; 41 | 42 | module.exports = config; 43 | -------------------------------------------------------------------------------- /dummy/webpack.client.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var path = require("path"); 3 | var ExtractTextPlugin = require("extract-text-webpack-plugin"); 4 | 5 | module.exports = { 6 | target: "web", 7 | cache: false, 8 | context: __dirname, 9 | devtool: false, 10 | entry: ["./src/client"], 11 | output: { 12 | path: path.join(__dirname, "static/dist"), 13 | filename: "client.js", 14 | chunkFilename: "[name].[id].js", 15 | publicPath: "dist/" 16 | }, 17 | plugins: [ 18 | new webpack.DefinePlugin({__CLIENT__: true, __SERVER__: false}), 19 | new webpack.DefinePlugin({"process.env": {NODE_ENV: "\"production\""}}), 20 | new webpack.optimize.DedupePlugin(), 21 | new webpack.optimize.OccurenceOrderPlugin(), 22 | new webpack.optimize.UglifyJsPlugin(), 23 | new ExtractTextPlugin("[name].css") 24 | ], 25 | module: { 26 | loaders: [ 27 | { test: /\.woff(2)?(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff" }, 28 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream" }, 29 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file" }, 30 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml" }, 31 | { include: /\.json$/, loaders: ["json"] }, 32 | { include: /\.js$/, loaders: ["babel?cacheDirectory&presets[]=es2015&presets[]=react&presets[]=stage-0"], exclude: /node_modules/ }, 33 | { test: /\.scss$/, loaders: ["style", "css", "sass"] } 34 | ], 35 | postLoaders: [ 36 | { test: /\.scss$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader!sass-loader", {publicPath: "/dist/"}) } 37 | ], 38 | noParse: [/\.min\.js/, /autoit\.js/] 39 | }, 40 | resolve: { 41 | alias: { 42 | react: path.join(__dirname, "node_modules/react") 43 | }, 44 | modulesDirectories: [ 45 | "src", 46 | "../src", 47 | "node_modules", 48 | "../node_modules", 49 | "web_modules" 50 | ], 51 | extensions: ["", ".json", ".js"] 52 | }, 53 | node: { 54 | __dirname: true, 55 | fs: "empty" 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /src/actions/authenticate.js: -------------------------------------------------------------------------------- 1 | export const AUTHENTICATE_START = "AUTHENTICATE_START"; 2 | export const AUTHENTICATE_COMPLETE = "AUTHENTICATE_COMPLETE"; 3 | export const AUTHENTICATE_ERROR = "AUTHENTICATE_ERROR"; 4 | 5 | export function authenticateStart() { 6 | return { type: AUTHENTICATE_START }; 7 | } 8 | export function authenticateComplete(user) { 9 | return { type: AUTHENTICATE_COMPLETE, user }; 10 | } 11 | export function authenticateError(errors) { 12 | return { type: AUTHENTICATE_ERROR, errors }; 13 | } 14 | -------------------------------------------------------------------------------- /src/actions/destroy-account.js: -------------------------------------------------------------------------------- 1 | import {getDestroyAccountUrl, setCurrentEndpointKey, getDefaultEndpointKey} from "../utils/session-storage"; 2 | import {parseResponse} from "../utils/handle-fetch-response"; 3 | import {storeCurrentEndpointKey} from "./configure"; 4 | import fetch from "../utils/fetch"; 5 | 6 | export const DESTROY_ACCOUNT_START = "DESTROY_ACCOUNT_START"; 7 | export const DESTROY_ACCOUNT_COMPLETE = "DESTROY_ACCOUNT_COMPLETE"; 8 | export const DESTROY_ACCOUNT_ERROR = "DESTROY_ACCOUNT_ERROR"; 9 | 10 | export function destroyAccountStart(endpoint) { 11 | return { type: DESTROY_ACCOUNT_START, endpoint }; 12 | } 13 | export function destroyAccountComplete(message, endpoint) { 14 | return { type: DESTROY_ACCOUNT_COMPLETE, endpoint, message }; 15 | } 16 | export function destroyAccountError(errors, endpoint) { 17 | return { type: DESTROY_ACCOUNT_ERROR, endpoint, errors }; 18 | } 19 | export function destroyAccount(endpoint) { 20 | return dispatch => { 21 | dispatch(destroyAccountStart(endpoint)); 22 | 23 | return fetch(getDestroyAccountUrl(endpoint), {method: "delete"}) 24 | .then(parseResponse) 25 | .then(({message}) => { 26 | dispatch(destroyAccountComplete(message, endpoint)); 27 | 28 | // revert current session endpoint to default 29 | let defaultEndpointKey = getDefaultEndpointKey() 30 | 31 | // set in store 32 | dispatch(storeCurrentEndpointKey(defaultEndpointKey)); 33 | 34 | // and in session 35 | setCurrentEndpointKey(defaultEndpointKey); 36 | }) 37 | .catch(({errors}) => dispatch(destroyAccountError(errors, endpoint))); 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/actions/email-sign-in.js: -------------------------------------------------------------------------------- 1 | import { 2 | getEmailSignInUrl, 3 | setCurrentEndpointKey, 4 | getCurrentEndpointKey 5 | } from "../utils/session-storage"; 6 | import {storeCurrentEndpointKey} from "./configure"; 7 | import {parseResponse} from "../utils/handle-fetch-response"; 8 | import fetch from "../utils/fetch"; 9 | 10 | export const EMAIL_SIGN_IN_START = "EMAIL_SIGN_IN_START"; 11 | export const EMAIL_SIGN_IN_COMPLETE = "EMAIL_SIGN_IN_COMPLETE"; 12 | export const EMAIL_SIGN_IN_ERROR = "EMAIL_SIGN_IN_ERROR"; 13 | export const EMAIL_SIGN_IN_FORM_UPDATE = "EMAIL_SIGN_IN_FORM_UPDATE"; 14 | 15 | export function emailSignInFormUpdate(endpoint, key, value) { 16 | return { type: EMAIL_SIGN_IN_FORM_UPDATE, endpoint, key, value }; 17 | } 18 | export function emailSignInStart(endpoint) { 19 | return { type: EMAIL_SIGN_IN_START, endpoint }; 20 | } 21 | export function emailSignInComplete(endpoint, user) { 22 | return { type: EMAIL_SIGN_IN_COMPLETE, user, endpoint }; 23 | } 24 | export function emailSignInError(endpoint, errors) { 25 | return { type: EMAIL_SIGN_IN_ERROR, errors, endpoint }; 26 | } 27 | export function emailSignIn(body, endpointKey) { 28 | return dispatch => { 29 | // save previous endpoint key in case of failure 30 | var prevEndpointKey = getCurrentEndpointKey(); 31 | 32 | // necessary for fetch to recognize the response as an api request 33 | setCurrentEndpointKey(endpointKey); 34 | var currentEndpointKey = getCurrentEndpointKey(); 35 | 36 | dispatch(storeCurrentEndpointKey(currentEndpointKey)); 37 | dispatch(emailSignInStart(currentEndpointKey)); 38 | 39 | return fetch(getEmailSignInUrl(currentEndpointKey), { 40 | headers: { 41 | "Accept": "application/json", 42 | "Content-Type": "application/json" 43 | }, 44 | method: "post", 45 | body: JSON.stringify(body) 46 | }) 47 | .then(parseResponse) 48 | .then((user) => dispatch(emailSignInComplete(currentEndpointKey, user))) 49 | .catch((errors) => { 50 | // revert endpoint key to what it was before failed request 51 | setCurrentEndpointKey(prevEndpointKey); 52 | dispatch(storeCurrentEndpointKey(prevEndpointKey)); 53 | dispatch(emailSignInError(currentEndpointKey, errors)); 54 | throw errors; 55 | }); 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /src/actions/email-sign-up.js: -------------------------------------------------------------------------------- 1 | import {getEmailSignUpUrl, getConfirmationSuccessUrl} from "../utils/session-storage"; 2 | import {parseResponse} from "../utils/handle-fetch-response"; 3 | import extend from "extend"; 4 | import fetch from "../utils/fetch"; 5 | 6 | export const EMAIL_SIGN_UP_START = "EMAIL_SIGN_UP_START"; 7 | export const EMAIL_SIGN_UP_COMPLETE = "EMAIL_SIGN_UP_COMPLETE"; 8 | export const EMAIL_SIGN_UP_ERROR = "EMAIL_SIGN_UP_ERROR"; 9 | export const EMAIL_SIGN_UP_FORM_UPDATE = "EMAIL_SIGN_UP_FORM_UPDATE"; 10 | 11 | export function emailSignUpFormUpdate(endpoint, key, value) { 12 | return { type: EMAIL_SIGN_UP_FORM_UPDATE, endpoint, key, value }; 13 | } 14 | export function emailSignUpStart(endpoint) { 15 | return { type: EMAIL_SIGN_UP_START, endpoint }; 16 | } 17 | export function emailSignUpComplete(user, endpoint) { 18 | return { type: EMAIL_SIGN_UP_COMPLETE, user, endpoint }; 19 | } 20 | export function emailSignUpError(errors, endpoint) { 21 | return { type: EMAIL_SIGN_UP_ERROR, errors, endpoint }; 22 | } 23 | export function emailSignUp(body, endpointKey) { 24 | return dispatch => { 25 | dispatch(emailSignUpStart(endpointKey)); 26 | 27 | return fetch(getEmailSignUpUrl(endpointKey), { 28 | headers: { 29 | "Accept": "application/json", 30 | "Content-Type": "application/json" 31 | }, 32 | method: "post", 33 | body: JSON.stringify(extend(body, { 34 | confirm_success_url: getConfirmationSuccessUrl() 35 | })) 36 | }) 37 | .then(parseResponse) 38 | .then(({data}) => dispatch(emailSignUpComplete(data, endpointKey))) 39 | .catch(({errors}) => { 40 | dispatch(emailSignUpError(errors, endpointKey)) 41 | throw errors; 42 | }); 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/actions/oauth-sign-in.js: -------------------------------------------------------------------------------- 1 | import * as C from "../utils/constants"; 2 | import {getAllParams, normalizeTokenKeys} from "../utils/parse-url"; 3 | import {getOAuthUrl} from "../utils/session-storage"; 4 | import { 5 | setCurrentEndpointKey, 6 | getCurrentEndpointKey, 7 | getTokenValidationPath, 8 | persistData, 9 | } from "../utils/session-storage"; 10 | import {storeCurrentEndpointKey} from "./configure"; 11 | import {parseResponse} from "../utils/handle-fetch-response"; 12 | import fetch from "../utils/fetch"; 13 | import _openPopup from "../utils/popup"; 14 | 15 | export const OAUTH_SIGN_IN_START = "OAUTH_SIGN_IN_START"; 16 | export const OAUTH_SIGN_IN_COMPLETE = "OAUTH_SIGN_IN_COMPLETE"; 17 | export const OAUTH_SIGN_IN_ERROR = "OAUTH_SIGN_IN_ERROR"; 18 | 19 | // hook for rewire 20 | var openPopup = _openPopup; 21 | 22 | function listenForCredentials (endpointKey, popup, provider, resolve, reject) { 23 | if (!resolve) { 24 | return new Promise((resolve, reject) => { 25 | listenForCredentials(endpointKey, popup, provider, resolve, reject); 26 | }); 27 | 28 | } else { 29 | let creds; 30 | 31 | try { 32 | creds = getAllParams(popup.location); 33 | } catch (err) {} 34 | 35 | if (creds && creds.uid) { 36 | popup.close(); 37 | persistData(C.SAVED_CREDS_KEY, normalizeTokenKeys(creds)); 38 | fetch(getTokenValidationPath(endpointKey)) 39 | .then(parseResponse) 40 | .then(({data}) => resolve(data)) 41 | .catch(({errors}) => reject({errors})); 42 | } else if (popup.closed) { 43 | reject({errors: "Authentication was cancelled."}) 44 | } else { 45 | setTimeout(() => { 46 | listenForCredentials(endpointKey, popup, provider, resolve, reject); 47 | }, 0); 48 | } 49 | } 50 | } 51 | 52 | 53 | function authenticate({endpointKey, provider, url, tab=false}) { 54 | let name = (tab) ? "_blank" : provider; 55 | let popup = openPopup(provider, url, name); 56 | return listenForCredentials(endpointKey, popup, provider); 57 | } 58 | 59 | 60 | export function oAuthSignInStart(provider, endpoint) { 61 | return { type: OAUTH_SIGN_IN_START, provider, endpoint }; 62 | } 63 | export function oAuthSignInComplete(user, endpoint) { 64 | return { type: OAUTH_SIGN_IN_COMPLETE, user, endpoint }; 65 | } 66 | export function oAuthSignInError(errors, endpoint) { 67 | return { type: OAUTH_SIGN_IN_ERROR, errors, endpoint }; 68 | } 69 | export function oAuthSignIn({provider, params, endpointKey}) { 70 | return dispatch => { 71 | // save previous endpoint key in case of failure 72 | var prevEndpointKey = getCurrentEndpointKey(); 73 | 74 | // necessary for `fetch` to recognize the response as an api request 75 | setCurrentEndpointKey(endpointKey); 76 | dispatch(storeCurrentEndpointKey(endpointKey)); 77 | 78 | var currentEndpointKey = getCurrentEndpointKey(); 79 | 80 | dispatch(oAuthSignInStart(provider, currentEndpointKey)); 81 | 82 | let url = getOAuthUrl({provider, params, currentEndpointKey}); 83 | 84 | return authenticate({endpointKey, provider, url}) 85 | .then(user => dispatch(oAuthSignInComplete(user, currentEndpointKey))) 86 | .catch(({ errors }) => { 87 | // revert endpoint key to what it was before failed request 88 | setCurrentEndpointKey(prevEndpointKey); 89 | dispatch(storeCurrentEndpointKey(prevEndpointKey)); 90 | dispatch(oAuthSignInError(errors, currentEndpointKey)) 91 | throw errors; 92 | }); 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /src/actions/request-password-reset.js: -------------------------------------------------------------------------------- 1 | import {getPasswordResetRequestUrl, getPasswordResetRedirectUrl} from "../utils/session-storage"; 2 | import {parseResponse} from "../utils/handle-fetch-response"; 3 | import extend from "extend"; 4 | import fetch from "../utils/fetch"; 5 | 6 | export const REQUEST_PASSWORD_RESET_START = "REQUEST_PASSWORD_RESET_START"; 7 | export const REQUEST_PASSWORD_RESET_COMPLETE = "REQUEST_PASSWORD_RESET_COMPLETE"; 8 | export const REQUEST_PASSWORD_RESET_ERROR = "REQUEST_PASSWORD_RESET_ERROR"; 9 | export const REQUEST_PASSWORD_RESET_FORM_UPDATE = "REQUEST_PASSWORD_RESET_FORM_UPDATE"; 10 | 11 | export function requestPasswordResetFormUpdate(endpoint, key, value) { 12 | return { type: REQUEST_PASSWORD_RESET_FORM_UPDATE, endpoint, key, value }; 13 | } 14 | export function requestPasswordResetStart(endpoint) { 15 | return { type: REQUEST_PASSWORD_RESET_START, endpoint }; 16 | } 17 | export function requestPasswordResetComplete(endpoint, message) { 18 | return { type: REQUEST_PASSWORD_RESET_COMPLETE, endpoint, message }; 19 | } 20 | export function requestPasswordResetError(endpoint, errors) { 21 | return { type: REQUEST_PASSWORD_RESET_ERROR, endpoint, errors }; 22 | } 23 | export function requestPasswordReset(body, endpoint) { 24 | return dispatch => { 25 | dispatch(requestPasswordResetStart(endpoint)); 26 | 27 | return fetch(getPasswordResetRequestUrl(endpoint), { 28 | headers: { 29 | "Accept": "application/json", 30 | "Content-Type": "application/json" 31 | }, 32 | method: "post", 33 | body: JSON.stringify(extend(body, { 34 | redirect_url: getPasswordResetRedirectUrl(endpoint) 35 | })) 36 | }) 37 | .then(parseResponse) 38 | .then(({message}) => dispatch(requestPasswordResetComplete(endpoint, message))) 39 | .catch(({errors}) => dispatch(requestPasswordResetError(endpoint, errors))); 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/actions/server.js: -------------------------------------------------------------------------------- 1 | export const SS_AUTH_TOKEN_UPDATE = "SS_AUTH_TOKEN_UPDATE"; 2 | 3 | export function ssAuthTokenUpdate({user, headers, mustResetPassword, firstTimeLogin, endpointKey}) { 4 | return { type: SS_AUTH_TOKEN_UPDATE, user, headers, mustResetPassword, firstTimeLogin, endpointKey }; 5 | } 6 | -------------------------------------------------------------------------------- /src/actions/sign-out.js: -------------------------------------------------------------------------------- 1 | import {getSignOutUrl, destroySession} from "../utils/session-storage"; 2 | import {parseResponse} from "../utils/handle-fetch-response"; 3 | import {storeCurrentEndpointKey} from "./configure"; 4 | import fetch from "../utils/fetch"; 5 | 6 | export const SIGN_OUT_START = "SIGN_OUT_START"; 7 | export const SIGN_OUT_COMPLETE = "SIGN_OUT_COMPLETE"; 8 | export const SIGN_OUT_ERROR = "SIGN_OUT_ERROR"; 9 | 10 | export function signOutStart(endpoint) { 11 | return { type: SIGN_OUT_START, endpoint }; 12 | } 13 | export function signOutComplete(endpoint, user) { 14 | return { type: SIGN_OUT_COMPLETE, user, endpoint }; 15 | } 16 | export function signOutError(endpoint, errors) { 17 | return { type: SIGN_OUT_ERROR, endpoint, errors }; 18 | } 19 | export function signOut(endpoint) { 20 | return dispatch => { 21 | dispatch(signOutStart(endpoint)); 22 | 23 | return fetch(getSignOutUrl(endpoint), {method: "delete"}) 24 | .then(parseResponse) 25 | .then((user) => { 26 | dispatch(signOutComplete(endpoint, user)) 27 | dispatch(storeCurrentEndpointKey(null)); 28 | destroySession(); 29 | }) 30 | .catch(({errors}) => { 31 | dispatch(signOutError(endpoint, errors)) 32 | dispatch(storeCurrentEndpointKey(null)); 33 | destroySession(); 34 | throw errors; 35 | }); 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/actions/update-password-modal.js: -------------------------------------------------------------------------------- 1 | import {getPasswordUpdateUrl} from "../utils/session-storage"; 2 | import {parseResponse} from "../utils/handle-fetch-response"; 3 | import fetch from "../utils/fetch"; 4 | 5 | export const UPDATE_PASSWORD_MODAL_START = "UPDATE_PASSWORD_MODAL_START"; 6 | export const UPDATE_PASSWORD_MODAL_COMPLETE = "UPDATE_PASSWORD_MODAL_COMPLETE"; 7 | export const UPDATE_PASSWORD_MODAL_ERROR = "UPDATE_PASSWORD_MODAL_ERROR"; 8 | export const UPDATE_PASSWORD_MODAL_FORM_UPDATE = "UPDATE_PASSWORD_MODAL_FORM_UPDATE"; 9 | 10 | export function updatePasswordModalFormUpdate(endpoint, key, value) { 11 | return { type: UPDATE_PASSWORD_MODAL_FORM_UPDATE, endpoint, key, value }; 12 | } 13 | export function updatePasswordModalStart(endpoint) { 14 | return { type: UPDATE_PASSWORD_MODAL_START, endpoint }; 15 | } 16 | export function updatePasswordModalComplete(endpoint, user) { 17 | return { type: UPDATE_PASSWORD_MODAL_COMPLETE, endpoint, user }; 18 | } 19 | export function updatePasswordModalError(endpoint, errors) { 20 | return { type: UPDATE_PASSWORD_MODAL_ERROR, endpoint, errors }; 21 | } 22 | export function updatePasswordModal(body, endpointKey) { 23 | return dispatch => { 24 | dispatch(updatePasswordModalStart(endpointKey)); 25 | 26 | return fetch(getPasswordUpdateUrl(endpointKey), { 27 | headers: { 28 | "Accept": "application/json", 29 | "Content-Type": "application/json" 30 | }, 31 | method: "put", 32 | body: JSON.stringify(body) 33 | }) 34 | .then(parseResponse) 35 | .then(({user}) => dispatch(updatePasswordModalComplete(endpointKey, user))) 36 | .catch(({errors}) => dispatch(updatePasswordModalError(endpointKey, errors))); 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/actions/update-password.js: -------------------------------------------------------------------------------- 1 | import {getPasswordUpdateUrl} from "../utils/session-storage"; 2 | import {parseResponse} from "../utils/handle-fetch-response"; 3 | import fetch from "../utils/fetch"; 4 | 5 | export const UPDATE_PASSWORD_START = "UPDATE_PASSWORD_START"; 6 | export const UPDATE_PASSWORD_COMPLETE = "UPDATE_PASSWORD_COMPLETE"; 7 | export const UPDATE_PASSWORD_ERROR = "UPDATE_PASSWORD_ERROR"; 8 | export const UPDATE_PASSWORD_FORM_UPDATE = "UPDATE_PASSWORD_FORM_UPDATE"; 9 | 10 | export function updatePasswordFormUpdate(endpoint, key, value) { 11 | return { type: UPDATE_PASSWORD_FORM_UPDATE, endpoint, key, value }; 12 | } 13 | export function updatePasswordStart(endpoint) { 14 | return { type: UPDATE_PASSWORD_START, endpoint }; 15 | } 16 | export function updatePasswordComplete(endpoint, user) { 17 | return { type: UPDATE_PASSWORD_COMPLETE, endpoint, user }; 18 | } 19 | export function updatePasswordError(endpoint, errors) { 20 | return { type: UPDATE_PASSWORD_ERROR, endpoint, errors }; 21 | } 22 | export function updatePassword(body, endpoint) { 23 | return dispatch => { 24 | dispatch(updatePasswordStart(endpoint)); 25 | 26 | return fetch(getPasswordUpdateUrl(endpoint), { 27 | headers: { 28 | "Accept": "application/json", 29 | "Content-Type": "application/json" 30 | }, 31 | method: "put", 32 | body: JSON.stringify(body) 33 | }) 34 | .then(parseResponse) 35 | .then(({user}) => dispatch(updatePasswordComplete(endpoint, user))) 36 | .catch(({errors}) => dispatch(updatePasswordError(endpoint, errors))); 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import authentication from "./reducers/authenticate"; 2 | import configure from "./reducers/configure"; 3 | import user from "./reducers/user"; 4 | import ui from "./reducers/ui"; 5 | import emailSignIn from "./reducers/email-sign-in"; 6 | import emailSignUp from "./reducers/email-sign-up"; 7 | import oAuthSignIn from "./reducers/oauth-sign-in"; 8 | import requestPasswordReset from "./reducers/request-password-reset"; 9 | import updatePassword from "./reducers/update-password"; 10 | import updatePasswordModal from "./reducers/update-password-modal"; 11 | import server from "./reducers/server"; 12 | import signOut from "./reducers/sign-out"; 13 | import destroyAccount from "./reducers/destroy-account"; 14 | import {combineReducers} from "redux-immutablejs"; 15 | 16 | /* reducers */ 17 | export const authStateReducer = combineReducers({ 18 | configure, 19 | emailSignIn, 20 | emailSignUp, 21 | signOut, 22 | authentication, 23 | requestPasswordReset, 24 | oAuthSignIn, 25 | updatePassword, 26 | updatePasswordModal, 27 | destroyAccount, 28 | server, 29 | ui, 30 | user 31 | }); 32 | 33 | /* actions */ 34 | export {configure} from "./actions/configure"; 35 | export {authenticate} from "./actions/authenticate"; 36 | export {emailSignIn, emailSignInFormUpdate} from "./actions/email-sign-in"; 37 | export {signOut} from "./actions/sign-out"; 38 | export {emailSignUp, emailSignUpFormUpdate} from "./actions/email-sign-up"; 39 | export {oAuthSignIn} from "./actions/oauth-sign-in"; 40 | export {requestPasswordReset, requestPasswordResetFormUpdate} from "./actions/request-password-reset"; 41 | export {updatePassword, updatePasswordFormUpdate} from "./actions/update-password"; 42 | export {updatePasswordModal, updatePasswordModalFormUpdate} from "./actions/update-password-modal"; 43 | export {destroyAccount} from "./actions/destroy-account"; 44 | export verifyAuth from "./utils/verify-auth"; 45 | export {getApiUrl} from "./utils/session-storage"; 46 | export { 47 | hideEmailSignInSuccessModal, 48 | hideEmailSignInErrorModal, 49 | hideOAuthSignInSuccessModal, 50 | hideOAuthSignInErrorModal, 51 | hideSignOutSuccessModal, 52 | hideSignOutErrorModal, 53 | hideEmailSignUpSuccessModal, 54 | hideEmailSignUpErrorModal, 55 | showFirstTimeLoginSuccessModal, 56 | showPasswordResetSuccessModal, 57 | hideFirstTimeLoginSuccessModal, 58 | hidePasswordResetSuccessModal, 59 | showFirstTimeLoginErrorModal, 60 | showPasswordResetErrorModal, 61 | hideFirstTimeLoginErrorModal, 62 | hidePasswordResetErrorModal, 63 | hidePasswordResetRequestSuccessModal, 64 | hidePasswordResetRequestErrorModal, 65 | hideUpdatePasswordSuccessModal, 66 | hideUpdatePasswordErrorModal, 67 | hideDestroyAccountSuccessModal, 68 | hideDestroyAccountErrorModal 69 | } from "./actions/ui"; 70 | 71 | /* utils */ 72 | export {default as fetch} from "./utils/fetch"; 73 | -------------------------------------------------------------------------------- /src/reducers/authenticate.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/authenticate"; 4 | 5 | const initialState = Immutable.fromJS({ 6 | loading: false, 7 | valid: false, 8 | errors: null 9 | }); 10 | 11 | export default createReducer(initialState, { 12 | [A.AUTHENTICATE_START]: state => state.set("loading", true), 13 | 14 | [A.AUTHENTICATE_COMPLETE]: (state) => { 15 | return state.merge({ 16 | loading: false, 17 | errors: null, 18 | valid: true 19 | }); 20 | }, 21 | 22 | [A.AUTHENTICATE_ERROR]: state => state.merge({ 23 | loading: false, 24 | errors: "Invalid token", 25 | valid: false 26 | }) 27 | }); 28 | -------------------------------------------------------------------------------- /src/reducers/configure.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/configure"; 4 | 5 | const initialState = Immutable.fromJS({ 6 | loading: true, 7 | errors: null, 8 | config: null, 9 | endpointKeys: null, 10 | defaultEndpointKey: null, 11 | currentEndpointKey: null 12 | }); 13 | 14 | export default createReducer(initialState, { 15 | [A.CONFIGURE_START]: state => state.set("loading", true), 16 | 17 | [A.STORE_CURRENT_ENDPOINT_KEY]: (state, {currentEndpointKey}) => state.merge({currentEndpointKey}), 18 | 19 | [A.SET_ENDPOINT_KEYS]: (state, {endpointKeys, defaultEndpointKey, currentEndpointKey}) => state.merge({ 20 | endpointKeys, defaultEndpointKey, currentEndpointKey 21 | }), 22 | 23 | [A.CONFIGURE_COMPLETE]: (state, {config}) => state.merge({ 24 | loading: false, 25 | errors: null, 26 | config 27 | }), 28 | 29 | [A.CONFIGURE_ERROR]: (state, {errors}) => state.merge({ 30 | loading: false, 31 | errors 32 | }) 33 | }); 34 | -------------------------------------------------------------------------------- /src/reducers/destroy-account.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/destroy-account"; 4 | import { SET_ENDPOINT_KEYS } from "../actions/configure"; 5 | 6 | const initialState = { 7 | loading: false, 8 | errors: null 9 | }; 10 | 11 | export default createReducer(Immutable.fromJS({}), { 12 | [SET_ENDPOINT_KEYS]: (state, {endpoints}) => state.merge(endpoints.reduce((coll, k) => { 13 | coll[k] = Immutable.fromJS(initialState); 14 | return coll; 15 | }, {})), 16 | 17 | [A.DESTROY_ACCOUNT_START]: (state, {endpoint}) => state.setIn([endpoint, "loading"], true), 18 | 19 | [A.DESTROY_ACCOUNT_COMPLETE]: (state, {endpoint}) => state.merge({ 20 | [endpoint]: initialState 21 | }), 22 | 23 | [A.DESTROY_ACCOUNT_ERROR]: (state, {endpoint, errors}) => state.merge({ 24 | [endpoint]: { 25 | loading: false, 26 | errors 27 | } 28 | }) 29 | }); 30 | -------------------------------------------------------------------------------- /src/reducers/email-sign-in.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/email-sign-in"; 4 | import { SET_ENDPOINT_KEYS } from "../actions/configure"; 5 | 6 | const initialState = { 7 | loading: false, 8 | errors: null, 9 | form: {} 10 | }; 11 | 12 | export default createReducer(Immutable.fromJS({}), { 13 | [SET_ENDPOINT_KEYS]: (state, {endpoints}) => state.merge(endpoints.reduce((coll, k) => { 14 | coll[k] = Immutable.fromJS(initialState); 15 | return coll; 16 | }, {})), 17 | 18 | [A.EMAIL_SIGN_IN_START]: (state, {endpoint}) => state.setIn([endpoint, "loading"], true), 19 | 20 | [A.EMAIL_SIGN_IN_COMPLETE]: (state, {endpoint}) => state.merge({[endpoint]: initialState}), 21 | 22 | [A.EMAIL_SIGN_IN_ERROR]: (state, {endpoint, errors}) => state.mergeDeep({ 23 | [endpoint]: { 24 | loading: false, 25 | errors 26 | } 27 | }), 28 | 29 | [A.EMAIL_SIGN_IN_FORM_UPDATE]: (state, {endpoint, key, value}) => { 30 | return state.mergeDeep({ 31 | [endpoint]: { 32 | form: { 33 | [key]: value 34 | } 35 | } 36 | }); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /src/reducers/email-sign-up.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/email-sign-up"; 4 | import { SET_ENDPOINT_KEYS } from "../actions/configure"; 5 | 6 | const initialState = { 7 | loading: false, 8 | errors: null, 9 | form: {} 10 | }; 11 | 12 | export default createReducer(Immutable.fromJS({}), { 13 | [SET_ENDPOINT_KEYS]: (state, {endpoints}) => state.merge(endpoints.reduce((coll, k) => { 14 | coll[k] = Immutable.fromJS(initialState); 15 | return coll; 16 | }, {})), 17 | 18 | [A.EMAIL_SIGN_UP_START]: (state, {endpoint}) => state.setIn([endpoint, "loading"], true), 19 | 20 | [A.EMAIL_SIGN_UP_COMPLETE]: (state, {endpoint}) => state.merge({ 21 | [endpoint]: initialState 22 | }), 23 | 24 | [A.EMAIL_SIGN_UP_ERROR]: (state, {endpoint, errors}) => state.mergeDeep({ 25 | [endpoint]: { 26 | loading: false, 27 | errors 28 | } 29 | }), 30 | 31 | [A.EMAIL_SIGN_UP_FORM_UPDATE]: (state, {endpoint, key, value}) => { 32 | return state.mergeDeep({ 33 | [endpoint]: { 34 | form: { 35 | [key]: value 36 | } 37 | } 38 | }); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /src/reducers/oauth-sign-in.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/oauth-sign-in"; 4 | import { SET_ENDPOINT_KEYS } from "../actions/configure"; 5 | 6 | const initialState = { 7 | loading: false, 8 | errors: null 9 | }; 10 | 11 | export default createReducer(Immutable.fromJS({}), { 12 | [SET_ENDPOINT_KEYS]: (state, {endpoints}) => state.merge(endpoints.reduce((coll, k) => { 13 | coll[k] = Immutable.fromJS(initialState); 14 | return coll; 15 | }, {})), 16 | 17 | [A.OAUTH_SIGN_IN_START]: (state, {endpoint}) => state.setIn([endpoint, "loading"], true), 18 | 19 | [A.OAUTH_SIGN_IN_COMPLETE]: (state, {endpoint}) => state.mergeDeep({ 20 | [endpoint]: { 21 | loading: false, 22 | errors: null 23 | } 24 | }), 25 | 26 | [A.OAUTH_SIGN_IN_ERROR]: (state, {endpoint, errors}) => state.mergeDeep({ 27 | [endpoint]: { 28 | loading: false, 29 | errors 30 | } 31 | }) 32 | }); 33 | -------------------------------------------------------------------------------- /src/reducers/request-password-reset.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/request-password-reset"; 4 | import { SET_ENDPOINT_KEYS } from "../actions/configure"; 5 | 6 | const initialState = { 7 | loading: false, 8 | errors: null, 9 | form: {} 10 | }; 11 | 12 | export default createReducer(Immutable.fromJS({}), { 13 | [SET_ENDPOINT_KEYS]: (state, {endpoints}) => state.merge(endpoints.reduce((coll, k) => { 14 | coll[k] = Immutable.fromJS(initialState); 15 | return coll; 16 | }, {})), 17 | 18 | [A.REQUEST_PASSWORD_RESET_START]: (state, {endpoint}) => state.setIn([endpoint, "loading"], true), 19 | 20 | [A.REQUEST_PASSWORD_RESET_COMPLETE]: (state, {endpoint}) => state.merge({ 21 | [endpoint]: initialState 22 | }), 23 | 24 | [A.REQUEST_PASSWORD_RESET_ERROR]: (state, {endpoint, errors}) => state.mergeDeep({ 25 | [endpoint]: { 26 | loading: false, 27 | errors 28 | } 29 | }), 30 | 31 | [A.REQUEST_PASSWORD_RESET_FORM_UPDATE]: (state, {endpoint, key, value}) => { 32 | return state.mergeDeep({ 33 | [endpoint]: { 34 | form: { 35 | [key]: value 36 | } 37 | } 38 | }); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /src/reducers/server.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/server"; 4 | 5 | const initialState = Immutable.fromJS({ 6 | user: null, 7 | headers: null, 8 | firstTimeLogin: false, 9 | mustResetPassword: false 10 | }); 11 | 12 | export default createReducer(initialState, { 13 | [A.SS_AUTH_TOKEN_UPDATE]: (state, { 14 | user = null, 15 | headers = null, 16 | mustResetPassword = false, 17 | firstTimeLogin = false 18 | }) => { 19 | return state.merge({ 20 | user, 21 | headers, 22 | mustResetPassword, 23 | firstTimeLogin 24 | }); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /src/reducers/sign-out.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/sign-out"; 4 | import { SET_ENDPOINT_KEYS } from "../actions/configure"; 5 | 6 | const initialState = { 7 | loading: false, 8 | errors: null 9 | }; 10 | 11 | export default createReducer(Immutable.fromJS({}), { 12 | [SET_ENDPOINT_KEYS]: (state, {endpoints}) => state.merge(endpoints.reduce((coll, k) => { 13 | coll[k] = Immutable.fromJS(initialState); 14 | return coll; 15 | }, {})), 16 | 17 | [A.SIGN_OUT_START]: (state, {endpoint}) => state.setIn([endpoint, "loading"], true), 18 | 19 | [A.SIGN_OUT_COMPLETE]: (state, {endpoint}) => state.mergeDeep({ 20 | [endpoint]: { 21 | loading: false, 22 | errors: null 23 | } 24 | }), 25 | 26 | [A.SIGN_OUT_ERROR]: (state, { endpoint, errors }) => { 27 | return state.mergeDeep({ 28 | [endpoint]: { 29 | loading: false, 30 | errors 31 | } 32 | }); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /src/reducers/update-password-modal.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/update-password-modal"; 4 | import { SET_ENDPOINT_KEYS } from "../actions/configure"; 5 | 6 | const initialState = { 7 | loading: false, 8 | errors: null, 9 | form: {} 10 | }; 11 | 12 | export default createReducer(Immutable.fromJS({}), { 13 | [SET_ENDPOINT_KEYS]: (state, {endpoints}) => state.merge(endpoints.reduce((coll, k) => { 14 | coll[k] = Immutable.fromJS(initialState); 15 | return coll; 16 | }, {})), 17 | 18 | [A.UPDATE_PASSWORD_MODAL_START]: (state, {endpoint}) => state.setIn([endpoint, "loading"], true), 19 | 20 | [A.UPDATE_PASSWORD_MODAL_COMPLETE]: (state, {endpoint}) => state.merge({ 21 | [endpoint]: initialState 22 | }), 23 | 24 | [A.UPDATE_PASSWORD_MODAL_ERROR]: (state, {endpoint, errors}) => state.mergeDeep({ 25 | [endpoint]: { 26 | loading: false, 27 | errors 28 | } 29 | }), 30 | 31 | [A.UPDATE_PASSWORD_MODAL_FORM_UPDATE]: (state, {endpoint, key, value}) => { 32 | return state.mergeDeep({ 33 | [endpoint]: { 34 | form: { 35 | [key]: value 36 | } 37 | } 38 | }); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /src/reducers/update-password.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import * as A from "../actions/update-password"; 4 | import { SET_ENDPOINT_KEYS } from "../actions/configure"; 5 | 6 | const initialState = { 7 | loading: false, 8 | errors: null, 9 | form: {} 10 | }; 11 | 12 | export default createReducer(Immutable.fromJS({}), { 13 | [SET_ENDPOINT_KEYS]: (state, {endpoints}) => state.merge(endpoints.reduce((coll, k) => { 14 | coll[k] = Immutable.fromJS(initialState); 15 | return coll; 16 | }, {})), 17 | 18 | [A.UPDATE_PASSWORD_START]: (state, {endpoint}) => state.setIn([endpoint, "loading"], true), 19 | 20 | [A.UPDATE_PASSWORD_COMPLETE]: (state, {endpoint}) => state.merge({ 21 | [endpoint]: initialState 22 | }), 23 | 24 | [A.UPDATE_PASSWORD_ERROR]: (state, {endpoint, errors}) => state.mergeDeep({ 25 | [endpoint]: { 26 | loading: false, 27 | errors 28 | } 29 | }), 30 | 31 | [A.UPDATE_PASSWORD_FORM_UPDATE]: (state, {endpoint, key, value}) => { 32 | return state.mergeDeep({ 33 | [endpoint]: { 34 | form: { 35 | [key]: value 36 | } 37 | } 38 | }); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /src/reducers/user.js: -------------------------------------------------------------------------------- 1 | import Immutable from "immutable"; 2 | import { createReducer } from "redux-immutablejs"; 3 | import { getCurrentEndpointKey } from "../utils/session-storage.js" 4 | import * as authActions from "../actions/authenticate"; 5 | import { EMAIL_SIGN_IN_COMPLETE } from "../actions/email-sign-in"; 6 | import { EMAIL_SIGN_UP_COMPLETE } from "../actions/email-sign-up"; 7 | import { SIGN_OUT_COMPLETE, SIGN_OUT_ERROR } from "../actions/sign-out"; 8 | import { OAUTH_SIGN_IN_COMPLETE } from "../actions/oauth-sign-in"; 9 | import { DESTROY_ACCOUNT_COMPLETE } from "../actions/destroy-account"; 10 | import * as ssActions from "../actions/server"; 11 | import { STORE_CURRENT_ENDPOINT_KEY, SET_ENDPOINT_KEYS } from "../actions/configure"; 12 | 13 | const initialState = Immutable.fromJS({ 14 | attributes: null, 15 | isSignedIn: false, 16 | firstTimeLogin: false, 17 | mustResetPassword: false, 18 | endpointKey: null 19 | }); 20 | 21 | export default createReducer(initialState, { 22 | [authActions.AUTHENTICATE_COMPLETE]: (state, { user }) => state.merge({ 23 | attributes: user, 24 | isSignedIn: true, 25 | endpointKey: getCurrentEndpointKey() 26 | }), 27 | 28 | [ssActions.SS_TOKEN_VALIDATION_COMPLETE]: (state, { user, mustResetPassword, firstTimeLogin }) => { 29 | return state.merge({ 30 | attributes: user, 31 | isSignedIn: true, 32 | firstTimeLogin, 33 | mustResetPassword 34 | }); 35 | }, 36 | 37 | [STORE_CURRENT_ENDPOINT_KEY]: (state, {currentEndpointKey}) => state.set("endpointKey", currentEndpointKey), 38 | [SET_ENDPOINT_KEYS]: (state, {currentEndpointKey}) => state.set("endpointKey", currentEndpointKey), 39 | 40 | [EMAIL_SIGN_IN_COMPLETE]: (state, { endpoint, user }) => state.merge({ 41 | attributes: user.data, 42 | isSignedIn: true, 43 | endpointKey: endpoint 44 | }), 45 | 46 | [EMAIL_SIGN_UP_COMPLETE]: (state, { endpoint, user }) => { 47 | // if registration does not require confirmation, user will be signed in at 48 | // this point. 49 | return (user.uid) 50 | ? state.merge({ 51 | attributes: user, 52 | isSignedIn: true, 53 | endpointKey: endpoint 54 | }) 55 | : state; 56 | }, 57 | 58 | [OAUTH_SIGN_IN_COMPLETE]: (state, { endpoint, user }) => state.merge({ 59 | attributes: user, 60 | isSignedIn: true, 61 | endpointKey: endpoint 62 | }), 63 | 64 | [ssActions.SS_AUTH_TOKEN_UPDATE]: (state, {user, mustResetPassword, firstTimeLogin}) => { 65 | return state.merge({ 66 | mustResetPassword, 67 | firstTimeLogin, 68 | isSignedIn: !!user, 69 | attributes: user 70 | }); 71 | }, 72 | 73 | [authActions.AUTHENTICATE_FAILURE]: state => state.merge(initialState), 74 | [ssActions.SS_TOKEN_VALIDATION_ERROR]: state => state.merge(initialState), 75 | [SIGN_OUT_COMPLETE]: state => state.merge(initialState), 76 | [SIGN_OUT_ERROR]: state => state.merge(initialState), 77 | [DESTROY_ACCOUNT_COMPLETE]: state => state.merge(initialState) 78 | }); 79 | -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const INITIAL_CONFIG_KEY = "default"; 2 | export const DEFAULT_CONFIG_KEY = "defaultConfigKey"; 3 | export const SAVED_CONFIG_KEY = "currentConfigName"; 4 | export const SAVED_CREDS_KEY = "authHeaders"; 5 | -------------------------------------------------------------------------------- /src/utils/fetch.js: -------------------------------------------------------------------------------- 1 | import originalFetch from "isomorphic-fetch"; 2 | import * as C from "./constants"; 3 | import extend from "extend"; 4 | import { 5 | getApiUrl, 6 | retrieveData, 7 | persistData, 8 | getTokenFormat, 9 | getSessionEndpointKey 10 | } from "./session-storage"; 11 | 12 | var isApiRequest = function(url) { 13 | return (url.match(getApiUrl(getSessionEndpointKey()))); 14 | }; 15 | 16 | /** 17 | * Add access token as a bearer token in accordance to RFC 6750 18 | * 19 | * @param {string} accessToken 20 | * @param {object} headers 21 | * @returns {object} New extended headers object, with Authorization property 22 | */ 23 | export function addAuthorizationHeader(accessToken, headers) { 24 | return Object.assign({}, headers, { 25 | Authorization: `Bearer ${accessToken}` 26 | }); 27 | } 28 | 29 | function getAuthHeaders(url) { 30 | if (isApiRequest(url)) { 31 | // fetch current auth headers from storage 32 | var currentHeaders = retrieveData(C.SAVED_CREDS_KEY) || {}, 33 | nextHeaders = {}; 34 | 35 | // bust IE cache 36 | nextHeaders["If-Modified-Since"] = "Mon, 26 Jul 1997 05:00:00 GMT"; 37 | 38 | // set header for each key in `tokenFormat` config 39 | for (var key in getTokenFormat()) { 40 | nextHeaders[key] = currentHeaders[key]; 41 | } 42 | 43 | return addAuthorizationHeader(currentHeaders['access-token'], nextHeaders); 44 | } else { 45 | return {}; 46 | } 47 | } 48 | 49 | function updateAuthCredentials(resp) { 50 | // check config apiUrl matches the current response url 51 | if (isApiRequest(resp.url)) { 52 | // set header for each key in `tokenFormat` config 53 | var newHeaders = {}; 54 | 55 | // set flag to ensure that we don't accidentally nuke the headers 56 | // if the response tokens aren't sent back from the API 57 | var blankHeaders = true; 58 | 59 | // set header key + val for each key in `tokenFormat` config 60 | for (var key in getTokenFormat()) { 61 | newHeaders[key] = resp.headers.get(key); 62 | 63 | if (newHeaders[key]) { 64 | blankHeaders = false; 65 | } 66 | } 67 | 68 | // persist headers for next request 69 | if (!blankHeaders) { 70 | persistData(C.SAVED_CREDS_KEY, newHeaders); 71 | } 72 | } 73 | 74 | return resp; 75 | } 76 | 77 | export default function (url, options={}) { 78 | if (!options.headers) { 79 | options.headers = {} 80 | } 81 | extend(options.headers, getAuthHeaders(url)); 82 | return originalFetch(url, options) 83 | .then(resp => updateAuthCredentials(resp)); 84 | } 85 | -------------------------------------------------------------------------------- /src/utils/handle-fetch-response.js: -------------------------------------------------------------------------------- 1 | export function parseResponse (response) { 2 | let json = response.json(); 3 | if (response.status >= 200 && response.status < 300) { 4 | return json; 5 | } else { 6 | return json.then(err => Promise.reject(err)); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/parse-endpoint-config.js: -------------------------------------------------------------------------------- 1 | import * as C from "./constants"; 2 | import extend from "extend"; 3 | 4 | // base endpoint that other endpoints extend from 5 | const defaultEndpoint = { 6 | apiUrl: "/api", 7 | signOutPath: "/auth/sign_out", 8 | emailSignInPath: "/auth/sign_in", 9 | emailRegistrationPath: "/auth", 10 | accountUpdatePath: "/auth", 11 | accountDeletePath: "/auth", 12 | passwordResetPath: "/auth/password", 13 | passwordUpdatePath: "/auth/password", 14 | tokenValidationPath: "/auth/validate_token", 15 | 16 | authProviderPaths: { 17 | github: "/auth/github", 18 | facebook: "/auth/facebook", 19 | google: "/auth/google_oauth2" 20 | } 21 | }; 22 | 23 | function getFirstObjectKey (obj) { 24 | for (var key in obj) { 25 | return key; 26 | } 27 | }; 28 | 29 | export default function parseEndpointConfig(endpoint, defaultEndpointKey = null) { 30 | // normalize so opts is always an array of objects 31 | if (endpoint.constructor !== Array) { 32 | // single config will always be called 'default' unless set 33 | // by previous session 34 | defaultEndpointKey = C.INITIAL_CONFIG_KEY; 35 | 36 | // config should look like {default: {...}} 37 | var defaultConfig = {}; 38 | defaultConfig[defaultEndpointKey] = endpoint; 39 | 40 | // endpoint should look like [{default: {...}}] 41 | endpoint = [defaultConfig]; 42 | } 43 | 44 | let currentEndpoint = {}; 45 | 46 | // iterate over config items, extend each from defaults 47 | for (var i = 0; i < endpoint.length; i++) { 48 | var configName = getFirstObjectKey(endpoint[i]); 49 | 50 | // set first as default config 51 | if (!defaultEndpointKey) { 52 | defaultEndpointKey = configName; 53 | } 54 | 55 | // save config to `configs` hash 56 | currentEndpoint[configName] = extend( 57 | {}, defaultEndpoint, endpoint[i][configName] 58 | ); 59 | } 60 | 61 | return {defaultEndpointKey, currentEndpoint}; 62 | } 63 | -------------------------------------------------------------------------------- /src/utils/popup.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | var settings = "scrollbars=no,toolbar=no,location=no,titlebar=no,directories=no,status=no,menubar=no"; 3 | 4 | /* istanbul ignore next */ 5 | function getPopupOffset({width, height}) { 6 | var wLeft = window.screenLeft ? window.screenLeft : window.screenX; 7 | var wTop = window.screenTop ? window.screenTop : window.screenY; 8 | 9 | var left = wLeft + (window.innerWidth / 2) - (width / 2); 10 | var top = wTop + (window.innerHeight / 2) - (height / 2); 11 | 12 | return {top, left}; 13 | } 14 | 15 | /* istanbul ignore next */ 16 | function getPopupSize(provider) { 17 | switch (provider) { 18 | case "facebook": 19 | return {width: 580, height: 400}; 20 | 21 | case "google": 22 | return {width: 452, height: 633}; 23 | 24 | case "github": 25 | return {width: 1020, height: 618}; 26 | 27 | case "linkedin": 28 | return {width: 527, height: 582}; 29 | 30 | case "twitter": 31 | return {width: 495, height: 645}; 32 | 33 | case "live": 34 | return {width: 500, height: 560}; 35 | 36 | case "yahoo": 37 | return {width: 559, height: 519}; 38 | 39 | default: 40 | return {width: 1020, height: 618}; 41 | } 42 | } 43 | 44 | /* istanbul ignore next */ 45 | function getPopupDimensions(provider) { 46 | let {width, height} = getPopupSize(provider); 47 | let {top, left} = getPopupOffset({width, height}); 48 | 49 | return `width=${width},height=${height},top=${top},left=${left}`; 50 | } 51 | 52 | /* istanbul ignore next */ 53 | export default function openPopup(provider, url, name) { 54 | return window.open(url, name, `${settings},${getPopupDimensions(provider)}`) 55 | } 56 | -------------------------------------------------------------------------------- /src/views/TokenBridge.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | 4 | class TokenBridge extends React.Component { 5 | render () { 6 | return ( 7 |