├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── demo ├── app.component.ts ├── demo.module.ts ├── entry.ts ├── index.html └── server │ └── server.js ├── e2e ├── login.spec.ts └── register.spec.ts ├── karma.conf.js ├── keypair.enc ├── package-lock.json ├── package.json ├── protractor.conf.js ├── src ├── authport │ ├── authport.component.ts │ └── index.ts ├── email-verification │ ├── email-verification.component.ts │ └── index.ts ├── extensions │ └── ionic │ │ └── index.js ├── forgot-password │ ├── forgot-password.component.ts │ └── index.ts ├── index.ts ├── login │ ├── index.ts │ └── login.component.ts ├── register │ ├── index.ts │ └── register.component.ts ├── resend-email-verification │ ├── index.ts │ └── resend-email-verification.component.ts ├── reset-password │ ├── index.ts │ └── reset-password.component.ts ├── shared │ ├── account.ts │ └── index.ts ├── stormpath.module.ts └── stormpath │ ├── auth.token.ts │ ├── event.manager.ts │ ├── index.ts │ ├── stormpath.config.ts │ ├── stormpath.http.ts │ ├── stormpath.service.ts │ └── token-store.manager.ts ├── static ├── angular.png └── stormpath.png ├── test ├── authport │ └── authport.component.spec.ts ├── entry.ts ├── forgot-password │ └── forgot-password.component.spec.ts ├── login │ └── login.component.spec.ts ├── mocks │ ├── helper.ts │ └── stormpath.mock.service.ts ├── register │ └── register.component.spec.ts └── stormpath │ ├── stormpath.config.spec.ts │ ├── stormpath.http.spec.ts │ └── stormpath.service.spec.ts ├── tsconfig-ngc.json ├── tsconfig.json ├── tslint.json ├── typedoc.json ├── webpack.config.js ├── webpack.config.umd.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | .DS_Store 4 | node_modules 5 | coverage 6 | npm-debug.log 7 | demo/demo.js 8 | demo/demo.js.map 9 | dist/ 10 | docs/ 11 | *.tgz 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | script: 5 | - npm test 6 | #- npm start & 7 | #- npm run e2e # run protractor tests against local instance 8 | #- npm run e2e:cookies # run protractor tests against local instance with cookie storage 9 | #- npm run e2e:client # run protractor tests against client api 10 | #- npm run e2e:client+cookies # run protractor tests against client api with cookie storage 11 | #- npm run typedoc 12 | cache: 13 | directories: 14 | - node_modules 15 | addons: 16 | apt: 17 | sources: 18 | - google-chrome 19 | packages: 20 | - google-chrome-stable 21 | - google-chrome-beta 22 | before_install: 23 | - if [ $TRAVIS_PULL_REQUEST == 'false' ]; then 24 | test -z "$BUILD_DOCS" || openssl aes-256-cbc -K $encrypted_d98a3227cf72_key -iv $encrypted_d98a3227cf72_iv -in keypair.enc -out ~/.ssh/id_rsa -d; 25 | test -z "$BUILD_DOCS" || chmod 600 ~/.ssh/id_rsa; 26 | fi 27 | - export DISPLAY=:99.0 28 | - sh -e /etc/init.d/xvfb start 29 | after_success: 30 | - test -z "$BUILD_DOCS" || CURRENT_HASH=`git rev-parse HEAD` 31 | - test -z "$BUILD_DOCS" || RELEASE_VERSION=`git tag | xargs -I@ git log --format=format:"%ai 32 | @%n" -1 @ | sort | awk '{print $4}' | tail -n 1` 33 | - test -z "$BUILD_DOCS" || RELEASE_HASH=`git rev-list $RELEASE_VERSION -n 1` 34 | - test -z "$BUILD_DOCS" || if [ "$CURRENT_HASH" = "$RELEASE_HASH" ]; then DEPLOY_DOCS=true; 35 | fi 36 | - test -z "$DEPLOY_DOCS" || git config --global user.email "evangelists@stormpath.com" 37 | - test -z "$DEPLOY_DOCS" || git config --global user.name "stormpath-sdk-angular Auto 38 | Doc Build" 39 | - test -z "$DEPLOY_DOCS" || git clone git@github.com:stormpath/stormpath.github.io.git 40 | - test -z "$DEPLOY_DOCS" || cd stormpath.github.io 41 | - test -z "$DEPLOY_DOCS" || git fetch origin source:source 42 | - test -z "$DEPLOY_DOCS" || git checkout source 43 | - test -z "$DEPLOY_DOCS" || mkdir -p source/angular 44 | - test -z "$DEPLOY_DOCS" || rm -rf source/angular/api 45 | - test -z "$DEPLOY_DOCS" || cp -r ../docs source/angular/api 46 | - test -z "$DEPLOY_DOCS" || git add --all 47 | - test -z "$DEPLOY_DOCS" || git commit -m "stormpath-sdk-angular release $RELEASE_VERSION" 48 | - test -z "$DEPLOY_DOCS" || git push origin source 49 | matrix: 50 | include: 51 | - env: BUILD_DOCS=true 52 | node_js: node 53 | notifications: 54 | webhooks: 55 | on_success: change 56 | on_failure: always 57 | on_start: false 58 | env: 59 | global: 60 | - secure: Ql5jkxDVxmLRB+enk2SXT7ZuZQbAv0txQbMYv9iFOk/eezyA+joep3VAze7h8gDsC7eipUisuPNHa0wvJklpacTrNsUfK/HM/IPUwMNdyBEX+lk28L1oT199Aijj51KBH7OuIgnTHp6RP4SahonY9iBRyUhOlAcyan4aCYvMIr9sIvx9PJAPeO5/4olR9UnIIjPsLs8+xuh1nbnu/nb6BClA3wKriSe64K+vREl3x863BPTDzVDBCKbB5F5QTRuNSlJ7NT6JnbqqH6Dt5LQYwu/h4l3gFFMzROKzOcId62EfdMlpyH/akJqxqqKuCd9cQ3eo42wSIu3zD2eq3rhEj6g4WjKilMPxYIjEah3f+75qmby7U8qEJGsuyd1J0nWRXm0e+Uei9w9TVV+shjwH85QVz09w5ktOzZm595pV9v/2gx3dzbPZ/g/xHlhTcvFdxt587EDUEeejXKHFN2/kzQTqc04ZBJ5P2E0SbuoOGv6p030VsZFVuUlZaRcjempXOD7SuoRZ/6v7mpzvT/uHuQdJPzIEUejrLUnCbJ3k/VU9jXp0V88IAl+/vF/4h9e6zuo4eLcQ0Z9q4+hMRXZbiljSENty5tdgGzUfz3bwqWaL8AuFrDyA3wdlT1tqSIjbq1yvD9IzHRToq2jvjqlidbeDKwuh+GlF79+CTjlgV14= 61 | - secure: lLFXP0J7p1rwSAIYzTClDaS05Iv3w8uK1DysdqCFtS180QEP8KN5uWwJQ4+YPklrzPDrE7jN6JJgtDvch9+C4ea1Y/Tuvct3snslWyOCfrg0UKRKkNOv8n/QAgczjoTZkniCc/PElKCyYP1CRPv4jN4AyhOzgSVZ2dzm5+BnKsPf02U6dSn8SO37gOmJGinVPyXLzUpFgvShC5sz1zEahPhr87BM7yz2ImqgibXqPvHDlw0E0ZKYXoTB4w6jaVrl/R743rjJ7PNCvSs2AQT/MZoHBNHGJby3NdSmV4gOCxIhe/eu5AJ6Nu95GRy4q9ThxqlBILJseaQr8OII5ceQ5eaADgaZHqW0fVXZ3VSjdUgmxrZ8ZaVchrgZKDBbNG/tImcPAkjWP6YScUfyqPjaE21W/ckKuwq6mgtPR6mY2z1gQAie0q0mWlbWfGfXE0cvCo0z6Eyr7zsl5NS54ywdjtns8KaN+O2+SrgJbhHunxkKr3ZE/JWfmYyBie4Hb+7inmOcEFmDQJfQ5AaXiHONBnQAIq7RHE73jykpVNUog9nvD7jLSHpPj1+kHg0c18ADzApyP0BCtj42mEnfHlPpoRyrM7e4UxQtlt1WP7zOtWmF0HI9QJ6PcqG2qarC+HHwGs0dbtAU+itYvGcA4e2AD7N6rkbzHutP1ymixI+Krns= 62 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | # [0.2.0](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.1.7...v0.2.0) (2017-11-21) 7 | 8 | 9 | 10 | 11 | ## [0.2.0](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.1.6...v0.2.0) (2017-11-21) 12 | 13 | 14 | ### Bug Fixes 15 | 16 | * **package.json:** Remove angular-cookie and webstorage from devDependencies ([833acbb](https://github.com/stormpath/stormpath-sdk-angular/commit/833acbb)) 17 | 18 | 19 | 20 | 21 | ## [0.1.6](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.1.5...v0.1.6) (2017-02-04) 22 | 23 | 24 | ### Bug Fixes 25 | 26 | * **dependencies:** Remove Ionic pages and module ([a3e837b](https://github.com/stormpath/stormpath-sdk-angular/commit/a3e837b)) 27 | 28 | 29 | 30 | 31 | ## [0.1.5](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.1.4...v0.1.5) (2017-02-03) 32 | 33 | 34 | ### Features 35 | 36 | * **templates:** Add pages for Ionic with Ionic components in templates ([4360bab](https://github.com/stormpath/stormpath-sdk-angular/commit/4360bab)) 37 | 38 | 39 | 40 | 41 | ## [0.1.4](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.1.3...v0.1.4) (2017-01-30) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * **build:** Move ng2-webstorage and angular2-cookies to dependencies since they're required ([d05d730](https://github.com/stormpath/stormpath-sdk-angular/commit/d05d730)), closes [#62](https://github.com/stormpath/stormpath-sdk-angular/issues/62) 47 | 48 | 49 | 50 | 51 | ## [0.1.3](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.1.2...v0.1.3) (2017-01-30) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * **angular:** Change to peerDependencies and allow Angular 2.0.0+ ([cbf9637](https://github.com/stormpath/stormpath-sdk-angular/commit/cbf9637)) 57 | 58 | 59 | 60 | 61 | ## [0.1.2](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.1.1...v0.1.2) (2017-01-27) 62 | 63 | 64 | ### Bug Fixes 65 | 66 | * **http:** Fix compatibility with Angular versions < 2.3 ([b3ae345](https://github.com/stormpath/stormpath-sdk-angular/commit/b3ae345)) 67 | * **logout:** Fix logout fails when no access token found ([6fcaeda](https://github.com/stormpath/stormpath-sdk-angular/commit/6fcaeda)) 68 | 69 | 70 | 71 | 72 | ## [0.1.1](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.1.0...v0.1.1) (2017-01-20) 73 | 74 | 75 | ### Bug Fixes 76 | 77 | * **config:** Allow autoAuthorizedUris to be modified ([#48](https://github.com/stormpath/stormpath-sdk-angular/issues/48)) ([ca81cbe](https://github.com/stormpath/stormpath-sdk-angular/commit/ca81cbe)), closes [#47](https://github.com/stormpath/stormpath-sdk-angular/issues/47) 78 | * **dependencies:** Lock down version of ng2-webstorage to 1.4.3. ([6b00209](https://github.com/stormpath/stormpath-sdk-angular/commit/6b00209)), closes [#46](https://github.com/stormpath/stormpath-sdk-angular/issues/46) 79 | * **security:** Set autocomplete="off" for all forms ([#52](https://github.com/stormpath/stormpath-sdk-angular/issues/52)) ([e85dc7d](https://github.com/stormpath/stormpath-sdk-angular/commit/e85dc7d)), closes [#51](https://github.com/stormpath/stormpath-sdk-angular/issues/51) 80 | 81 | 82 | 83 | 84 | ## [0.1.0](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.0.5...v0.1.0) (2017-01-12) 85 | 86 | 87 | ### Bug Fixes 88 | 89 | * **build:** Fix installation by downgrading to TypeScript 2.0.10. ([#45](https://github.com/stormpath/stormpath-sdk-angular/issues/45)) ([2ec5ae3](https://github.com/stormpath/stormpath-sdk-angular/commit/2ec5ae3)) 90 | * **demo:** Fix logout link so event propogation is stopped ([7758ed2](https://github.com/stormpath/stormpath-sdk-angular/commit/7758ed2)), closes [#22](https://github.com/stormpath/stormpath-sdk-angular/issues/22) 91 | * **forms:** Add required field validation to login and forgot password forms. ([#40](https://github.com/stormpath/stormpath-sdk-angular/issues/40)) ([ca2f339](https://github.com/stormpath/stormpath-sdk-angular/commit/ca2f339)), closes [#24](https://github.com/stormpath/stormpath-sdk-angular/issues/24) 92 | * **webpack:** Change X-Stormpath-Agent to read versions from package.json and Angular ([8e3747e](https://github.com/stormpath/stormpath-sdk-angular/commit/8e3747e)) 93 | 94 | 95 | ### Features 96 | 97 | * **oauth:** Add support for OAuth and Client API ([#37](https://github.com/stormpath/stormpath-sdk-angular/issues/37)) ([e18ed49](https://github.com/stormpath/stormpath-sdk-angular/commit/e18ed49)) 98 | * **tests:** Add Protractor configuration and tests ([41c216a](https://github.com/stormpath/stormpath-sdk-angular/commit/41c216a)) 99 | 100 | 101 | ### Performance Improvements 102 | 103 | * **webpack:** Upgrade to webpack 2 for better performance and ES6 ([9c8b345](https://github.com/stormpath/stormpath-sdk-angular/commit/9c8b345)) 104 | 105 | 106 | 107 | 108 | ## [0.0.5](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.0.4...v0.0.5) (2016-12-14) 109 | 110 | 111 | ### Bug Fixes 112 | 113 | * **cors:** Use withCredentials: true for requests so CORS works ([991af7b](https://github.com/stormpath/stormpath-sdk-angular/commit/991af7b)) 114 | 115 | 116 | 117 | ## [0.0.4](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.0.3...v0.0.4) (2016-12-06) 118 | 119 | 120 | ### Bug Fixes 121 | 122 | * resolving conflicted typedoc dependency ([da6fa97](https://github.com/stormpath/stormpath-sdk-angular/commit/da6fa97)) 123 | * **templates:** Fixed 404 in templates by embedding them ([cd0d377](https://github.com/stormpath/stormpath-sdk-angular/commit/cd0d377)) 124 | * **typedoc:** Fix typedoc listing of classes in right column ([0a464a4](https://github.com/stormpath/stormpath-sdk-angular/commit/0a464a4)) 125 | 126 | 127 | 128 | 129 | ## [0.0.4](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.0.3...v0.0.4) (2016-12-06) 130 | 131 | 132 | ### Bug Fixes 133 | 134 | * **templates:** Fixed 404 in templates by embedding them ([98982c4](https://github.com/stormpath/stormpath-sdk-angular/commit/98982c4)) 135 | * **typedoc:** Fix typedoc listing of classes in right column ([0a464a4](https://github.com/stormpath/stormpath-sdk-angular/commit/0a464a4)) 136 | 137 | 138 | ## [0.0.3](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.0.2...v0.0.3) (2016-12-02) 139 | 140 | 141 | ### Features 142 | 143 | * Add support for overridding templates ([bc4e6b0](https://github.com/stormpath/stormpath-sdk-angular/commit/bc4e6b0)) 144 | 145 | 146 | ## [0.0.2](https://github.com/stormpath/stormpath-sdk-angular/compare/v0.0.1...v0.0.2) (2016-11-11) 147 | 148 | ### Bug Fixes 149 | 150 | * Change user-agent to be stormpath-sdk-angular ([021d882](https://github.com/stormpath/stormpath-sdk-angular/commit/021d882)) 151 | 152 | 153 | ## 0.0.1 (2016-11-10) 154 | 155 | * Initial Release 156 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Stormpath 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright [yyyy] [name of copyright owner] 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stormpath is Joining Okta 2 | We are incredibly excited to announce that [Stormpath is joining forces with Okta](https://stormpath.com/blog/stormpaths-new-path?utm_source=github&utm_medium=readme&utm-campaign=okta-announcement). Please visit [the Migration FAQs](https://stormpath.com/oktaplusstormpath?utm_source=github&utm_medium=readme&utm-campaign=okta-announcement) for a detailed look at what this means for Stormpath users. 3 | 4 | We're available to answer all questions at [support@stormpath.com](mailto:support@stormpath.com). 5 | 6 | # Stormpath Angular SDK 7 | [![Build Status](https://travis-ci.org/stormpath/stormpath-sdk-angular.svg?branch=master)](https://travis-ci.org/stormpath/stormpath-sdk-angular) 8 | [![npm version](https://badge.fury.io/js/angular-stormpath.svg)](http://badge.fury.io/js/angular-stormpath) 9 | [![devDependency Status](https://david-dm.org/stormpath/stormpath-sdk-angular/dev-status.svg)](https://david-dm.org/stormpath/stormpath-sdk-angular#info=devDependencies) 10 | [![GitHub issues](https://img.shields.io/github/issues/stormpath/stormpath-sdk-angular.svg)](https://github.com/stormpath/stormpath-sdk-angular/issues) 11 | [![GitHub stars](https://img.shields.io/github/stars/stormpath/stormpath-sdk-angular.svg)](https://github.com/stormpath/stormpath-sdk-angular/stargazers) 12 | [![GitHub license](https://img.shields.io/badge/license-APACHE-red.svg)](https://raw.githubusercontent.com/stormpath/stormpath-sdk-angular/master/LICENSE) 13 | 14 | > Angular Components for integrating with Stormpath's API 15 | 16 |
17 | 18 | 19 | 20 | 21 |
22 | 23 | ## Table of contents 24 | 25 | - [About](#about) 26 | - [Installation](#installation) 27 | - [Configuration](#configuration) 28 | - [Documentation](#documentation) 29 | - [Development](#development) 30 | - [License](#licence) 31 | 32 | ## About 33 | 34 | Angular SDK for Stormpath's API. If you're looking for **AngularJS** support, please see [stormpath-sdk-angularjs](https://github.com/stormpath/stormpath-sdk-angularjs). 35 | 36 | ## Installation 37 | 38 | Install through npm: 39 | ``` 40 | npm install --save angular-stormpath 41 | ``` 42 | 43 | Then use it in your app like so: 44 | 45 | ```typescript 46 | import { Component } from '@angular/core'; 47 | import { AuthPortComponent } from 'angular-stormpath'; 48 | 49 | @Component({ 50 | selector: 'app-root', 51 | template: `
52 |

53 | Welcome, {{ ( user$ | async ).fullName }}. 54 |

55 | 56 | 59 |
60 | 61 | ` 62 | }) 63 | export class AppComponent extends AuthPortComponent { 64 | } 65 | ``` 66 | 67 | If you're using a version of Angular < 2.3, extending AuthPortComponent won't work for you. As an alternative, you can inject the `Stormpath` service into your component, subscribe to `stormpath.user$` and implement `logout()` yourself. 68 | 69 | ```typescript 70 | import { Account, Stormpath } from 'angular-stormpath'; 71 | ... 72 | export class AppComponent { 73 | user$: Observable; 74 | 75 | constructor(private stormpath: Stormpath) { 76 | this.user$ = this.stormpath.user$; 77 | } 78 | 79 | logout(): void { 80 | this.stormpath.logout(); 81 | } 82 | } 83 | ``` 84 | 85 | You may also find it useful to view the [demo source](https://github.com/stormpath/stormpath-sdk-angular/blob/master/demo/app.component.ts). 86 | 87 | ### Configuration 88 | 89 | To override the endpoint prefix or URIs for the various endpoints, you can modify the defaults in [StormpathConfiguration](https://github.com/stormpath/stormpath-sdk-angular/blob/master/src/stormpath/stormpath.config.ts). 90 | 91 | For example, to override the endpoint prefix and `/me` URI in [demo.module.ts](https://github.com/stormpath/stormpath-sdk-angular/blob/master/demo/demo.module.ts), change it to the following: 92 | 93 | ```typescript 94 | export function stormpathConfig(): StormpathConfiguration { 95 | let spConfig: StormpathConfiguration = new StormpathConfiguration(); 96 | spConfig.endpointPrefix = 'http://api.mycompany.com'; 97 | spConfig.meUri = '/account'; 98 | return spConfig; 99 | } 100 | 101 | @NgModule({ 102 | declarations: [AppComponent], 103 | imports: [BrowserModule, StormpathModule], 104 | bootstrap: [AppComponent], 105 | providers: [{ 106 | provide: StormpathConfiguration, useFactory: stormpathConfig 107 | }] 108 | }) 109 | export class DemoModule { 110 | } 111 | ``` 112 | 113 | #### OAuth 114 | 115 | If your Angular app is on a different domain than your endpoints, OAuth will be used for login/logout. The access token will be stored in localStorage under the name `stormpath:token` and it will be automatically added as an `Authorization` header when you send HTTP requests to your `/me` endpoint. 116 | 117 | If you'd like to add this header to additional URLs, you'll need to add them as follows: 118 | 119 | ```typescript 120 | let config: StormpathConfiguration = new StormpathConfiguration(); 121 | config.autoAuthorizedUris.push(new RegExp('http://localhost:3000/myapi/*)'); 122 | ``` 123 | 124 | #### Templates 125 | 126 | To override templates, you can use the `customTemplate` attribute on a component. Below is an example of [app.component.ts](https://github.com/stormpath/stormpath-sdk-angular/blob/master/demo/app.component.ts) with a custom `` and ``: 127 | 128 | ```typescript 129 | import { Component, OnInit } from '@angular/core'; 130 | import { Observable } from 'rxjs/Observable'; 131 | import { Stormpath, StormpathErrorResponse, Account, LoginFormModel } from 'angular-stormpath'; 132 | 133 | @Component({ 134 | selector: 'demo-app', 135 | template: `
136 |
137 |

138 | Welcome, {{ ( user$ | async ).fullName }}. 139 |

140 |
141 | 142 | 145 |
146 | 147 | 148 |
{{error}}
149 |
150 | 151 | 152 | 153 | 154 | 155 |
156 |
157 | 158 | 159 |
160 |

Sign In

161 | 162 |
163 |
164 | 165 | 166 | 167 |
` 168 | }) 169 | export class AppComponent implements OnInit { 170 | 171 | protected loginFormModel: LoginFormModel; 172 | protected error: string; 173 | 174 | private user$: Observable; 175 | private loggedIn$: Observable; 176 | private _login: boolean; 177 | private _register: boolean; 178 | 179 | constructor(public stormpath: Stormpath) { 180 | this.loginFormModel = { 181 | login: '', 182 | password: '' 183 | }; 184 | } 185 | 186 | ngOnInit(): void { 187 | this._login = true; 188 | this._register = false; 189 | this.user$ = this.stormpath.user$; 190 | this.loggedIn$ = this.user$.map(user => !!user); 191 | } 192 | 193 | showLogin(): void { 194 | this._login = !(this._register = false); 195 | } 196 | 197 | showRegister(): void { 198 | this._register = !(this._login = false); 199 | } 200 | 201 | login(): void { 202 | this.error = null; 203 | this.stormpath.login(this.loginFormModel) 204 | .subscribe(null, (error: StormpathErrorResponse) => { 205 | this.error = error.message; 206 | }); 207 | } 208 | 209 | logout(): void { 210 | this.stormpath.logout(); 211 | } 212 | } 213 | ``` 214 | 215 | **NOTE:** One problem with this approach is you'll need to copy all the referenced variables in the template into your component. Another option is to extend the existing Stormpath component and override its `template` variable in `@Component`. This is the recommended solution if you're using Angular 2.3+. 216 | 217 | #### Access Token Storage 218 | 219 | To change the storage mechanism for authentication tokens from localStorage (the default), to cookies, change the class for the 'tokenStore' provider. 220 | 221 | ```typescript 222 | { 223 | provide: 'tokenStore', useClass: CookieTokenStoreManager 224 | } 225 | ``` 226 | 227 | Below is a list of direct links to each component. You can use the HTML defined in their `template` variable as a starting point for your customizations. 228 | 229 | * [authport.component.ts](https://github.com/stormpath/stormpath-sdk-angular/blob/master/src/authport/authport.component.ts) 230 | * [email-verification.component.ts](https://github.com/stormpath/stormpath-sdk-angular/blob/master/src/email-verification/email-verification.component.ts) 231 | * [forgot-password.component.ts](https://github.com/stormpath/stormpath-sdk-angular/blob/master/src/forgot-password/forgot-password.component.ts) 232 | * [login.component.ts](https://github.com/stormpath/stormpath-sdk-angular/blob/master/src/login/login.component.ts) 233 | * [register.component.ts](https://github.com/stormpath/stormpath-sdk-angular/blob/master/src/register/register.component.ts) 234 | * [resend-email-verification.component.ts](https://github.com/stormpath/stormpath-sdk-angular/blob/master/src/resend-email-verification/resend-email-verification.component.ts) 235 | * [reset-password-component.component.ts](https://github.com/stormpath/stormpath-sdk-angular/blob/master/src/reset-password/reset-password.component.ts) 236 | 237 | ### Usage without a module bundler 238 | ``` 239 | 240 | 243 | ``` 244 | 245 | ## Documentation 246 | All documentation is auto-generated from the source via typedoc and can be viewed at https://docs.stormpath.com/angular/api. 247 | 248 | ## Development 249 | 250 | ### Prepare your environment 251 | * Install [Node.js](http://nodejs.org/) and npm 252 | * Install local dev dependencies: `npm install` while current directory is this repo 253 | 254 | ### Development server 255 | Run `npm start` to start a development server on port 8000 with auto reload + tests. 256 | 257 | ### Testing 258 | Run `npm test` to run tests once or `npm run test:watch` to continually run tests. 259 | 260 | ### Using npm link 261 | 262 | If you want to use `npm link` to use this module in another Angular project, follow the steps below: 263 | 264 | 1. Build this project using `npm run build:dist`. 265 | 2. Run `npm link` in this project's directory. 266 | 3. Run `npm link angular-stormpath` in the ``. 267 | 4. Run `rm -rf node_modules/angular-stormpath/node_modules` in ``. 268 | 5. Manually install dependencies required by `angular-stormpath`: 269 | 270 | ``` 271 | npm install ng2-webstorage angular2-cookie --save 272 | ``` 273 | 274 | You'll know if this doesn't work because you'll see an error message like the following: 275 | 276 | ``` 277 | Type 'Observable' is not assignable to type 'Observable'. ' 278 | Property 'source' is protected but type 'Observable' is not a class derived from 'Observable'. 279 | ``` 280 | 281 | When this happens, you can use the ol' fashioned copy and paste method. If you have `stormpath-sdk-angular` in an adjacent directory, the commands below should work on your project that has `angular-stormpath` already installed. You will need to run `npm run build:dist` every time you change code in this project. 282 | 283 | ``` 284 | rm -rf node_modules/angular-stormpath/dist 285 | cp -r ../stormpath-sdk-angular/dist node_modules/angular-stormpath/. 286 | cp ../stormpath-sdk-angular/package.json node_modules/angular-stormpath/. 287 | ``` 288 | 289 | ### Release 290 | 291 | Bump the version in package.json (once the module hits 1.0 this will become automatic). 292 | 293 | ```bash 294 | npm run release 295 | ``` 296 | 297 | For more information, see [generator-angular-library](https://www.npmjs.com/package/generator-angular-library). 298 | It was used to create this project. 299 | 300 | ## License 301 | 302 | Apache-2.0 © [Stormpath](https://stormpath.com) 303 | -------------------------------------------------------------------------------- /demo/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { AuthPortComponent } from 'angular-stormpath'; 3 | 4 | @Component({ 5 | selector: 'demo-app', 6 | template: `
7 |
8 |
9 |

Hello

10 |
11 |
12 |
13 |

14 | Welcome, {{ ( user$ | async ).fullName }} 15 |

16 |
17 | 18 | 21 |
22 | 23 | 24 | 25 |
` 26 | }) 27 | export class AppComponent extends AuthPortComponent { 28 | } 29 | -------------------------------------------------------------------------------- /demo/demo.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { AppComponent } from './app.component'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { 6 | StormpathConfiguration, StormpathModule, CookieTokenStoreManager, 7 | LocalStorageTokenStoreManager 8 | } from 'angular-stormpath'; 9 | 10 | let params: any = {}; 11 | 12 | export function stormpathConfig(): StormpathConfiguration { 13 | let config: StormpathConfiguration = new StormpathConfiguration(); 14 | 15 | if (location.search) { 16 | let parts: Array = location.search.substring(1).split('&'); 17 | 18 | for (let i: number = 0; i < parts.length; i++) { 19 | let nv: any = parts[i].split('='); 20 | if (!nv[0]) continue; 21 | params[nv[0]] = nv[1] || true; 22 | } 23 | } 24 | 25 | // allow switching between local server and client api 26 | if (params['api']) { 27 | config.endpointPrefix = params['api']; 28 | console.info('Configured endpointPrefix to be: ' + params['api']); 29 | } 30 | 31 | return config; 32 | } 33 | 34 | // allow switching between localStorage and cookies 35 | let tokenStore: any; 36 | if (params['storage'] && (params['storage'] === 'cookies')) { 37 | tokenStore = CookieTokenStoreManager; 38 | console.info('Configured token storage to use cookies'); 39 | } else { 40 | tokenStore = LocalStorageTokenStoreManager; 41 | console.info('Configured token storage to use localStorage'); 42 | } 43 | 44 | @NgModule({ 45 | declarations: [AppComponent], 46 | imports: [BrowserModule, FormsModule, StormpathModule], 47 | bootstrap: [AppComponent], 48 | providers: [ 49 | { 50 | provide: StormpathConfiguration, useFactory: stormpathConfig 51 | }, 52 | { 53 | provide: 'tokenStore', useClass: tokenStore 54 | } 55 | ] 56 | }) 57 | export class DemoModule { 58 | } 59 | -------------------------------------------------------------------------------- /demo/entry.ts: -------------------------------------------------------------------------------- 1 | import 'core-js'; 2 | import 'zone.js/dist/zone'; 3 | import { enableProdMode } from '@angular/core'; 4 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 5 | import { DemoModule } from './demo.module'; 6 | 7 | declare var ENV: string; 8 | if (ENV === 'production') { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(DemoModule); 13 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Stormpath Angular SDK 9 | 10 | 11 | 12 | 19 | 20 | 31 | 32 |
33 | Loading Stormpath... 34 |
35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /demo/server/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var path = require('path'); 5 | var stormpath = require('express-stormpath'); 6 | 7 | /** 8 | * Create the Express application. 9 | */ 10 | var app = express(); 11 | 12 | /** 13 | * The 'trust proxy' setting is required if you will be deploying your 14 | * application to Heroku, or any other environment where you will be behind an 15 | * HTTPS proxy. 16 | */ 17 | app.set('trust proxy', true); 18 | 19 | /* 20 | We need to setup a static file server that can serve the assets for the 21 | angular application. We don't need to authenticate those requests, so we 22 | setup this server before we initialize Stormpath. 23 | */ 24 | 25 | app.use('/', express.static(path.join(__dirname, '..'), {redirect: false})); 26 | 27 | 28 | app.use(function (req, res, next) { 29 | console.log(new Date, req.method, req.url); 30 | next(); 31 | }); 32 | 33 | // app.post('/forgot', function (req,res) { 34 | // res.status(500).json({message:'foo'}); 35 | // }) 36 | 37 | /** 38 | * Now we initialize Stormpath, any middleware that is registered after this 39 | * point will be protected by Stormpath. 40 | */ 41 | 42 | console.log('Initializing Stormpath'); 43 | 44 | app.use(stormpath.init(app, { 45 | web: { 46 | // produces: ['text/html'], 47 | spa: { 48 | enabled: true, 49 | view: path.join(__dirname, '..', 'index.html') 50 | }, 51 | me: { 52 | // enabled: false, 53 | expand: { 54 | customData: true, 55 | groups: true 56 | } 57 | } 58 | } 59 | })); 60 | 61 | /** 62 | * Now that our static file server and Stormpath are configured, we let Express 63 | * know that any other route that hasn't been defined should load the Angular 64 | * application. It then becomes the responsibility of the Angular application 65 | * to define all view routes, and redirect to the home page if the URL is not 66 | * defined. 67 | */ 68 | app.route('/*') 69 | .get(function (req, res) { 70 | res.sendFile(path.join(__dirname, '..', 'index.html')); 71 | }); 72 | 73 | /** 74 | * Start the web server. 75 | */ 76 | app.on('stormpath.ready', function () { 77 | console.log('Stormpath Ready'); 78 | }); 79 | 80 | var port = process.env.PORT || 3000; 81 | app.listen(port, function () { 82 | console.log('Application running at http://localhost:' + port); 83 | }); 84 | -------------------------------------------------------------------------------- /e2e/login.spec.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by, $ } from 'protractor'; 2 | 3 | describe('login', () => { 4 | 5 | const username = element(by.id('loginField')); 6 | const password = element(by.id('passwordField')); 7 | const logout = element(by.id('logout')); 8 | const title = element.all(by.css('h1')).first(); 9 | 10 | beforeAll(() => { 11 | browser.get(''); 12 | }); 13 | 14 | it('should fail to login with bad password', () => { 15 | expect(element.all(by.css('h1')).first().getText()).toMatch(/Hello/); 16 | 17 | username.sendKeys('admin'); 18 | password.sendKeys('foo'); 19 | element(by.css('button[type=submit]')).click(); 20 | 21 | let error = $('.alert-danger').getText(); 22 | expect(error).toMatch(/Invalid username or password./); 23 | }); 24 | 25 | it('should login successfully with valid account', () => { 26 | expect(title.getText()).toMatch(/Hello/); 27 | 28 | username.clear(); 29 | username.sendKeys('matt.raible+user@stormpath.com'); 30 | password.clear(); 31 | password.sendKeys('Stormpath1'); 32 | element(by.css('button[type=submit]')).click(); 33 | 34 | // sleep for 1 second because client API takes a smidge longer 35 | browser.sleep(1000); 36 | expect(title.getText()).toMatch(/Welcome, Hip User/); 37 | 38 | logout.click(); 39 | 40 | expect(title.getText()).toMatch(/Hello/); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /e2e/register.spec.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by, $, protractor } from 'protractor'; 2 | 3 | describe('register', () => { 4 | 5 | const registerLink = element(by.id('register')); 6 | const firstName = element(by.id('givenName')); 7 | const lastName = element(by.id('surname')); 8 | const email = element(by.id('email')); 9 | const password = element(by.id('password')); 10 | const logout = element(by.id('logout')); 11 | const title = element.all(by.css('h1')).first(); 12 | 13 | beforeEach(() => { 14 | browser.get(''); 15 | registerLink.click(); 16 | expect(firstName.isDisplayed()); 17 | }); 18 | 19 | afterAll(() => { 20 | // delete user via the REST API 21 | }); 22 | 23 | it('should fail with short password', () => { 24 | firstName.sendKeys('Test'); 25 | lastName.sendKeys('User'); 26 | email.sendKeys('matt.raible+user' + getRandomInt(0, 1000) + '@stormpath.com'); 27 | password.sendKeys('short'); 28 | element(by.css('button[type=submit]')).click(); 29 | 30 | let error = $('.alert-danger').getText(); 31 | expect(error).toMatch(/Account password minimum length not satisfied./); 32 | }); 33 | 34 | it('should register successfully', () => { 35 | firstName.sendKeys('Test'); 36 | lastName.sendKeys('User'); 37 | email.sendKeys('matt.raible+user' + getRandomInt(0, 1000) + '@stormpath.com'); 38 | password.sendKeys('Stormpath123'); 39 | element(by.css('button[type=submit]')).click(); 40 | 41 | let success = $('.alert-success').getText(); 42 | expect(success).toMatch(/Your account has been created, you may now log in./); 43 | 44 | browser.sleep(2000); 45 | expect(title.getText()).toMatch(/Welcome, Test User/); 46 | 47 | logout.click(); 48 | 49 | expect(title.getText()).toMatch(/Hello/); 50 | }); 51 | }); 52 | 53 | function getRandomInt(min, max) { 54 | min = Math.ceil(min); 55 | max = Math.floor(max); 56 | return Math.floor(Math.random() * (max - min)) + min; 57 | } 58 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const WATCH = process.argv.indexOf('--watch') > -1; 4 | const LoaderOptionsPlugin = require("webpack/lib/LoaderOptionsPlugin"); 5 | const StringReplacePlugin = require('string-replace-webpack-plugin'); 6 | const TOKENS = { 7 | VERSION: require('./package.json').version.replace(/['"]+/g, '') 8 | }; 9 | 10 | module.exports = function (config) { 11 | config.set({ 12 | 13 | // base path that will be used to resolve all patterns (eg. files, exclude) 14 | basePath: './', 15 | 16 | // frameworks to use 17 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 18 | frameworks: ['jasmine'], 19 | 20 | // list of files / patterns to load in the browser 21 | files: [ 22 | 'test/entry.ts' 23 | ], 24 | 25 | // list of files to exclude 26 | exclude: [], 27 | 28 | // preprocess matching files before serving them to the browser 29 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 30 | preprocessors: { 31 | 'test/entry.ts': ['webpack', 'sourcemap'] 32 | }, 33 | 34 | webpack: { 35 | resolve: { 36 | extensions: ['.ts', '.js'], 37 | alias: { 38 | sinon: 'sinon/pkg/sinon' 39 | } 40 | }, 41 | module: { 42 | rules: [ 43 | { 44 | test: /\.ts$/, enforce: 'pre', loader: 'tslint-loader', exclude: /(test|node_modules)/ 45 | }, 46 | { 47 | test: /\.ts$/, 48 | loaders: ['awesome-typescript-loader', 'angular2-template-loader?keepUrl=true'], 49 | exclude: /node_modules/ 50 | }, 51 | { 52 | test: /stormpath.config.ts$/, 53 | loader: StringReplacePlugin.replace({ 54 | replacements: [{ 55 | pattern: /\${(.*)}/g, 56 | replacement: function (match, p1, offset, string) { 57 | return TOKENS[p1]; 58 | } 59 | }] 60 | }) 61 | }, 62 | { 63 | test: /\.(html|css)$/, 64 | loader: 'raw-loader', 65 | exclude: /\.async\.(html|css)$/ 66 | }, 67 | { 68 | test: /\.async\.(html|css)$/, 69 | loaders: ['file?name=[name].[hash].[ext]', 'extract'] 70 | }, 71 | { 72 | test: /sinon.js$/, loader: 'imports-loader?define=>false,require=>false' 73 | }, 74 | { 75 | test: /src\/.+\.ts$/, 76 | enforce: 'post', 77 | exclude: /(test|node_modules)/, 78 | loader: 'sourcemap-istanbul-instrumenter-loader?force-sourcemap=true' 79 | }] 80 | }, 81 | devtool: 'inline-source-map', 82 | plugins: [ 83 | new webpack.ContextReplacementPlugin( 84 | // The (\\|\/) piece accounts for path separators in *nix and Windows 85 | /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, 86 | root('./src') // location of your src 87 | ), 88 | // the LoaderOptionsPlugin is necessary to configure tslint, but causes issues with StringReplacePlugin 89 | // https://github.com/wbuchwalter/tslint-loader/issues/38 90 | /*new LoaderOptionsPlugin({ 91 | options: { 92 | tslint: { 93 | emitErrors: !WATCH, 94 | failOnHint: false 95 | } 96 | } 97 | }),*/ 98 | new StringReplacePlugin() 99 | ] 100 | }, 101 | 102 | remapIstanbulReporter: { 103 | reports: { // eslint-disable-line 104 | 'html': 'coverage/html', 105 | 'text-summary': null 106 | } 107 | }, 108 | 109 | // test results reporter to use 110 | // possible values: 'dots', 'progress' 111 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 112 | reporters: ['progress', 'coverage', 'karma-remap-istanbul'], 113 | 114 | // web server port 115 | port: 9876, 116 | 117 | // enable / disable colors in the output (reporters and logs) 118 | colors: true, 119 | 120 | // level of logging 121 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 122 | logLevel: config.LOG_INFO, 123 | 124 | // enable / disable watching file and executing tests whenever any file changes 125 | autoWatch: WATCH, 126 | 127 | // start these browsers 128 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 129 | browsers: ['PhantomJS'], 130 | 131 | // Continuous Integration mode 132 | // if true, Karma captures browsers, runs the tests and exits 133 | singleRun: !WATCH 134 | }); 135 | }; 136 | 137 | function root(__path) { 138 | return path.join(__dirname, __path); 139 | } 140 | -------------------------------------------------------------------------------- /keypair.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormpath/stormpath-sdk-angular/902640ef46bf1b3beb6e160bf999c3e5955096f7/keypair.enc -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-stormpath", 3 | "version": "0.2.0", 4 | "description": "Stormpath Components for Angular.", 5 | "main": "./dist/umd/stormpath-sdk-angular.js", 6 | "module": "./dist/esm/src/index.js", 7 | "typings": "./dist/esm/src/index.d.ts", 8 | "scripts": { 9 | "start": "concurrently --raw \"webpack-dev-server --open\" \"node demo/server/server.js stormpath-api\" \"npm run test:watch\"", 10 | "stop": "killall -SIGINT stormpath-api", 11 | "build:demo": "webpack -p", 12 | "build:umd": "webpack --config webpack.config.umd.js", 13 | "build:ngc": "ngc -p tsconfig-ngc.json", 14 | "build:dist": "npm run build:umd && npm run build:ngc", 15 | "build:clean": "rm -rf dist", 16 | "test": "karma start", 17 | "test:watch": "karma start --watch", 18 | "pree2e": "webdriver-manager update", 19 | "e2e": "protractor", 20 | "e2e:client": "protractor --params.api https://raible.apps.stormpath.io", 21 | "e2e:client+cookies": "protractor --params.api https://raible.apps.stormpath.io --params.storage cookies", 22 | "e2e:all": "npm run e2e && npm run e2e:client && npm run e2e:client+cookies", 23 | "commit": "git-cz", 24 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", 25 | "typedoc": "mkdir -p dist/docs && typedoc --options typedoc.json src/*.ts --out dist/docs", 26 | "gh-pages": "git checkout gh-pages && git merge master && npm run build:demo && npm run typedoc && git add . && git commit -m 'chore: build demo and docs' && git push && git checkout master", 27 | "prerelease": "npm test", 28 | "release": "standard-version --first-release && git push --follow-tags origin master && npm run build:dist && npm publish", 29 | "postrelease": "npm run build:clean && npm run gh-pages" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "git+https://github.com/stormpath/stormpath-sdk-angular.git" 34 | }, 35 | "keywords": [ 36 | "angular", 37 | "angular2", 38 | "angular-component", 39 | "stormpath", 40 | "authentication", 41 | "authorization", 42 | "security" 43 | ], 44 | "author": "Stormpath, Inc.", 45 | "license": "Apache-2.0", 46 | "bugs": { 47 | "url": "https://github.com/stormpath/stormpath-sdk-angular/issues" 48 | }, 49 | "homepage": "https://github.com/stormpath/stormpath-sdk-angular#readme", 50 | "dependencies": { 51 | "angular2-cookie": "^1.2.6", 52 | "ng2-webstorage": "^2.0.0" 53 | }, 54 | "devDependencies": { 55 | "@angular/common": "^5.0.0", 56 | "@angular/compiler": "^5.0.0", 57 | "@angular/compiler-cli": "^5.0.0", 58 | "@angular/core": "^5.0.0", 59 | "@angular/forms": "^5.0.0", 60 | "@angular/http": "^5.0.0", 61 | "@angular/platform-browser": "^5.0.0", 62 | "@angular/platform-browser-dynamic": "^5.0.0", 63 | "@angular/platform-server": "^5.0.0", 64 | "@types/chai": "^4.0.5", 65 | "@types/jasmine": "^2.5.38", 66 | "@types/sinon": "^4.0.0", 67 | "@types/sinon-chai": "~2.7.26", 68 | "@types/tether": "^1.4.0", 69 | "angular2-template-loader": "^0.6.0", 70 | "awesome-typescript-loader": "^3.0.0-beta.17", 71 | "chai": "^4.1.2", 72 | "commitizen": "^2.9.2", 73 | "concurrently": "^3.1.0", 74 | "conventional-changelog": "~1.1.0", 75 | "conventional-changelog-cli": "^1.3.5", 76 | "core-js": "^2.5.1", 77 | "cz-conventional-changelog": "^2.1.0", 78 | "express-stormpath": "^3.1.5", 79 | "ghooks": "^2.0.0", 80 | "imports-loader": "^0.7.0", 81 | "jasmine-core": "^2.8.0", 82 | "jasmine-reporters": "^2.2.0", 83 | "karma": "^1.4.0", 84 | "karma-coverage": "~1.1.0", 85 | "karma-jasmine": "^1.1.0", 86 | "karma-phantomjs-launcher": "~1.0.0", 87 | "karma-remap-istanbul": "^0.6.0", 88 | "karma-sourcemap-loader": "~0.3.7", 89 | "karma-webpack": "^2.0.1", 90 | "phantomjs-prebuilt": "~2.1.7", 91 | "protractor": "^5.0.0", 92 | "protractor-jasmine2-screenshot-reporter": "^0.5.0", 93 | "raw-loader": "^0.5.1", 94 | "rxjs": "^5.0.2", 95 | "sinon": "^4.1.2", 96 | "sinon-chai": "^2.14.0", 97 | "sourcemap-istanbul-instrumenter-loader": "~0.2.0", 98 | "standard-version": "^4.0.0", 99 | "string-replace-webpack-plugin": "^0.1.3", 100 | "ts-loader": "^1.3.2s", 101 | "ts-node": "^3.3.0", 102 | "tslint": "^5.8.0", 103 | "tslint-loader": "^3.3.0", 104 | "typedoc": "^0.9.0", 105 | "typescript": "^2.4.2", 106 | "validate-commit-msg": "^2.14.0", 107 | "webpack": "^3.8.1", 108 | "webpack-dev-server": "^2.9.4", 109 | "zone.js": "^0.8.18" 110 | }, 111 | "peerDependencies": { 112 | "@angular/core": "^5.0.0", 113 | "@angular/forms": "^5.0.0", 114 | "@angular/http": "^5.0.0", 115 | "rxjs": "^5.0.2" 116 | }, 117 | "files": [ 118 | "dist" 119 | ], 120 | "config": { 121 | "ghooks": { 122 | "commit-msg": "validate-commit-msg" 123 | }, 124 | "commitizen": { 125 | "path": "node_modules/cz-conventional-changelog" 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | var HtmlScreenshotReporter = require("protractor-jasmine2-screenshot-reporter"); 2 | var JasmineReporters = require('jasmine-reporters'); 3 | 4 | exports.config = { 5 | allScriptsTimeout: 20000, 6 | 7 | specs: [ 8 | './e2e/*.spec.ts' 9 | ], 10 | 11 | capabilities: { 12 | 'browserName': 'chrome', 13 | 'phantomjs.binary.path': require('phantomjs-prebuilt').path, 14 | 'phantomjs.ghostdriver.cli.args': ['--loglevel=DEBUG'], 15 | marionette: false 16 | }, 17 | 18 | directConnect: true, 19 | 20 | baseUrl: 'http://localhost:8000/', 21 | 22 | framework: 'jasmine2', 23 | 24 | jasmineNodeOpts: { 25 | showColors: true, 26 | defaultTimeoutInterval: 30000 27 | }, 28 | 29 | beforeLaunch: function () { 30 | require('ts-node').register({ 31 | project: '' 32 | }); 33 | }, 34 | 35 | onPrepare: function () { 36 | browser.driver.manage().window().setSize(1280, 1024); 37 | jasmine.getEnv().addReporter(new JasmineReporters.JUnitXmlReporter({ 38 | savePath: 'dist/reports/e2e', 39 | consolidateAll: false 40 | })); 41 | jasmine.getEnv().addReporter(new HtmlScreenshotReporter({ 42 | dest: "dist/reports/e2e/screenshots" 43 | })); 44 | if (browser.params.api) { 45 | browser.baseUrl = browser.baseUrl + '?api=' + browser.params.api 46 | } 47 | if (browser.params.storage) { 48 | browser.baseUrl = (browser.baseUrl.indexOf('?') > -1) ? browser.baseUrl + '&' : browser.baseUrl + '?'; 49 | browser.baseUrl += 'storage=' + browser.params.storage; 50 | } 51 | }, 52 | 53 | useAllAngular2AppRoots: true 54 | }; 55 | -------------------------------------------------------------------------------- /src/authport/authport.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, TemplateRef, Input } from '@angular/core'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { Stormpath, LoginService } from '../stormpath/stormpath.service'; 5 | import { Account } from '../shared/account'; 6 | 7 | @Component({ 8 | selector: 'sp-authport', 9 | template: ` 10 |
11 |
12 |
13 |
14 |
15 |

16 | 24 |

25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 | 33 |
34 |
35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 48 | `, 49 | providers: [LoginService] 50 | }) 51 | @Injectable() 52 | export class AuthPortComponent implements OnInit { 53 | /** 54 | * A reference to a `` tag that if set will override this component's template. Use like so: 55 | * ``` 56 | * 57 | * // custom HTML with login form 58 | * 59 | * ``` 60 | * Then pass customTemplate to the sp-authport component like so `[customTemplate]="customTemplate"` 61 | */ 62 | @Input() customTemplate: TemplateRef; 63 | 64 | private user$: Observable; 65 | private loggedIn$: Observable; 66 | private login: boolean; 67 | private register: boolean; 68 | private forgot: boolean; 69 | 70 | constructor(public stormpath: Stormpath, public loginService: LoginService) { 71 | this.user$ = this.stormpath.user$; 72 | this.loggedIn$ = this.user$.map(user => !!user); 73 | } 74 | 75 | ngOnInit(): void { 76 | this.loginService.login = true; 77 | this.loginService.register = false; 78 | this.forgot = this.loginService.forgot; 79 | this.user$ = this.stormpath.user$; 80 | this.loggedIn$ = this.user$.map(user => !!user); 81 | } 82 | 83 | showLogin(): void { 84 | this.loginService.login = !(this.loginService.forgot = this.loginService.register = false); 85 | } 86 | 87 | showRegister(): void { 88 | this.loginService.forgot = this.loginService.login = false; 89 | this.loginService.register = true; 90 | } 91 | 92 | forgotPassword(): void { 93 | this.loginService.login = false; 94 | this.forgot = true; 95 | } 96 | 97 | logout(): void { 98 | this.stormpath.logout(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/authport/index.ts: -------------------------------------------------------------------------------- 1 | export * from './authport.component'; 2 | -------------------------------------------------------------------------------- /src/email-verification/email-verification.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, TemplateRef, Input } from '@angular/core'; 2 | import { Injectable } from '@angular/core'; 3 | import { Location } from '@angular/common'; 4 | import { Stormpath, defaultSpTokenResolver } from '../stormpath/stormpath.service'; 5 | 6 | @Component({ 7 | selector: 'email-verification', 8 | template: ` 9 |
10 |
11 |

We are verifying your account

12 |

13 | Your account has has been verified! You may now login. 14 |

15 |
16 | This email verification link is not valid. Please request a new email verification link. 17 |
18 |

{{error}}

19 |
20 |
21 |
22 | 24 | ` 25 | }) 26 | @Injectable() 27 | export class EmailVerificationComponent implements OnInit { 28 | /** 29 | * A reference to a `` tag that if set will override this component's template. Use like so: 30 | * ``` 31 | * 32 | * // custom HTML with login form 33 | * 34 | * ``` 35 | * Then pass customTemplate to the `email-verification` component like so `[customTemplate]="customTemplate"` 36 | */ 37 | @Input() customTemplate: TemplateRef; 38 | 39 | protected error: string; 40 | protected verifying: boolean; 41 | protected verified: boolean; 42 | protected verificationFailed: boolean; 43 | protected sptoken: string; 44 | 45 | constructor(public stormpath: Stormpath, public location: Location) { 46 | } 47 | 48 | ngOnInit(): void { 49 | this.verifying = false; 50 | this.verified = false; 51 | this.verificationFailed = false; 52 | this.sptoken = this.spTokenResolver(); 53 | if (this.sptoken) { 54 | this.verify(); 55 | } 56 | } 57 | 58 | spTokenResolver(): string { 59 | return defaultSpTokenResolver(this.location); 60 | } 61 | 62 | verify(): void { 63 | this.verifying = true; 64 | this.stormpath.verifyEmailVerificationToken(this.sptoken) 65 | .subscribe(() => { 66 | this.verifying = false; 67 | this.verified = true; 68 | }, (error) => { 69 | this.verifying = false; 70 | if (error.status && error.status === 404) { 71 | this.verificationFailed = true; 72 | } else { 73 | this.error = error.message; 74 | } 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/email-verification/index.ts: -------------------------------------------------------------------------------- 1 | export * from './email-verification.component'; 2 | -------------------------------------------------------------------------------- /src/extensions/ionic/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function __export(m) { 3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 4 | } 5 | __export(require('./forgot')); 6 | __export(require('./login')); 7 | __export(require('./register')); 8 | -------------------------------------------------------------------------------- /src/forgot-password/forgot-password.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, TemplateRef, Input } from '@angular/core'; 2 | import { Injectable } from '@angular/core'; 3 | import { Stormpath, ForgotPasswordFormModel, StormpathErrorResponse } from '../stormpath/stormpath.service'; 4 | 5 | @Component({ 6 | selector: 'forgot-password-form', 7 | template: ` 8 |
9 |
10 |

11 | We have sent a password reset link to the email address of the account that you specified. 12 | Please check your email for this message, then click on the link. 13 |

14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 |
22 | 24 |
25 |
26 |
27 |
28 |

{{error}}

29 | 30 |
31 |
32 |
33 |
34 |
35 |
36 | 38 | ` 39 | }) 40 | @Injectable() 41 | export class ForgotPasswordComponent implements OnInit { 42 | /** 43 | * A reference to a `` tag that if set will override this component's template. Use like so: 44 | * ``` 45 | * 46 | * // custom HTML with login form 47 | * 48 | * ``` 49 | * Then pass customTemplate to the `forgot-password-form` component like so `[customTemplate]="customTemplate"` 50 | */ 51 | @Input() customTemplate: TemplateRef; 52 | protected forgotPasswordFormModel: ForgotPasswordFormModel; 53 | protected error: string; 54 | protected sent: boolean; 55 | 56 | constructor(public stormpath: Stormpath) { 57 | this.sent = false; 58 | } 59 | 60 | ngOnInit(): void { 61 | this.forgotPasswordFormModel = { 62 | email: '' 63 | }; 64 | } 65 | 66 | send(): void { 67 | this.error = null; 68 | this.stormpath.sendPasswordResetEmail(this.forgotPasswordFormModel) 69 | .subscribe(() => this.sent = true, 70 | (error: StormpathErrorResponse) => this.error = error.message); 71 | } 72 | 73 | onSubmit(form: any): void { 74 | this.send(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/forgot-password/index.ts: -------------------------------------------------------------------------------- 1 | export * from './forgot-password.component'; 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './stormpath.module'; 2 | 3 | // all components that will be codegen'd need to be exported for AOT to work 4 | export * from './authport/index'; 5 | export * from './email-verification/index'; 6 | export * from './forgot-password/index'; 7 | export * from './login/index'; 8 | export * from './register/index'; 9 | export * from './resend-email-verification/index'; 10 | export * from './reset-password/index'; 11 | export * from './shared/index'; 12 | export * from './stormpath/index'; 13 | -------------------------------------------------------------------------------- /src/login/index.ts: -------------------------------------------------------------------------------- 1 | export * from './login.component'; 2 | -------------------------------------------------------------------------------- /src/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, TemplateRef, Input } from '@angular/core'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { Account } from '../shared/account'; 5 | import { 6 | Stormpath, LoginFormModel, LoginService, StormpathErrorResponse 7 | } from '../stormpath/stormpath.service'; 8 | 9 | @Component({ 10 | selector: 'login-form', 11 | template: ` 12 |
13 |
14 | 15 |
16 | 17 |
18 |
19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 |  Forgot Password? 29 |
30 |
31 | 32 |
{{error}}
33 | 34 |
35 |
36 | 38 | ` 39 | }) 40 | @Injectable() 41 | export class LoginComponent implements OnInit { 42 | /** 43 | * A reference to a `` tag that if set will override this component's template. Use like so: 44 | * ``` 45 | * 46 | * // custom HTML with login form 47 | * 48 | * ``` 49 | * Then pass customTemplate to the `login-form` component like so `[customTemplate]="customTemplate"` 50 | */ 51 | @Input() customTemplate: TemplateRef; 52 | 53 | protected loginFormModel: LoginFormModel; 54 | protected user$: Observable; 55 | protected loggedIn$: Observable; 56 | protected error: string; 57 | 58 | constructor(public stormpath: Stormpath, public loginService: LoginService) { 59 | } 60 | 61 | ngOnInit(): void { 62 | this.user$ = this.stormpath.user$; 63 | this.loggedIn$ = this.user$.map(user => !!user); 64 | this.loginFormModel = { 65 | login: '', 66 | password: '' 67 | }; 68 | } 69 | 70 | login(form: any): void { 71 | this.error = null; 72 | this.stormpath.login(this.loginFormModel) 73 | .subscribe(null, (error: StormpathErrorResponse) => { 74 | this.error = error.message; 75 | }); 76 | } 77 | 78 | forgot(): void { 79 | this.loginService.forgotPassword(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/register/index.ts: -------------------------------------------------------------------------------- 1 | export * from './register.component'; 2 | -------------------------------------------------------------------------------- /src/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit, TemplateRef } from '@angular/core'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs/Observable'; 4 | 5 | import { Account } from '../shared/account'; 6 | import { Stormpath, LoginFormModel, RegistrationFormModel } from '../stormpath/stormpath.service'; 7 | 8 | @Component({ 9 | selector: 'register-form', 10 | template: ` 11 |
12 |
13 | 14 |
15 | 17 |
18 |
19 |
{{error}}
20 | 21 |
22 |

23 | Your account has been created and requires verification. 24 | Please check your email for a verification link. 25 |

26 |

27 | Your account has been created, you may now log in. 28 |

29 |
30 | 32 | ` 33 | }) 34 | @Injectable() 35 | export class RegisterComponent implements OnInit { 36 | /** 37 | * A reference to a `` tag that if set will override this component's template. Use like so: 38 | * ``` 39 | * 40 | * // custom HTML with login form 41 | * 42 | * ``` 43 | * Then pass customTemplate to the `register-form` component like so `[customTemplate]="customTemplate"` 44 | */ 45 | @Input() customTemplate: TemplateRef; 46 | @Input() autoLogin: boolean; 47 | protected model: Object; 48 | protected error: string; 49 | protected viewModel$: Observable; 50 | protected formModel: RegistrationFormModel; 51 | protected unverified: boolean; 52 | protected canLogin: boolean; 53 | protected registered: boolean; 54 | 55 | constructor(public stormpath: Stormpath) { 56 | this.unverified = false; 57 | this.canLogin = false; 58 | this.formModel = { 59 | email: '', 60 | surname: '', 61 | givenName: '', 62 | password: '' 63 | }; 64 | } 65 | 66 | ngOnInit(): void { 67 | this.stormpath.getRegistrationViewModel() 68 | .subscribe(model => { 69 | this.model = model; 70 | }, error => 71 | this.error = error.message 72 | ); 73 | } 74 | 75 | register(): void { 76 | this.stormpath.register(this.formModel) 77 | .subscribe((account: Account) => { 78 | this.registered = true; 79 | this.unverified = account.status === 'UNVERIFIED'; 80 | this.canLogin = account.status === 'ENABLED'; 81 | 82 | if (this.canLogin && this.autoLogin) { 83 | let loginAttempt: LoginFormModel = { 84 | login: this.formModel.email, 85 | password: this.formModel.password 86 | }; 87 | 88 | this.stormpath.login(loginAttempt); 89 | } 90 | }, error => this.error = error.message); 91 | } 92 | 93 | onSubmit(): void { 94 | this.register(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/resend-email-verification/index.ts: -------------------------------------------------------------------------------- 1 | export * from './resend-email-verification.component'; 2 | -------------------------------------------------------------------------------- /src/resend-email-verification/resend-email-verification.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Injectable, OnInit, Input, TemplateRef } from '@angular/core'; 2 | import { Stormpath, ResendEmailVerificationRequest } from '../stormpath/stormpath.service'; 3 | 4 | @Component({ 5 | selector: 'resend-email-verification', 6 | template: ` 7 |
8 |
9 | 10 |

11 | We have sent a new verification message to your email address, please check your email for this message. 12 |

13 | 14 |

{{error}}

15 |
16 |
17 | 18 |
19 |
20 |
21 | 22 |
23 | 24 |
25 | 26 |
27 |
28 |
29 |
30 |

{{error}}

31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 | 40 | ` 41 | }) 42 | @Injectable() 43 | export class ResendEmailVerificationComponent implements OnInit { 44 | /** 45 | * A reference to a `` tag that if set will override this component's template. Use like so: 46 | * ``` 47 | * 48 | * // custom HTML with login form 49 | * 50 | * ``` 51 | * Then pass customTemplate to the `resend-email-verification` component like so `[customTemplate]="customTemplate"` 52 | */ 53 | @Input() customTemplate: TemplateRef; 54 | protected error: string; 55 | protected formModel: ResendEmailVerificationRequest; 56 | protected posting: boolean; 57 | protected sent: boolean; 58 | 59 | constructor(public stormpath: Stormpath) { 60 | } 61 | 62 | ngOnInit(): void { 63 | this.posting = false; 64 | this.sent = false; 65 | this.formModel = { 66 | login: '' 67 | }; 68 | } 69 | 70 | send(): void { 71 | this.posting = true; 72 | this.stormpath.resendVerificationEmail(this.formModel) 73 | .subscribe(() => { 74 | this.posting = false; 75 | this.sent = true; 76 | }, (error) => { 77 | this.posting = false; 78 | this.error = error.message; 79 | }); 80 | } 81 | 82 | onSubmit(): void { 83 | this.send(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/reset-password/index.ts: -------------------------------------------------------------------------------- 1 | export * from './reset-password.component'; 2 | -------------------------------------------------------------------------------- /src/reset-password/reset-password.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Injectable, OnInit, TemplateRef, Input } from '@angular/core'; 2 | import { Location } from '@angular/common'; 3 | import { Stormpath, PasswordResetRequest, defaultSpTokenResolver } from '../stormpath/stormpath.service'; 4 | 5 | @Component({ 6 | selector: 'reset-password', 7 | template: ` 8 |
9 |
10 |

We are verifying your password reset link

11 |

Your new password has been set, you may now login.

12 |
13 | This password reset link is not valid, please request a new reset link. 14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 |
23 | 25 |
26 |
27 |
28 | 29 |
30 | 32 |
33 |
34 |
35 |
36 |

{{error}}

37 | 38 |
39 |
40 |
41 |
42 |
43 |
44 | 46 | ` 47 | }) 48 | @Injectable() 49 | export class ResetPasswordComponent implements OnInit { 50 | /** 51 | * A reference to a `` tag that if set will override this component's template. Use like so: 52 | * ``` 53 | * 54 | * // custom HTML with login form 55 | * 56 | * ``` 57 | * Then pass customTemplate to the `reset-password` component like so `[customTemplate]="customTemplate"` 58 | */ 59 | @Input() customTemplate: TemplateRef; 60 | protected disabled: boolean; 61 | protected error: string; 62 | protected formModel: PasswordResetRequest; 63 | protected posting: boolean; 64 | protected reset: boolean; 65 | protected verifying: boolean; 66 | protected verified: boolean; 67 | protected verificationFailed: boolean; 68 | protected sptoken: string; 69 | 70 | constructor(public stormpath: Stormpath, public location: Location) { 71 | } 72 | 73 | ngOnInit(): void { 74 | this.verifying = false; 75 | this.verified = false; 76 | this.verificationFailed = false; 77 | this.formModel = { 78 | sptoken: this.spTokenResolver(), 79 | password: '' 80 | }; 81 | // debugger 82 | if (this.formModel.sptoken) { 83 | this.verify(); 84 | 85 | } 86 | } 87 | 88 | spTokenResolver(): string { 89 | return defaultSpTokenResolver(this.location); 90 | } 91 | 92 | verify(): void { 93 | this.verifying = true; 94 | this.stormpath.verifyPasswordResetToken(this.formModel.sptoken) 95 | .subscribe(() => { 96 | this.verifying = false; 97 | this.verified = true; 98 | }, (error) => { 99 | this.verifying = false; 100 | if (error.status && error.status === 404) { 101 | this.verificationFailed = true; 102 | } else { 103 | this.error = error.message; 104 | } 105 | }); 106 | } 107 | 108 | send(): void { 109 | this.stormpath.resetPassword(this.formModel) 110 | .subscribe( 111 | () => { 112 | this.posting = false; 113 | this.reset = true; 114 | }, 115 | (error) => { 116 | this.posting = false; 117 | this.error = error.message; 118 | } 119 | ); 120 | } 121 | 122 | onSubmit(): void { 123 | this.send(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/shared/account.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This interface represents the known base properties of a Stormpath Account 3 | * object, that you will receive from the /me endpoint on your server. The 4 | * object can have more properties, depending on the `expand` options that are 5 | * configured in the server. 6 | */ 7 | export interface BaseStormpathAccount { 8 | 9 | href: string; 10 | username: string; 11 | email: string; 12 | givenName: string; 13 | middleName: string; 14 | surname: string; 15 | fullName: string; 16 | status: string; 17 | createdAt: string; 18 | modifiedAt: string; 19 | passwordModifiedAt: string; 20 | [propName: string]: any; 21 | } 22 | 23 | export class Account { 24 | 25 | status: string; 26 | 27 | constructor(account: BaseStormpathAccount) { 28 | for (let property in account) { 29 | this[property] = account[property]; 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './account'; 2 | -------------------------------------------------------------------------------- /src/stormpath.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { AuthPortComponent } from './authport/authport.component'; 5 | import { ForgotPasswordComponent } from './forgot-password/forgot-password.component'; 6 | import { LoginComponent } from './login/login.component'; 7 | import { RegisterComponent } from './register/register.component'; 8 | import { HttpModule, RequestOptions, Http, XHRBackend } from '@angular/http'; 9 | import { LoginService } from './stormpath/stormpath.service'; 10 | import { StormpathConfiguration } from './stormpath/stormpath.config'; 11 | import { httpFactory } from './stormpath/stormpath.http'; 12 | import { EmailVerificationComponent } from './email-verification/email-verification.component'; 13 | import { ResetPasswordComponent } from './reset-password/reset-password.component'; 14 | import { ResendEmailVerificationComponent } from './resend-email-verification/resend-email-verification.component'; 15 | import { EventManager } from './stormpath/event.manager'; 16 | import { LocalStorageTokenStoreManager, CookieTokenStoreManager } from './stormpath/token-store.manager'; 17 | import { Stormpath } from './stormpath/stormpath.service'; 18 | import { Ng2Webstorage } from 'ng2-webstorage'; 19 | import { CookieService } from 'angular2-cookie/core'; 20 | 21 | @NgModule({ 22 | declarations: [ 23 | AuthPortComponent, 24 | ForgotPasswordComponent, 25 | LoginComponent, 26 | RegisterComponent, 27 | EmailVerificationComponent, 28 | ResetPasswordComponent, 29 | ResendEmailVerificationComponent 30 | ], 31 | imports: [CommonModule, FormsModule, HttpModule, Ng2Webstorage], 32 | exports: [ 33 | AuthPortComponent, 34 | ForgotPasswordComponent, 35 | LoginComponent, 36 | RegisterComponent, 37 | EmailVerificationComponent, 38 | ResetPasswordComponent, 39 | ResendEmailVerificationComponent 40 | ], 41 | providers: [ 42 | EventManager, LocalStorageTokenStoreManager, CookieTokenStoreManager, CookieService, 43 | Stormpath, StormpathConfiguration, LoginService, 44 | { 45 | provide: 'tokenStore', useClass: LocalStorageTokenStoreManager, 46 | }, 47 | { 48 | provide: Http, 49 | useFactory: httpFactory, 50 | deps: [XHRBackend, RequestOptions, StormpathConfiguration, 'tokenStore'] 51 | } 52 | ] 53 | }) 54 | export class StormpathModule { 55 | } 56 | -------------------------------------------------------------------------------- /src/stormpath/auth.token.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Class to represent a Stormpath Authentication Token. 3 | */ 4 | export class AuthToken { 5 | 6 | static isValid(token: AuthToken): boolean { 7 | return token && new Date() <= new Date(token.exp); 8 | } 9 | 10 | constructor(public accessToken: string, public refreshToken: string, public tokenType: string, 11 | public expiresIn: number, public expiresAt: number, public exp: Date) {} 12 | } 13 | -------------------------------------------------------------------------------- /src/stormpath/event.manager.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observer, Subscription} from 'rxjs/Rx'; 3 | import { Observable } from 'rxjs/Observable'; 4 | 5 | @Injectable() 6 | export class EventManager { 7 | 8 | observable: Observable; 9 | observer: Observer; 10 | 11 | constructor() { 12 | this.observable = Observable.create((observer: Observer) => { 13 | this.observer = observer; 14 | }).share(); 15 | } 16 | 17 | broadcast(event: any): any { 18 | this.observer.next(event); 19 | } 20 | 21 | subscribe(eventName: string, callback: any): any { 22 | return this.observable.filter((event: any) => { 23 | return event.name === eventName; 24 | }).subscribe(callback); 25 | } 26 | 27 | destroy(subscriber: Subscription): void { 28 | subscriber.unsubscribe(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/stormpath/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth.token'; 2 | export * from './event.manager'; 3 | export * from './stormpath.config'; 4 | export * from './stormpath.http'; 5 | export * from './stormpath.service'; 6 | export * from './token-store.manager'; 7 | -------------------------------------------------------------------------------- /src/stormpath/stormpath.config.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Headers } from '@angular/http'; 3 | 4 | export class StormpathConstants { 5 | public static readonly VERSION: string = '${VERSION}'; // This value will be overwritten by webpack 6 | 7 | /** 8 | * Default: `/login` 9 | * 10 | * The URI that the login form will post to. The endpoint MUST accept data 11 | * in the following format: 12 | * 13 | * ``` 14 | * { 15 | * username: '', 16 | * password: '' 17 | * } 18 | * ``` 19 | */ 20 | public static readonly AUTHENTICATION_ENDPOINT: string = '/login'; 21 | 22 | /** 23 | * Default: `/oauth/token` 24 | * 25 | * The endpoint that is used to authenticate and refresh using OAuth tokens. 26 | * This endpoint MUST support password and refresh_token grant authentication flows. 27 | */ 28 | public static readonly OAUTH_AUTHENTICATION_ENDPOINT: string = '/oauth/token'; 29 | 30 | /** 31 | * Default: 'stormpath:token' 32 | * 33 | * The name under which tokens are stored in the token storage mechanism. 34 | * Might not be relevant if the underlying storage mechanism is not key-value based. 35 | */ 36 | public static readonly OAUTH_TOKEN_STORAGE_NAME: string = 'stormpath:token'; 37 | 38 | public static readonly OAUTH_HEADERS: Headers = new Headers({ 39 | 'Content-Type': 'application/x-www-form-urlencoded', 40 | 'Accept': 'application/json' 41 | }); 42 | 43 | /** 44 | * Default: `/me` 45 | * 46 | * The URI that is used to fetch the account object of 47 | * the currently logged in user. This endpoint MUST: 48 | * * Respond with a JSON object that is the Stormpath account object, 49 | * if the user has an active session. 50 | * * Respond with `401 Unauthorized` if the user has no session. 51 | */ 52 | public static readonly CURRENT_USER_URI: string = '/me'; 53 | 54 | /** 55 | * Default: `/logout` 56 | * 57 | * The URL that the logout action will make a GET request to, this endpoint 58 | * MUST delete the access token cookie, XSRF token cookie, and any other cookies 59 | * that relate to the user session. 60 | */ 61 | public static readonly DESTROY_SESSION_ENDPOINT: string = '/logout'; 62 | 63 | /** 64 | * Default: `/oauth/revoke` 65 | * 66 | * The endpoint that is used to revoke OAuth tokens. 67 | */ 68 | public static readonly OAUTH_REVOKE_ENDPOINT: string = '/oauth/revoke'; 69 | 70 | /** 71 | * Default: `/verify` 72 | * 73 | * The endpoint that is used for verifying an account that requires email 74 | * verification. 75 | * 76 | * This endpoint MUST accept a POST request with the following format and 77 | * use Stormpath to verify the token: 78 | * ``` 79 | * { 80 | * sptoken: '' 81 | * } 82 | * ``` 83 | * 84 | */ 85 | public static readonly EMAIL_VERIFICATION_ENDPOINT: string = '/verify'; 86 | 87 | /** 88 | * Default: `/forgot` 89 | * 90 | * The endpoint that is used by to create password reset tokens. 91 | */ 92 | public static readonly FORGOT_PASSWORD_ENDPOINT: string = '/forgot'; 93 | 94 | /** 95 | * Default: `/change` 96 | * 97 | * The endpoint that is used to verify and consume password reset tokens 98 | * (change a user's password with the token). 99 | */ 100 | public static readonly CHANGE_PASSWORD_ENDPOINT: string = '/change'; 101 | 102 | /** 103 | * Default: `/register` 104 | * 105 | * The endpoint that is used to POST new users. This endpoint MUST accept a 106 | * stormpath account object and use Stormpath to create the new user. 107 | */ 108 | public static readonly REGISTER_URI: string = '/register'; 109 | 110 | /** 111 | * Default: *none* 112 | * 113 | * A prefix, e.g. "base URL" to add to all endpoints that are used by this SDK. 114 | * Use this if your backend API is running on a different port or domain than 115 | * your Angular application. Omit the trailing forward slash. 116 | * 117 | * **NOTE:** This may trigger 118 | * [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) 119 | * behaviour in the browser, and your server 120 | * will need to respond to requests accordingly. If you are using our 121 | * Express SDK see 122 | * [allowedOrigins](https://github.com/stormpath/stormpath-sdk-express#allowedOrigins) 123 | * 124 | * **Example:** 125 | *
126 |    *   ENDPOINT_PREFIX = 'http://api.mydomain.com'
127 |    * 
128 | */ 129 | public static readonly ENDPOINT_PREFIX: string = ''; 130 | } 131 | 132 | @Injectable() 133 | export class StormpathConfiguration { 134 | private _changeUri: string; 135 | private _forgotUri: string; 136 | private _loginUri: string; 137 | private _logoutUri: string; 138 | private _oauthLoginUri: string; 139 | private _oauthLogoutUri: string; 140 | private _oauthTokenName: string; 141 | private _meUri: string; 142 | private _registerUri: string; 143 | private _verifyUri: string; 144 | private _endpointPrefix: string; 145 | private _version: string; 146 | private _autoAuthorizedUris: Array = []; 147 | 148 | constructor() { 149 | this._changeUri = StormpathConstants.CHANGE_PASSWORD_ENDPOINT; 150 | this._forgotUri = StormpathConstants.FORGOT_PASSWORD_ENDPOINT; 151 | this._loginUri = StormpathConstants.AUTHENTICATION_ENDPOINT; 152 | this._logoutUri = StormpathConstants.DESTROY_SESSION_ENDPOINT; 153 | this._oauthLoginUri = StormpathConstants.OAUTH_AUTHENTICATION_ENDPOINT; 154 | this._oauthLogoutUri = StormpathConstants.OAUTH_REVOKE_ENDPOINT; 155 | this._oauthTokenName = StormpathConstants.OAUTH_TOKEN_STORAGE_NAME; 156 | this._meUri = StormpathConstants.CURRENT_USER_URI; 157 | this._registerUri = StormpathConstants.REGISTER_URI; 158 | this._verifyUri = StormpathConstants.EMAIL_VERIFICATION_ENDPOINT; 159 | this._endpointPrefix = StormpathConstants.ENDPOINT_PREFIX; 160 | this._version = StormpathConstants.VERSION; 161 | } 162 | 163 | /** 164 | * Return a list of all available URIs. This list is used to decide whether 165 | * X-Stormpath-Agent is sent as a header or not. 166 | * 167 | * @returns {[string]} Array of all Stormpath endpoints, in alphabetical order. 168 | */ 169 | get endpointUris(): Array { 170 | return [this.changeUri, this.forgotUri, this.loginUri, this.logoutUri, this.meUri, this.registerUri, this.verifyUri]; 171 | } 172 | 173 | /** 174 | * Return a list of URIs that get a Bearer token added automatically. To add to this list, use the following syntax: 175 | * 176 | *
177 |    *   let config: StormpathConfiguration = new StormpathConfiguration();
178 |    *   config.autoAuthorizedUris.push(new RegExp('http://localhost:3000/myapi/*)');
179 |    * 
180 | * 181 | * @returns {[string]} 182 | */ 183 | get autoAuthorizedUris(): Array { 184 | // if empty, set to me and return. Can't do in constructor because /me will be set as default 185 | // and won't be picked up if it's overriden by a developer. 186 | if (this._autoAuthorizedUris.length === 0) { 187 | this._autoAuthorizedUris = [new RegExp(this.meUri)]; 188 | } 189 | return this._autoAuthorizedUris; 190 | } 191 | 192 | get changeUri(): string { 193 | return this._endpointPrefix + this._changeUri; 194 | } 195 | 196 | set changeUri(value: string) { 197 | this._changeUri = value; 198 | } 199 | 200 | get forgotUri(): string { 201 | return this._endpointPrefix + this._forgotUri; 202 | } 203 | 204 | set forgotUri(value: string) { 205 | this._forgotUri = value; 206 | } 207 | 208 | get loginUri(): string { 209 | return this._endpointPrefix + this._loginUri; 210 | } 211 | 212 | set loginUri(value: string) { 213 | this._loginUri = value; 214 | } 215 | 216 | get logoutUri(): string { 217 | return this._endpointPrefix + this._logoutUri; 218 | } 219 | 220 | set logoutUri(value: string) { 221 | this._logoutUri = value; 222 | } 223 | 224 | get oauthLoginUri(): string { 225 | return this._endpointPrefix + this._oauthLoginUri; 226 | } 227 | 228 | set oauthLoginUri(value: string) { 229 | this._oauthLoginUri = value; 230 | } 231 | 232 | get oauthLogoutUri(): string { 233 | return this._endpointPrefix + this._oauthLogoutUri; 234 | } 235 | 236 | set oauthLogoutUri(value: string) { 237 | this._oauthLogoutUri = value; 238 | } 239 | 240 | get oauthTokenName(): string { 241 | return this._oauthTokenName; 242 | } 243 | 244 | set oauthTokenName(value: string) { 245 | this._oauthTokenName = value; 246 | } 247 | 248 | get meUri(): string { 249 | return this._endpointPrefix + this._meUri; 250 | } 251 | 252 | set meUri(value: string) { 253 | this._meUri = value; 254 | } 255 | 256 | get registerUri(): string { 257 | return this._endpointPrefix + this._registerUri; 258 | } 259 | 260 | set registerUri(value: string) { 261 | this._registerUri = value; 262 | } 263 | 264 | get verifyUri(): string { 265 | return this._endpointPrefix + this._verifyUri; 266 | } 267 | 268 | set verifyUri(value: string) { 269 | this._verifyUri = value; 270 | } 271 | 272 | get endpointPrefix(): string { 273 | return this._endpointPrefix; 274 | } 275 | 276 | set endpointPrefix(value: string) { 277 | this._endpointPrefix = value; 278 | } 279 | 280 | get version(): string { 281 | return this._version; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/stormpath/stormpath.http.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject, VERSION } from '@angular/core'; 2 | import { 3 | Http, 4 | ConnectionBackend, 5 | Response, 6 | RequestOptionsArgs, 7 | Request, 8 | RequestOptions, 9 | XHRBackend, 10 | Headers 11 | } from '@angular/http'; 12 | import { Observable } from 'rxjs/Observable'; 13 | import { JsonGetOptions } from './stormpath.service'; 14 | import { TokenStoreManager } from './token-store.manager'; 15 | import { StormpathConfiguration, StormpathConstants } from './stormpath.config'; 16 | import { AuthToken } from './auth.token'; 17 | 18 | // function that allows overriding the default Http provider 19 | export function httpFactory(backend: XHRBackend, defaultOptions: RequestOptions, 20 | config: StormpathConfiguration, tokenStore: TokenStoreManager): Http { 21 | return new StormpathHttp(backend, defaultOptions, config, tokenStore); 22 | } 23 | 24 | @Injectable() 25 | export class StormpathHttp extends Http { 26 | private currentDomain: CurrentDomain; 27 | 28 | constructor(private backend: ConnectionBackend, 29 | private defaultOptions: RequestOptions, 30 | private config: StormpathConfiguration, 31 | @Inject('tokenStore') private tokenStore: TokenStoreManager) { 32 | super(backend, defaultOptions); 33 | this.currentDomain = new CurrentDomain(); 34 | } 35 | 36 | /** 37 | * Override all requests to add x-stormpath-agent and authorization headers when appropriate 38 | * @param url the url or a Request 39 | * @param options request options 40 | * @returns {Observable} the response as an observable 41 | */ 42 | request(url: string|Request, options?: RequestOptionsArgs): Observable { 43 | this.addHeaders(url, options); 44 | return super.request(url, options) 45 | .catch(initialError => { 46 | if (initialError && initialError.status === 401) { 47 | let token: AuthToken = this.tokenStore.get(this.config.oauthTokenName); 48 | // token might be expired, try to refresh 49 | if ((!AuthToken.isValid(token)) && token && token.refreshToken) { 50 | let data: string = 'grant_type=refresh_token&refresh_token=' + token.refreshToken; 51 | return super.post(this.config.oauthLoginUri, data, { 52 | headers: StormpathConstants.OAUTH_HEADERS 53 | }).map(response => response.json()) 54 | .mergeMap(token => { 55 | this.tokenStore.setToken(this.config.oauthTokenName, token); 56 | // attempt the same request again 57 | return this.request(url, options); 58 | }); 59 | } else { 60 | return Observable.throw(initialError); 61 | } 62 | } else { 63 | return Observable.throw(initialError); 64 | } 65 | }); 66 | } 67 | 68 | /** 69 | * Add Stormpath headers and be aware of options versus request.headers 70 | * @param url 71 | * @param options 72 | */ 73 | private addHeaders(url: string|Request, options: RequestOptionsArgs): void { 74 | let requestUri: string = (url instanceof Request) ? url.url : url; 75 | let addToken: boolean = this.config.autoAuthorizedUris.some(rx => rx.test(requestUri)); 76 | 77 | if (options == null && addToken) { 78 | // add headers 'accept: application/json' and 'withCredential: true' 79 | options = new JsonGetOptions(); 80 | } else { 81 | options = new RequestOptions(); 82 | } 83 | if (options.headers == null) { 84 | options.headers = new Headers(); 85 | } 86 | 87 | let version: string = (VERSION) ? VERSION.full : '2.x'; 88 | if (this.config.endpointUris.indexOf(requestUri) > -1) { 89 | options.headers.set('X-Stormpath-Agent', 'stormpath-sdk-angular/' + this.config.version + ' angular/' + version); 90 | if (url instanceof Request) { 91 | url.headers.set('X-Stormpath-Agent', 'stormpath-sdk-angular/' + this.config.version + ' angular/' + version); 92 | } 93 | } 94 | 95 | if (!this.currentDomain.equals(requestUri) && addToken) { 96 | let token: AuthToken = this.tokenStore.get(this.config.oauthTokenName); 97 | if (AuthToken.isValid(token)) { 98 | options.headers.set('Authorization', 'Bearer ' + token.accessToken); 99 | if (url instanceof Request) { 100 | url.headers.set('Authorization', 'Bearer ' + token.accessToken); 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | @Injectable() 108 | export class CurrentDomain { 109 | private window: any = window; 110 | 111 | equals(url: string|Request): boolean { 112 | let link: any = this.window.document.createElement('a'); 113 | link.href = url; 114 | 115 | return this.window.location.host === link.host; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/stormpath/stormpath.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { Headers, Http, Response, RequestOptions } from '@angular/http'; 3 | import { Location } from '@angular/common'; 4 | import { ReplaySubject } from 'rxjs/ReplaySubject'; 5 | import { Observable } from 'rxjs/Observable'; 6 | import { Account, BaseStormpathAccount } from '../shared/account'; 7 | import { ErrorObservable } from 'rxjs/observable/ErrorObservable'; 8 | import { AuthToken } from './auth.token'; 9 | import { StormpathConfiguration, StormpathConstants } from './stormpath.config'; 10 | import { CurrentDomain } from './stormpath.http'; 11 | import { TokenStoreManager } from './token-store.manager'; 12 | 13 | let APPLICATION_JSON: string = 'application/json'; 14 | 15 | export class JsonGetOptions extends RequestOptions { 16 | constructor() { 17 | super({ 18 | headers: new Headers({'Accept': APPLICATION_JSON}), 19 | withCredentials: true 20 | }); 21 | } 22 | } 23 | 24 | export class JsonPostOptions extends JsonGetOptions { 25 | constructor() { 26 | super(); 27 | this.headers.append('Content-Type', APPLICATION_JSON); 28 | } 29 | } 30 | 31 | export function defaultSpTokenResolver(location: Location): string { 32 | let m: RegExpMatchArray = location.path().match(/sptoken=([^&]+)/); 33 | return m && m.length === 2 ? m[1] : ''; 34 | } 35 | 36 | export interface RegistrationFormModel { 37 | email?: string; 38 | surname?: string; 39 | givenName?: string; 40 | password?: string; 41 | [propName: string]: any; 42 | } 43 | 44 | export interface ForgotPasswordFormModel { 45 | email: string; 46 | accountStore?: Object; 47 | organizationNameKey?: string; 48 | } 49 | 50 | export interface ResendEmailVerificationRequest { 51 | login: string; 52 | accountStore?: Object; 53 | organizationNameKey?: string; 54 | } 55 | 56 | export interface PasswordResetRequest { 57 | accountStore?: Object; 58 | organizationNameKey?: string; 59 | password: string; 60 | sptoken: string; 61 | } 62 | 63 | export interface LoginFormModel { 64 | login: string; 65 | password: string; 66 | accountStore?: Object; 67 | organizationNameKey?: string; 68 | } 69 | 70 | export interface StormpathErrorResponse { 71 | status: number; 72 | message: string; 73 | } 74 | 75 | export class LoginService { 76 | public forgot: boolean; 77 | public login: boolean; 78 | public register: boolean; 79 | 80 | constructor() { 81 | this.forgot = false; 82 | this.login = true; 83 | this.register = false; 84 | } 85 | 86 | forgotPassword(): void { 87 | this.forgot = true; 88 | this.login = false; 89 | } 90 | } 91 | 92 | @Injectable() 93 | export class Stormpath { 94 | public user$: Observable; 95 | public userSource: ReplaySubject; 96 | private currentDomain: CurrentDomain; 97 | private oauthHeaders: Headers; 98 | 99 | constructor(public http: Http, public config: StormpathConfiguration, 100 | @Inject('tokenStore') public tokenStore: TokenStoreManager) { 101 | this.userSource = new ReplaySubject(1); 102 | this.user$ = this.userSource.asObservable(); 103 | this.getAccount().subscribe(user => this.userSource.next(user)); 104 | this.currentDomain = new CurrentDomain(); 105 | this.oauthHeaders = StormpathConstants.OAUTH_HEADERS; 106 | } 107 | 108 | /** 109 | * Attempts to get the current user by making a request of the /me endpoint. 110 | * 111 | * @return {Observable} 112 | * An observable that will return an Account if the user is logged in, or false 113 | * if the user is not logged in. 114 | */ 115 | getAccount(): Observable { 116 | return this.http.get(this.config.meUri, new JsonGetOptions()) 117 | .map(this.jsonParser) 118 | .map(this.accountTransformer) 119 | .catch((error: any) => { 120 | if (error.status && error.status === 401) { 121 | return Observable.of(false); 122 | } 123 | if (error.status && error.status === 404) { 124 | return Observable.throw(new Error('/me endpoint not found, please check server configuration.')); 125 | } 126 | return Observable.throw(error); 127 | }); 128 | } 129 | 130 | /** 131 | * Retrieves the OAuth token data object from storage, relying on its set token 132 | * store for the loading implementation details. 133 | * @returns {@link AuthToken} 134 | */ 135 | getToken(): AuthToken { 136 | return this.tokenStore.get(this.config.oauthTokenName); 137 | } 138 | 139 | getRegistrationViewModel(): any { 140 | return this.http.get(this.config.registerUri, new JsonGetOptions()) 141 | .map(this.jsonParser) 142 | .catch(this.errorTranslator); 143 | } 144 | 145 | /** 146 | * Attempts to register a new account by making a POST request to the 147 | * /register endpoint. 148 | * 149 | * @return {Observable} 150 | * An observable that will return an Account if the POST was successful. 151 | */ 152 | register(form: Object): Observable { 153 | let observable: Observable = this.http.post(this.config.registerUri, JSON.stringify(form), new JsonPostOptions()) 154 | .map(this.jsonParser) 155 | .map(this.accountTransformer) 156 | .catch(this.errorTranslator) 157 | .share(); 158 | return observable; 159 | } 160 | 161 | login(form: LoginFormModel): Observable { 162 | let observable: Observable; 163 | 164 | if (this.currentDomain.equals(this.config.loginUri)) { 165 | observable = this.http.post(this.config.loginUri, JSON.stringify(form), new JsonPostOptions()) 166 | .map(this.jsonParser) 167 | .map(this.accountTransformer) 168 | .catch(this.errorTranslator) 169 | .share(); 170 | 171 | observable.subscribe(user => this.userSource.next(user), () => undefined); 172 | return observable; 173 | } else { 174 | let data: string = 'username=' + encodeURIComponent(form.login) + '&password=' + 175 | encodeURIComponent(form.password) + '&grant_type=password'; 176 | 177 | observable = this.http.post(this.config.oauthLoginUri, data, { 178 | headers: this.oauthHeaders 179 | }).map(this.jsonParser) 180 | .map(token => { 181 | let authToken: AuthToken = this.tokenStore.setToken(this.config.oauthTokenName, token); 182 | return Observable.of(authToken); 183 | }).flatMap(() => { 184 | return this.getAccount(); 185 | }).catch(this.errorTranslator) 186 | .share(); 187 | 188 | observable.subscribe(user => this.userSource.next(user), () => undefined); 189 | return observable; 190 | } 191 | } 192 | 193 | logout(): void { 194 | if (this.currentDomain.equals(this.config.loginUri)) { 195 | this.http.post(this.config.logoutUri, null, new JsonGetOptions()) 196 | .catch(this.errorThrower) 197 | .subscribe(() => this.userSource.next(false)); 198 | } else { 199 | let token: AuthToken = this.getToken(); 200 | if (token) { 201 | let tokenValue: any = token.refreshToken || token.accessToken; 202 | let tokenHint: any = token.refreshToken ? 'refresh_token' : 'access_token'; 203 | let data: any = 'token=' + encodeURIComponent(tokenValue) + '&token_type_hint=' + 204 | encodeURIComponent(tokenHint); 205 | 206 | this.http.post(this.config.oauthLogoutUri, data, {headers: this.oauthHeaders}) 207 | .map((response: Response) => { 208 | this.tokenStore.remove(this.config.oauthTokenName); 209 | }) 210 | .catch(this.errorThrower) 211 | .subscribe(() => this.userSource.next(false)); 212 | } else { 213 | this.tokenStore.remove(this.config.oauthTokenName); 214 | this.userSource.next(false); 215 | } 216 | } 217 | } 218 | 219 | resendVerificationEmail(request: ResendEmailVerificationRequest): any { 220 | return this.http.post(this.config.verifyUri, JSON.stringify(request), new JsonPostOptions()) 221 | .map(this.jsonParser) 222 | .catch(this.errorTranslator); 223 | } 224 | 225 | sendPasswordResetEmail(form: ForgotPasswordFormModel): any { 226 | return this.http.post(this.config.forgotUri, JSON.stringify(form), new JsonPostOptions()) 227 | .map(this.jsonParser) 228 | .catch(this.errorTranslator); 229 | } 230 | 231 | resetPassword(form: PasswordResetRequest): any { 232 | return this.http.post(this.config.changeUri, JSON.stringify(form), new JsonPostOptions()) 233 | .map(this.jsonParser) 234 | .catch(this.errorTranslator); 235 | } 236 | 237 | verifyEmailVerificationToken(sptoken: string): any { 238 | return this.http.get(this.config.verifyUri + '?sptoken=' + sptoken, new JsonGetOptions()) 239 | .map(this.jsonParser) 240 | .catch(this.errorTranslator); 241 | } 242 | 243 | verifyPasswordResetToken(sptoken: string): any { 244 | return this.http.get(this.config.changeUri + '?sptoken=' + sptoken, new JsonGetOptions()) 245 | .map(this.jsonParser) 246 | .catch(this.errorTranslator); 247 | } 248 | 249 | /** 250 | * Returns the JSON error from an HTTP response, or a generic error if the 251 | * response is not a JSON error 252 | * @param {any} error 253 | */ 254 | private errorTranslator(error: any): Observable { 255 | let errorObject: StormpathErrorResponse; 256 | try { 257 | errorObject = error.json(); 258 | } catch (e) { 259 | console.error(error); 260 | } 261 | if (!errorObject || !errorObject.message) { 262 | errorObject = {message: 'Server Error', status: 0}; 263 | } 264 | return Observable.throw(errorObject); 265 | } 266 | 267 | private errorThrower(error: any): Observable { 268 | return Observable.throw(error); 269 | } 270 | 271 | private accountTransformer(json: any): Account { 272 | if (json && json.account) { 273 | return new Account(json.account as BaseStormpathAccount); 274 | } else { 275 | Observable.throw(new Error('expected an account response')); 276 | } 277 | } 278 | 279 | private jsonParser(res: Response): any { 280 | if (res.text() === '') { 281 | return null; 282 | } 283 | try { 284 | return res.json(); 285 | } catch (e) { 286 | throw new Error('Response was not JSON, check your server configuration'); 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/stormpath/token-store.manager.ts: -------------------------------------------------------------------------------- 1 | import { LocalStorageService } from 'ng2-webstorage'; 2 | import { CookieService } from 'angular2-cookie/core'; 3 | import { Injectable } from '@angular/core'; 4 | import { AuthToken } from './auth.token'; 5 | 6 | export abstract class TokenStoreManager { 7 | abstract get(key: string): AuthToken; 8 | 9 | abstract put(key: string, value: AuthToken): any; 10 | 11 | abstract remove(key: string): void; 12 | 13 | setToken(name: string, token: any): AuthToken { 14 | // Store a time at which we should renew the token, subtract off one second to give us some buffer of time 15 | let exp: Date = new Date(new Date().setMilliseconds(0) + ((token.expires_in - 1) * 1000)); 16 | let authToken: AuthToken = new AuthToken(token.access_token, token.refresh_token, token.token_type, 17 | token.expires_in, token.expires_in, exp); 18 | this.put(name, authToken); 19 | return authToken; 20 | } 21 | } 22 | 23 | @Injectable() 24 | export class LocalStorageTokenStoreManager extends TokenStoreManager { 25 | 26 | constructor(private localStorage: LocalStorageService) { 27 | super(); 28 | } 29 | 30 | get(key: string): AuthToken { 31 | return this.localStorage.retrieve(key); 32 | } 33 | 34 | put(key: string, value: AuthToken): void { 35 | this.localStorage.store(key, value); 36 | } 37 | 38 | remove(key: string): void { 39 | this.localStorage.clear(key); 40 | } 41 | } 42 | 43 | @Injectable() 44 | export class CookieTokenStoreManager extends TokenStoreManager { 45 | 46 | constructor(private cookieService: CookieService) { 47 | super(); 48 | } 49 | 50 | get(key: string): AuthToken { 51 | let token: any = this.cookieService.getObject(key); 52 | if (token) { 53 | return new AuthToken(token.accessToken, token.refreshToken, token.tokenType, token.expiresIn, token.expiresAt, token.exp); 54 | } else { 55 | return null; 56 | } 57 | } 58 | 59 | put(key: string, value: AuthToken): void { 60 | this.cookieService.putObject(key, value); 61 | } 62 | 63 | remove(key: string): void { 64 | this.cookieService.remove(key); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /static/angular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormpath/stormpath-sdk-angular/902640ef46bf1b3beb6e160bf999c3e5955096f7/static/angular.png -------------------------------------------------------------------------------- /static/stormpath.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormpath/stormpath-sdk-angular/902640ef46bf1b3beb6e160bf999c3e5955096f7/static/stormpath.png -------------------------------------------------------------------------------- /test/authport/authport.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, ComponentFixture } from '@angular/core/testing'; 2 | import { MockStormpathService } from '../mocks/stormpath.mock.service'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { AuthPortComponent, LoginComponent, ForgotPasswordComponent, RegisterComponent, Stormpath } from '../../src'; 5 | import { Observable } from 'rxjs/Observable'; 6 | import { EventManager } from '../../src/stormpath/event.manager'; 7 | 8 | describe('AuthPortComponent', () => { 9 | let mockStormpathService: MockStormpathService; 10 | 11 | beforeEach(() => { 12 | mockStormpathService = new MockStormpathService(); 13 | mockStormpathService.setUser({ 14 | href: 'href', 15 | username: 'username', 16 | email: 'foo@testmail.stormpath.com', 17 | givenName: '', 18 | middleName: '', 19 | surname: '', 20 | fullName: '', 21 | status: '', 22 | createdAt: '', 23 | modifiedAt: '', 24 | passwordModifiedAt: '' 25 | }); 26 | 27 | TestBed.configureTestingModule({ 28 | declarations: [AuthPortComponent, LoginComponent, ForgotPasswordComponent, RegisterComponent], 29 | imports: [FormsModule], 30 | providers: [ 31 | {provide: Stormpath, useValue: mockStormpathService}, 32 | {provide: EventManager, useClass: EventManager} 33 | ] 34 | }); 35 | }); 36 | 37 | it('should show user as logged in when account present', () => { 38 | const fixture: ComponentFixture = TestBed.createComponent(AuthPortComponent); 39 | fixture.detectChanges(); 40 | 41 | // verify data was set on component when initialized 42 | let authportComponent: any = fixture.debugElement.componentInstance; 43 | authportComponent.loggedIn$.subscribe((value: boolean) => { 44 | expect(value).toBe(true); 45 | }, error => console.log(error.message)); 46 | }); 47 | 48 | it('should show user as not logged in when no user', () => { 49 | mockStormpathService.user$ = Observable.of(false); 50 | TestBed.configureTestingModule({ 51 | declarations: [AuthPortComponent, LoginComponent, ForgotPasswordComponent, RegisterComponent], 52 | imports: [FormsModule], 53 | providers: [ 54 | {provide: Stormpath, useValue: mockStormpathService} 55 | ] 56 | }); 57 | 58 | const fixture: ComponentFixture = TestBed.createComponent(AuthPortComponent); 59 | fixture.detectChanges(); 60 | 61 | let authportComponent: any = fixture.debugElement.componentInstance; 62 | authportComponent.loggedIn$.subscribe((value: boolean) => { 63 | expect(value).toBe(false); 64 | }, error => console.log(error.message)); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/entry.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import 'core-js'; 4 | import 'zone.js/dist/zone'; 5 | import 'zone.js/dist/long-stack-trace-zone'; 6 | import 'zone.js/dist/async-test'; 7 | import 'zone.js/dist/fake-async-test'; 8 | import 'zone.js/dist/sync-test'; 9 | import 'zone.js/dist/proxy'; 10 | import 'zone.js/dist/jasmine-patch'; 11 | import 'rxjs/Rx'; 12 | import { use } from 'chai'; 13 | import * as sinonChai from 'sinon-chai'; 14 | import { TestBed } from '@angular/core/testing'; 15 | import { 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting 18 | } from '@angular/platform-browser-dynamic/testing'; 19 | 20 | use(sinonChai); 21 | 22 | TestBed.initTestEnvironment( 23 | BrowserDynamicTestingModule, 24 | platformBrowserDynamicTesting() 25 | ); 26 | 27 | declare const require: any; 28 | const testsContext: any = require.context('./', true, /\.spec/); 29 | testsContext.keys().forEach(testsContext); 30 | -------------------------------------------------------------------------------- /test/forgot-password/forgot-password.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, ComponentFixture } from '@angular/core/testing'; 2 | import { StormpathModule, ForgotPasswordComponent } from '../../src'; 3 | 4 | describe('ForgotPasswordComponent', () => { 5 | 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [StormpathModule] 9 | }); 10 | }); 11 | 12 | it('should show email field', () => { 13 | const fixture: ComponentFixture = TestBed.createComponent(ForgotPasswordComponent); 14 | fixture.detectChanges(); 15 | let html: string = fixture.nativeElement.innerHTML.trim(); 16 | 17 | expect(html).toMatch(/